From ed1cdcd19d939687da00b529fbb2f4523477deed Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:54:25 -0500 Subject: [PATCH] Leaf 1.21.4 WIP --- .github/workflows/auto-update.yml | 10 +- .../{build-1213.yml => build-1214.yml} | 32 +- .gitignore | 11 +- LICENSE.md | 2 +- Leaf-API/build.gradle.kts.patch | 138 + .../features}/0001-Rebrand.patch | 6 +- .../features}/0002-Leaf-config-files.patch | 4 +- .../features}/0003-Pufferfish-Sentry.patch | 12 - .../features}/0004-Purpur-API-Changes.patch | 395 +- .../0005-Purpur-generated-api-Changes.patch | 26 +- .../features/0006-Remove-Timings.patch | 16 +- .../0007-KeYi-Player-Skull-API.patch | 4 +- .../0008-Slice-Smooth-Teleports.patch | 4 +- ...nfigurable-LibraryLoader-maven-repos.patch | 0 .../0010-Leaves-Replay-Mod-API.patch | 22 +- Leaf-Server/build.gradle.kts.patch | 189 + ...01-Fix-Pufferfish-and-Purpur-patches.patch | 33 + ...ble-arrow-despawn-counter-by-default.patch | 4 +- ...ce-items-finding-hopper-nearby-check.patch | 22 + .../features/0001-Rebrand.patch | 62 + .../features/0002-Leaf-Config.patch | 36 + ...003-Pufferfish-Optimize-mob-spawning.patch | 166 + ...fferfish-Dynamic-Activation-of-Brain.patch | 334 + ...tle-goal-selector-during-inactive-ti.patch | 27 + ...0006-Purpur-Server-Minecraft-Changes.patch | 16816 +++++++--------- ...07-Fix-Pufferfish-and-Purpur-patches.patch | 227 + ...-Purpur-Configurable-server-mod-name.patch | 21 + .../0009-Configurable-server-GUI-name.patch | 37 + .../0010-Remove-vanilla-username-check.patch | 51 + ...eck-for-Broken-BungeeCord-Configurat.patch | 19 + ...Remove-UseItemOnPacket-Too-Far-Check.patch | 29 + ...on-for-spigot-item-merging-mechanism.patch | 21 + ...rpet-Fixes-Optimized-getBiome-method.patch | 84 +- ...et-Fixes-Use-optimized-RecipeManager.patch | 34 +- ...Akarin-Save-Json-list-asynchronously.patch | 37 + .../0017-Slice-Smooth-Teleports.patch | 49 + ...rchment-Make-FixLight-use-action-bar.patch | 6 +- .../features/0019-Leaves-Protocol-Core.patch | 113 + .../features/0020-Leaves-Jade-Protocol.patch | 127 + .../0021-Leaves-Xaero-Map-Protocol.patch | 22 + .../0022-Leaves-Syncmatica-Protocol.patch | 31 + .../features/0023-Leaves-Replay-Mod-API.patch | 423 + ...aves-Disable-moved-wrongly-threshold.patch | 49 + .../0025-Petal-Async-Pathfinding.patch | 851 + ...educe-work-done-by-game-event-system.patch | 144 +- .../features/0027-Reduce-canSee-work.patch | 10 +- .../features/0028-Fix-sprint-glitch.patch | 10 +- ...able-movement-speed-of-more-entities.patch | 134 +- ...g-of-futures-for-chunk-structure-gen.patch | 83 + ...ce-items-finding-hopper-nearby-check.patch | 25 + .../0032-Linear-region-file-format.patch | 368 + ...me-missing-Pufferfish-configurations.patch | 87 + ...missing-purpur-configuration-options.patch | 240 +- ...p-distanceToSqr-call-in-ServerEntity.patch | 32 + ...p-MapItem-update-if-the-map-does-not.patch | 25 + ...p-EntityScheduler-s-executeTick-chec.patch | 54 + ...-SparklyPaper-Optimize-canSee-checks.patch | 30 + ...ow-throttling-hopper-checks-if-the-t.patch | 24 + ...gg-and-snowball-can-knockback-player.patch | 43 + ...-getProfiler-in-PathNavigationRegion.patch | 8 +- ...reating-stats-json-bases-on-player-n.patch | 22 +- .../0043-Improve-Purpur-AFK-system.patch | 133 +- ...044-Virtual-thread-for-chat-executor.patch | 19 + ...irtual-thread-for-User-Authenticator.patch | 19 + ...Configurable-chat-message-signatures.patch | 195 + .../0047-Cache-player-profileResult.patch | 49 + ...on-editable-sign-warning-spam-in-con.patch | 19 + .../features/0049-Matter-Secure-Seed.patch | 436 + .../features/0050-Matter-Seed-Command.patch | 14 +- .../0051-Faster-Random-Generator.patch | 290 + .../0052-Don-t-save-primed-tnt-entity.patch | 22 + ...0053-Don-t-save-falling-block-entity.patch | 22 + ...0054-Configurable-connection-message.patch | 71 + ...Configurable-unknown-command-message.patch | 131 + ...m-in-BlockBehaviour-cache-blockstate.patch | 36 + ...eam-in-entity-visible-effects-filter.patch | 32 + ...d-double-iteration-in-enough-deep-sl.patch | 26 +- ...move-stream-in-trial-spawner-ticking.patch | 81 + .../0060-Remove-stream-in-Brain.patch | 79 + .../0061-Remove-stream-in-BehaviorUtils.patch | 36 + .../0062-Remove-stream-in-YieldJobSite.patch | 54 + .../0063-Remove-stream-in-PlayerSensor.patch | 41 +- .../0064-Remove-stream-in-GolemSensor.patch | 31 + .../0065-Remove-stream-in-GateBehavior.patch | 31 + ...6-Remove-stream-in-updateFluidOnEyes.patch | 76 + .../0067-Remove-stream-in-matchingSlot.patch | 14 +- ...ctive-effects-map-with-optimized-col.patch | 25 +- ...terion-map-with-optimized-collection.patch | 14 +- ...brain-maps-with-optimized-collection.patch | 8 +- .../0071-Reduce-worldgen-allocations.patch | 97 + ...he-kickPermission-instead-of-using-g.patch | 32 +- ...t-place-player-if-the-server-is-full.patch | 34 + .../paper-patches/features/0001-Rebrand.patch | 199 + .../features/0002-Leaf-Bootstrap.patch | 24 +- ...003-Pufferfish-Optimize-mob-spawning.patch | 72 + .../0004-Purpur-Server-Paper-Changes.patch | 2033 ++ ...05-Fix-Pufferfish-and-Purpur-patches.patch | 30 + .../features/0006-Remove-Timings.patch | 20 +- .../features/0007-KeYi-Player-Skull-API.patch | 10 +- .../0008-Slice-Smooth-Teleports.patch | 40 + .../features/0009-Leaves-Protocol-Core.patch | 33 + .../features/0010-Leaves-Replay-Mod-API.patch | 88 + .../0011-Skip-event-if-no-listeners.patch | 2 +- ...p-EntityScheduler-s-executeTick-chec.patch | 52 +- ...-SparklyPaper-Optimize-canSee-checks.patch | 19 +- .../0014-Including-5s-in-getTPS.patch | 19 + ...ception-on-missing-ResourceKey-value.patch | 0 ...6-Virtual-Thread-for-async-scheduler.patch | 27 - ...Configurable-chat-message-signatures.patch | 25 + .../features/0018-Matter-Secure-Seed.patch | 50 + .../0019-Faster-Random-Generator.patch | 27 + ...Configurable-unknown-command-message.patch | 26 + ...-world-map-with-optimized-collection.patch | 4 +- ...EntityType-minecraftToBukkit-convert.patch | 40 + .../features/0023-Multithreaded-Tracker.patch | 42 + .../features/0024-Asynchronous-locator.patch | 42 + ...fault-don-t-use-blockstate-snapshots.patch | 0 ...-CraftServer-getworlds-list-creation.patch | 22 + .../features/0027-Cache-chunk-key.patch | 33 + .../sentry/PufferfishSentryAppender.java | 133 + .../pufferfish/sentry/SentryManager.java | 44 + .../pufferfish/util/AsyncExecutor.java | 76 + .../pufferfish/util/IterableWrapper.java | 20 + .../util/Long2ObjectOpenHashMapWrapper.java | 42 + .../java/org/dreeam/leaf/LeafBootstrap.java | 17 + .../leaf/async/AsyncPlayerDataSaving.java | 23 + .../leaf/async/locate/AsyncLocator.java | 164 + .../org/dreeam/leaf/async/path/AsyncPath.java | 288 + .../leaf/async/path/AsyncPathProcessor.java | 51 + .../leaf/async/path/NodeEvaluatorCache.java | 45 + .../async/path/NodeEvaluatorFeatures.java | 23 + .../async/path/NodeEvaluatorGenerator.java | 11 + .../leaf/async/path/NodeEvaluatorType.java | 17 + .../leaf/async/path/PathProcessState.java | 7 + .../async/tracker/MultithreadedTracker.java | 140 + .../org/dreeam/leaf/config/ConfigModules.java | 57 + .../leaf/config/EnumConfigCategory.java | 26 + .../org/dreeam/leaf/config/LeafConfig.java | 271 + .../dreeam/leaf/config/LeafGlobalConfig.java | 137 + .../leaf/config/annotations/DoNotLoad.java | 8 + .../leaf/config/annotations/Experimental.java | 12 + .../annotations/HotReloadUnsupported.java | 8 + .../config/modules/async/AsyncLocator.java | 37 + .../modules/async/AsyncMobSpawning.java | 34 + .../modules/async/AsyncPathfinding.java | 32 + .../modules/async/AsyncPlayerDataSave.java | 28 + .../modules/async/MultithreadedTracker.java | 48 + .../modules/fixes/DontPlacePlayerIfFull.java | 25 + .../ConfigurableMaxUseItemDistance.java | 28 + .../gameplay/ConfigurableTripWireDupe.java | 18 + .../DisableMovedWronglyThreshold.java | 22 + .../config/modules/gameplay/Knockback.java | 34 + .../modules/gameplay/MaxItemsStackCount.java | 27 + .../modules/gameplay/SmoothTeleport.java | 29 + .../gameplay/UseSpigotItemMergingMech.java | 18 + .../leaf/config/modules/misc/Cache.java | 29 + .../modules/misc/ConnectionMessage.java | 41 + .../modules/misc/HiddenItemComponents.java | 60 + .../modules/misc/Including5sIngetTPS.java | 18 + .../config/modules/misc/LagCompensation.java | 29 + .../modules/misc/RegionFormatConfig.java | 62 + .../RemoveChangeNonEditableSignWarning.java | 22 + .../modules/misc/RemoveSpigotCheckBungee.java | 22 + .../misc/RemoveVanillaUsernameCheck.java | 23 + .../leaf/config/modules/misc/SecureSeed.java | 24 + .../leaf/config/modules/misc/SentryDSN.java | 43 + .../leaf/config/modules/misc/ServerBrand.java | 20 + .../modules/misc/UnknownCommandMessage.java | 23 + .../modules/network/ChatMessageSignature.java | 25 + .../modules/network/ProtocolSupport.java | 42 + .../config/modules/opt/DontSaveEntity.java | 28 + .../modules/opt/DynamicActivationofBrain.java | 81 + .../opt/EnableCachedMTBEntityTypeConvert.java | 21 + .../leaf/config/modules/opt/FastRNG.java | 80 + .../FasterStructureGenFutureSequencing.java | 21 + .../modules/opt/ReduceUselessPackets.java | 18 + .../modules/opt/SkipAIForNonAwareMob.java | 18 + .../modules/opt/SkipMapItemDataUpdates.java | 18 + .../modules/opt/ThrottleHopperWhenFull.java | 26 + .../opt/ThrottleInactiveGoalSelectorTick.java | 23 + .../opt/TileEntitySnapshotCreation.java | 18 + .../modules/opt/VT4BukkitScheduler.java | 21 + .../config/modules/opt/VT4ChatExecutor.java | 21 + .../modules/opt/VT4UserAuthenticator.java | 21 + .../org/dreeam/leaf/misc/LagCompensation.java | 114 + .../dreeam/leaf/util/HashedReferenceList.java | 282 + .../util/biome/PositionalBiomeGetter.java | 36 + .../util/cache/CachedOrNewBitsGetter.java | 21 + .../leaf/util/cache/IterateOutwardsCache.java | 71 + .../LongList2BlockPosMutableIterable.java | 46 + .../leaf/util/item/ItemStackObfuscator.java | 29 + .../util/map/StringCanonizingOpenHashMap.java | 58 + .../dreeam/leaf/util/math/CompactSineLUT.java | 90 + .../util/math/random/FasterRandomSource.java | 127 + .../leaf/version/LeafVersionFetcher.java | 17 + .../org/leavesmc/leaves/LeavesLogger.java | 24 + .../leavesmc/leaves/bot/BotStatsCounter.java | 38 + .../leaves/entity/CraftPhotographer.java | 73 + .../entity/CraftPhotographerManager.java | 82 + .../leaves/protocol/AppleSkinProtocol.java | 124 + .../leaves/protocol/AsteorBarProtocol.java | 102 + .../leaves/protocol/ChatImageProtocol.java | 147 + .../leaves/protocol/XaeroMapProtocol.java | 45 + .../protocol/chatimage/ChatImageIndex.java | 16 + .../protocol/core/LeavesCustomPayload.java | 29 + .../leaves/protocol/core/LeavesProtocol.java | 12 + .../protocol/core/LeavesProtocolManager.java | 379 + .../leaves/protocol/core/ProtocolHandler.java | 56 + .../leaves/protocol/core/ProtocolUtils.java | 52 + .../leaves/protocol/jade/JadeProtocol.java | 271 + .../protocol/jade/accessor/Accessor.java | 32 + .../protocol/jade/accessor/AccessorImpl.java | 83 + .../protocol/jade/accessor/BlockAccessor.java | 50 + .../jade/accessor/BlockAccessorImpl.java | 163 + .../jade/accessor/EntityAccessor.java | 44 + .../jade/accessor/EntityAccessorImpl.java | 123 + .../jade/payload/ReceiveDataPayload.java | 28 + .../jade/payload/RequestBlockPayload.java | 51 + .../jade/payload/RequestEntityPayload.java | 53 + .../jade/payload/ServerPingPayload.java | 49 + .../protocol/jade/provider/IJadeProvider.java | 12 + .../jade/provider/IServerDataProvider.java | 8 + .../provider/IServerExtensionProvider.java | 10 + .../ItemStorageExtensionProvider.java | 138 + .../jade/provider/ItemStorageProvider.java | 88 + .../provider/StreamServerDataProvider.java | 26 + .../jade/provider/block/BeehiveProvider.java | 34 + .../provider/block/BrewingStandProvider.java | 43 + .../jade/provider/block/CampfireProvider.java | 55 + .../block/ChiseledBookshelfProvider.java | 39 + .../provider/block/CommandBlockProvider.java | 40 + .../jade/provider/block/FurnaceProvider.java | 60 + .../provider/block/HopperLockProvider.java | 32 + .../jade/provider/block/JukeboxProvider.java | 32 + .../jade/provider/block/LecternProvider.java | 33 + .../block/MobSpawnerCooldownProvider.java | 42 + .../provider/block/ObjectNameProvider.java | 63 + .../jade/provider/block/RedstoneProvider.java | 36 + .../provider/entity/AnimalOwnerProvider.java | 43 + .../provider/entity/MobBreedingProvider.java | 44 + .../provider/entity/MobGrowthProvider.java | 43 + .../entity/NextEntityDropProvider.java | 35 + .../entity/StatusEffectsProvider.java | 45 + .../entity/ZombieVillagerProvider.java | 34 + .../protocol/jade/tool/ShearsToolHandler.java | 32 + .../protocol/jade/tool/SimpleToolHandler.java | 71 + .../protocol/jade/tool/ToolHandler.java | 17 + .../leaves/protocol/jade/util/CommonUtil.java | 79 + .../protocol/jade/util/HierarchyLookup.java | 139 + .../protocol/jade/util/IHierarchyLookup.java | 66 + .../protocol/jade/util/ItemCollector.java | 118 + .../protocol/jade/util/ItemIterator.java | 102 + .../leaves/protocol/jade/util/JadeCodec.java | 59 + .../jade/util/LootTableMineableCollector.java | 109 + .../jade/util/PairHierarchyLookup.java | 120 + .../protocol/jade/util/PriorityStore.java | 40 + .../leaves/protocol/jade/util/ViewGroup.java | 62 + .../jade/util/WrappedHierarchyLookup.java | 104 + .../syncmatica/CommunicationManager.java | 391 + .../leaves/protocol/syncmatica/Feature.java | 23 + .../protocol/syncmatica/FeatureSet.java | 67 + .../protocol/syncmatica/FileStorage.java | 80 + .../syncmatica/LocalLitematicState.java | 24 + .../protocol/syncmatica/MessageType.java | 8 + .../protocol/syncmatica/PacketType.java | 30 + .../protocol/syncmatica/PlayerIdentifier.java | 37 + .../syncmatica/PlayerIdentifierProvider.java | 46 + .../protocol/syncmatica/ServerPlacement.java | 166 + .../protocol/syncmatica/ServerPosition.java | 51 + .../protocol/syncmatica/SubRegionData.java | 90 + .../SubRegionPlacementModification.java | 65 + .../protocol/syncmatica/SyncmaticManager.java | 108 + .../syncmatica/SyncmaticaPayload.java | 27 + .../syncmatica/SyncmaticaProtocol.java | 126 + .../syncmatica/exchange/AbstractExchange.java | 66 + .../syncmatica/exchange/DownloadExchange.java | 125 + .../syncmatica/exchange/Exchange.java | 20 + .../syncmatica/exchange/ExchangeTarget.java | 39 + .../syncmatica/exchange/FeatureExchange.java | 48 + .../exchange/ModifyExchangeServer.java | 81 + .../syncmatica/exchange/UploadExchange.java | 101 + .../exchange/VersionHandshakeServer.java | 66 + .../leaves/replay/DigestOutputStream.java | 46 + .../leaves/replay/RecordMetaData.java | 23 + .../org/leavesmc/leaves/replay/Recorder.java | 285 + .../leaves/replay/RecorderOption.java | 57 + .../leavesmc/leaves/replay/ReplayFile.java | 198 + .../leavesmc/leaves/replay/ReplayMarker.java | 43 + .../leaves/replay/ServerPhotographer.java | 222 + .../replay/ServerPhotographerGameMode.java | 35 + .../leavesmc/leaves/util/UUIDSerializer.java | 17 + .../purpurmc/purpur/command/AFKCommand.java | 37 + .../region/EnumRegionFileExtension.java | 56 + .../linearpaper/region/IRegionFile.java | 28 + .../region/IRegionFileFactory.java | 52 + .../linearpaper/region/LinearRegionFile.java | 299 + .../src/main/java/su/plo/matter/Globals.java | 94 + .../src/main/java/su/plo/matter/Hashing.java | 73 + .../su/plo/matter/WorldgenCryptoRandom.java | 159 + .../0001-Purpur-generated-api-Changes.patch | 483 + .../hardfork}/0094-Fix-MC-65198.patch | 27 +- .../hardfork}/0095-Fix-MC-200418.patch | 6 +- .../hardfork}/0096-Fix-MC-119417.patch | 6 +- .../hardfork}/0097-Fix-MC-223153.patch | 6 +- .../hardfork}/0098-Fix-MC-177381.patch | 6 +- ...-LeavesProtocolManager-init-protocol.patch | 0 ...EntityType-minecraftToBukkit-convert.patch | 51 +- ...Configurable-player-knockback-zombie.patch | 6 +- ...-during-inactive-ticks-for-non-aware.patch | 36 +- ...-zombie-reinforcements-loading-chunk.patch | 6 +- ...PaperPR-Fix-some-beacon-event-issues.patch | 12 +- ...107-Dont-send-useless-entity-packets.patch | 30 +- ...on-t-spawn-if-lastSpawnState-is-null.patch | 6 +- .../0109-Multithreaded-Tracker.patch | 242 +- .../0110-Nitori-Async-playerdata-Save.patch | 56 + .../0111-Change-max-stack-count.patch | 116 + ...ze-nearby-alive-players-for-spawning.patch | 24 +- .../0113-Cache-blockstate-cache.patch | 6 +- .../hardfork}/0114-Asynchronous-locator.patch | 238 +- ...ntities-in-NearestLivingEntitySensor.patch | 6 +- ...duce-memory-footprint-of-CompoundTag.patch | 42 + .../0117-Optimize-Entity-distanceToSqr.patch | 6 +- ...se-snapshots-for-TileEntity-getOwner.patch | 23 + ...fault-don-t-use-blockstate-snapshots.patch | 34 + .../0120-Cache-tile-entity-position.patch | 6 +- .../hardfork/0121-TT20-Lag-compensation.patch | 64 + .../0122-C2ME-Reduce-Allocations.patch | 24 + ...ecessary-calculations-if-player-is-n.patch | 6 +- .../hardfork}/0124-Lithium-fast-util.patch | 12 +- .../0125-Lithium-CompactSineLUT.patch | 38 + .../0126-Lithium-IterateOutwardsCache.patch | 33 + .../hardfork/0127-Lithium-HashedList.patch | 28 + .../0128-Smooth-teleport-config.patch | 47 +- ...hread-safe-ban-list-date-format-pars.patch | 12 +- ...startEachNonRunningBehavior-in-Brain.patch | 6 +- .../0131-Lithium-equipment-tracking.patch | 273 +- ...-CraftServer-getworlds-list-creation.patch | 0 .../0133-C2ME-Optimize-world-gen-math.patch | 24 +- .../hardfork}/0134-Cache-chunk-key.patch | 12 +- .../0135-Cache-random-tick-block-status.patch | 6 +- .../0136-Cache-canHoldAnyFluid-result.patch | 12 +- .../0137-Configurable-tripwire-dupe.patch | 20 + .../hardfork}/server/0002-Decompile-fix.patch | 13 +- ...R-Rewrite-framed-map-tracker-ticking.patch | 2 + ...e-Remove-stream-in-PoiCompetitorScan.patch | 2 + .../hardfork}/server/0099-Fix-MC-150224.patch | 2 + ...specified-item-components-to-clients.patch | 1 + .../server/0111-Change-max-stack-count.patch | 2 + ...Allow-unknown-event-thread-execution.patch | 0 ...e-color-distance-check-in-MapPalette.patch | 0 .../legacy}/server/0002-Leaf-Config-v1.patch | 0 .../legacy}/server/0003-Leaf-Config-v2.patch | 0 .../0004-Leaf-Config-legacy-converter.patch | 0 .../server/0010-Pufferfish-Entity-TTL.patch | 0 ...Allow-unknown-event-thread-execution.patch | 0 ...log4j-compatible-with-future-release.patch | 0 ...-Fix-vehicle-teleport-by-end-gateway.patch | 0 .../0039-NachoSpigot-Async-Explosion.patch | 0 .../0041-Petal-Multithreaded-Tracker.patch | 0 .../0048-Fix-keepalive-kicked-name.patch | 0 ...8-PandaSpigot-Configurable-knockback.patch | 0 .../legacy}/server/0051-Fix-MC-26678.patch | 0 ...oleAppender-NPE-error-on-server-clos.patch | 0 ...inearPurpur-Add-Linear-region-format.patch | 0 ...ust-remove-all-locks-on-region-files.patch | 0 .../legacy}/server/0056-Fix-MC-2025.patch | 0 ...he-coordinate-key-used-for-nearby-pl.patch | 0 ...60-Moonrise-Bitstorage-optimisations.patch | 0 ...oonrise-block-counting-optimisations.patch | 0 ...rise-Optimise-BiomeManager-getFiddle.patch | 0 ...-s-method-to-fix-plugin-incompatibil.patch | 0 ...send-chunk-radius-packet-from-Player.patch | 0 ...g-when-attempting-to-locate-a-buried.patch | 0 ...ect-lookup-by-chunk-for-NearbyPlayer.patch | 0 ...0068-Block-log4j-rce-exploit-in-chat.patch | 0 .../legacy}/server/0068-Fix-MC-172047.patch | 0 ...e-countEntries-for-low-size-SimpleBi.patch | 0 ...69-Fix-NPE-during-creating-GUI-graph.patch | 0 ...-Moonrise-fluid-method-optimisations.patch | 0 .../0070-Configurable-bamboo-collision.patch | 0 ...0070-Moonrise-optimise-palette-reads.patch | 0 ...-a-shadow-fork-that-supports-Java-21.patch | 0 .../server/0081-Implement-Noisium.patch | 0 ...083-Ignore-terminal-provider-warning.patch | 0 ...-Optimize-Reduce-expensive-iteration.patch | 0 ...084-Fix-console-freeze-above-JAVA-22.patch | 0 ...-output-display-on-Pterodactyl-panel.patch | 0 ...stream-in-RecipeManager-getRecipeFor.patch | 0 ...k-nearby-fire-or-lava-on-entity-move.patch | 0 .../server/0094-Fix-Nova-compatibility.patch | 0 .../0102-Remove-stream-in-PlacedFeature.patch | 0 .../legacy}/server/0110-Fix-MC-183518.patch | 0 ...work-done-in-CraftMapCanvas.drawImag.patch | 0 ...rformance-of-RecipeManager-removeRec.patch | 0 ...o-jline-terminal-ffm-on-java-22-and-.patch | 0 .../0151-Better-inline-world-height.patch | 0 Leaf-archived-patches/ver/README.md | 5 + ...ion-optimized-PoweredRailBlock-logic.patch | 0 .../0042-Optimize-Minecart-collisions.patch | 0 .../server/0044-Faster-Natural-Spawning.patch | 0 ...e-nearby-players-for-spawning-iterat.patch | 0 ...-Moonrise-Optimise-checkInsideBlocks.patch | 0 ...treams-for-block-retrieval-in-Entity.patch | 0 ...mplexity-to-make-block-isValid-calls.patch | 0 ...er-PR-Throttle-failed-spawn-attempts.patch | 0 ...-behavior-in-fluid-caused-by-inconsi.patch | 0 ...yncExecutor-for-MAIN_WORKER_EXECUTOR.patch | 0 README.md | 14 +- build-data/dev-imports.txt | 1 - build.gradle.kts | 144 +- gradle.properties | 17 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 3 +- patches/api/0006-Bump-Dependencies.patch | 151 - patches/server/0001-Rebrand.patch | 857 - patches/server/0003-Leaf-Config.patch | 615 - patches/server/0005-Pufferfish-Utils.patch | 164 - patches/server/0006-Pufferfish-Sentry.patch | 266 - ...007-Pufferfish-Optimize-mob-spawning.patch | 256 - ...fferfish-Dynamic-Activation-of-Brain.patch | 423 - ...tle-goal-selector-during-inactive-ti.patch | 56 - ...11-Fix-Pufferfish-and-Purpur-patches.patch | 325 - ...-Purpur-Configurable-server-mod-name.patch | 45 - .../0013-Configurable-server-GUI-name.patch | 53 - patches/server/0015-Bump-Dependencies.patch | 109 - .../0016-Remove-vanilla-username-check.patch | 80 - ...eck-for-Broken-BungeeCord-Configurat.patch | 47 - ...Remove-UseItemOnPacket-Too-Far-Check.patch | 59 - ...on-for-spigot-item-merging-mechanism.patch | 45 - ...Akarin-Save-Json-list-asynchronously.patch | 44 - .../server/0025-Slice-Smooth-Teleports.patch | 78 - patches/server/0027-Leaves-Server-Utils.patch | 43 - .../server/0028-Leaves-Protocol-Core.patch | 763 - .../server/0029-Leaves-Jade-Protocol.patch | 3556 ---- .../0030-Leaves-Appleskin-Protocol.patch | 160 - .../0031-Leaves-Xaero-Map-Protocol.patch | 98 - .../0032-Leaves-Syncmatica-Protocol.patch | 2092 -- patches/server/0033-Chat-Image-protocol.patch | 198 - patches/server/0034-Asteor-Bar-protocol.patch | 139 - .../server/0035-Leaves-Replay-Mod-API.patch | 1667 -- ...aves-Disable-moved-wrongly-threshold.patch | 76 - ...ndom-for-xaeroMapServerID-generation.patch | 28 - .../server/0038-Petal-Async-Pathfinding.patch | 1335 -- ...g-of-futures-for-chunk-structure-gen.patch | 110 - ...ce-items-finding-hopper-nearby-check.patch | 42 - .../0045-Linear-region-file-format.patch | 923 - ...me-missing-Pufferfish-configurations.patch | 73 - ...p-distanceToSqr-call-in-ServerEntity.patch | 32 - ...p-MapItem-update-if-the-map-does-not.patch | 49 - ...ow-throttling-hopper-checks-if-the-t.patch | 56 - ...gg-and-snowball-can-knockback-player.patch | 77 - .../server/0057-Including-5s-in-getTPS.patch | 43 - ...062-Virtual-thread-for-chat-executor.patch | 46 - ...irtual-thread-for-User-Authenticator.patch | 46 - ...Configurable-chat-message-signatures.patch | 204 - .../0065-Cache-player-profileResult.patch | 93 - ...on-editable-sign-warning-spam-in-con.patch | 47 - patches/server/0067-Matter-Secure-Seed.patch | 850 - .../server/0069-Faster-Random-Generator.patch | 538 - .../0070-Don-t-save-primed-tnt-entity.patch | 54 - ...0071-Don-t-save-falling-block-entity.patch | 41 - ...0072-Configurable-connection-message.patch | 114 - ...Configurable-unknown-command-message.patch | 184 - ...m-in-BlockBehaviour-cache-blockstate.patch | 37 - ...eam-in-entity-visible-effects-filter.patch | 27 - ...move-stream-in-trial-spawner-ticking.patch | 82 - .../server/0079-Remove-stream-in-Brain.patch | 80 - .../0080-Remove-stream-in-BehaviorUtils.patch | 36 - .../0081-Remove-stream-in-YieldJobSite.patch | 53 - .../0083-Remove-stream-in-GolemSensor.patch | 31 - .../0084-Remove-stream-in-GateBehavior.patch | 32 - ...5-Remove-stream-in-updateFluidOnEyes.patch | 84 - .../0091-Reduce-worldgen-allocations.patch | 137 - ...t-place-player-if-the-server-is-full.patch | 65 - .../0110-Nitori-Async-playerdata-Save.patch | 119 - ...duce-memory-footprint-of-CompoundTag.patch | 107 - ...se-snapshots-for-TileEntity-getOwner.patch | 47 - .../server/0121-TT20-Lag-compensation.patch | 220 - .../server/0122-C2ME-Reduce-Allocations.patch | 51 - .../server/0125-Lithium-CompactSineLUT.patch | 134 - .../0126-Lithium-IterateOutwardsCache.patch | 164 - patches/server/0127-Lithium-HashedList.patch | 316 - .../0137-Configurable-tripwire-dupe.patch | 44 - settings.gradle.kts | 11 +- todos.md | 12 +- 485 files changed, 29906 insertions(+), 30502 deletions(-) rename .github/workflows/{build-1213.yml => build-1214.yml} (75%) create mode 100644 Leaf-API/build.gradle.kts.patch rename {patches/api => Leaf-API/paper-patches/features}/0001-Rebrand.patch (89%) rename {patches/api => Leaf-API/paper-patches/features}/0002-Leaf-config-files.patch (82%) rename {patches/api => Leaf-API/paper-patches/features}/0003-Pufferfish-Sentry.patch (94%) rename {patches/api => Leaf-API/paper-patches/features}/0004-Purpur-API-Changes.patch (94%) rename patches/generated-api/0001-Purpur-generated-api-Changes.patch => Leaf-API/paper-patches/features/0005-Purpur-generated-api-Changes.patch (55%) rename patches/api/0005-Remove-Timings.patch => Leaf-API/paper-patches/features/0006-Remove-Timings.patch (99%) rename {patches/api => Leaf-API/paper-patches/features}/0007-KeYi-Player-Skull-API.patch (87%) rename {patches/api => Leaf-API/paper-patches/features}/0008-Slice-Smooth-Teleports.patch (93%) rename {patches/api => Leaf-API/paper-patches/features}/0009-Configurable-LibraryLoader-maven-repos.patch (100%) rename {patches/api => Leaf-API/paper-patches/features}/0010-Leaves-Replay-Mod-API.patch (88%) create mode 100644 Leaf-Server/build.gradle.kts.patch create mode 100644 Leaf-Server/gale-patches/features/0001-Fix-Pufferfish-and-Purpur-patches.patch rename patches/server/0020-KeYi-Disable-arrow-despawn-counter-by-default.patch => Leaf-Server/gale-patches/features/0002-KeYi-Disable-arrow-despawn-counter-by-default.patch (89%) create mode 100644 Leaf-Server/gale-patches/features/0003-Reduce-items-finding-hopper-nearby-check.patch create mode 100644 Leaf-Server/minecraft-patches/features/0001-Rebrand.patch create mode 100644 Leaf-Server/minecraft-patches/features/0002-Leaf-Config.patch create mode 100644 Leaf-Server/minecraft-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch create mode 100644 Leaf-Server/minecraft-patches/features/0004-Pufferfish-Dynamic-Activation-of-Brain.patch create mode 100644 Leaf-Server/minecraft-patches/features/0005-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch rename patches/server/0010-Purpur-Server-Changes.patch => Leaf-Server/minecraft-patches/features/0006-Purpur-Server-Minecraft-Changes.patch (61%) create mode 100644 Leaf-Server/minecraft-patches/features/0007-Fix-Pufferfish-and-Purpur-patches.patch create mode 100644 Leaf-Server/minecraft-patches/features/0008-Purpur-Configurable-server-mod-name.patch create mode 100644 Leaf-Server/minecraft-patches/features/0009-Configurable-server-GUI-name.patch create mode 100644 Leaf-Server/minecraft-patches/features/0010-Remove-vanilla-username-check.patch create mode 100644 Leaf-Server/minecraft-patches/features/0011-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch create mode 100644 Leaf-Server/minecraft-patches/features/0012-Remove-UseItemOnPacket-Too-Far-Check.patch create mode 100644 Leaf-Server/minecraft-patches/features/0013-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch rename patches/server/0022-Carpet-Fixes-Optimized-getBiome-method.patch => Leaf-Server/minecraft-patches/features/0014-Carpet-Fixes-Optimized-getBiome-method.patch (67%) rename patches/server/0023-Carpet-Fixes-Use-optimized-RecipeManager.patch => Leaf-Server/minecraft-patches/features/0015-Carpet-Fixes-Use-optimized-RecipeManager.patch (57%) create mode 100644 Leaf-Server/minecraft-patches/features/0016-Akarin-Save-Json-list-asynchronously.patch create mode 100644 Leaf-Server/minecraft-patches/features/0017-Slice-Smooth-Teleports.patch rename patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch => Leaf-Server/minecraft-patches/features/0018-Parchment-Make-FixLight-use-action-bar.patch (88%) create mode 100644 Leaf-Server/minecraft-patches/features/0019-Leaves-Protocol-Core.patch create mode 100644 Leaf-Server/minecraft-patches/features/0020-Leaves-Jade-Protocol.patch create mode 100644 Leaf-Server/minecraft-patches/features/0021-Leaves-Xaero-Map-Protocol.patch create mode 100644 Leaf-Server/minecraft-patches/features/0022-Leaves-Syncmatica-Protocol.patch create mode 100644 Leaf-Server/minecraft-patches/features/0023-Leaves-Replay-Mod-API.patch create mode 100644 Leaf-Server/minecraft-patches/features/0024-Leaves-Disable-moved-wrongly-threshold.patch create mode 100644 Leaf-Server/minecraft-patches/features/0025-Petal-Async-Pathfinding.patch rename patches/server/0039-Petal-reduce-work-done-by-game-event-system.patch => Leaf-Server/minecraft-patches/features/0026-Petal-reduce-work-done-by-game-event-system.patch (52%) rename patches/server/0040-Reduce-canSee-work.patch => Leaf-Server/minecraft-patches/features/0027-Reduce-canSee-work.patch (80%) rename patches/server/0041-Fix-sprint-glitch.patch => Leaf-Server/minecraft-patches/features/0028-Fix-sprint-glitch.patch (59%) rename patches/server/0042-Configurable-movement-speed-of-more-entities.patch => Leaf-Server/minecraft-patches/features/0029-Configurable-movement-speed-of-more-entities.patch (57%) create mode 100644 Leaf-Server/minecraft-patches/features/0030-Faster-sequencing-of-futures-for-chunk-structure-gen.patch create mode 100644 Leaf-Server/minecraft-patches/features/0031-Reduce-items-finding-hopper-nearby-check.patch create mode 100644 Leaf-Server/minecraft-patches/features/0032-Linear-region-file-format.patch create mode 100644 Leaf-Server/minecraft-patches/features/0033-Plazma-Add-some-missing-Pufferfish-configurations.patch rename patches/server/0047-Plazma-Add-missing-purpur-configuration-options.patch => Leaf-Server/minecraft-patches/features/0034-Plazma-Add-missing-purpur-configuration-options.patch (59%) create mode 100644 Leaf-Server/minecraft-patches/features/0035-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch create mode 100644 Leaf-Server/minecraft-patches/features/0036-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch create mode 100644 Leaf-Server/minecraft-patches/features/0037-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch create mode 100644 Leaf-Server/minecraft-patches/features/0038-SparklyPaper-Optimize-canSee-checks.patch create mode 100644 Leaf-Server/minecraft-patches/features/0039-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch create mode 100644 Leaf-Server/minecraft-patches/features/0040-Polpot-Make-egg-and-snowball-can-knockback-player.patch rename patches/server/0056-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch => Leaf-Server/minecraft-patches/features/0041-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch (65%) rename patches/server/0058-Remove-useless-creating-stats-json-bases-on-player-n.patch => Leaf-Server/minecraft-patches/features/0042-Remove-useless-creating-stats-json-bases-on-player-n.patch (51%) rename patches/server/0060-Improve-Purpur-AFK-system.patch => Leaf-Server/minecraft-patches/features/0043-Improve-Purpur-AFK-system.patch (63%) create mode 100644 Leaf-Server/minecraft-patches/features/0044-Virtual-thread-for-chat-executor.patch create mode 100644 Leaf-Server/minecraft-patches/features/0045-Virtual-thread-for-User-Authenticator.patch create mode 100644 Leaf-Server/minecraft-patches/features/0046-Mirai-Configurable-chat-message-signatures.patch create mode 100644 Leaf-Server/minecraft-patches/features/0047-Cache-player-profileResult.patch create mode 100644 Leaf-Server/minecraft-patches/features/0048-Prevent-change-non-editable-sign-warning-spam-in-con.patch create mode 100644 Leaf-Server/minecraft-patches/features/0049-Matter-Secure-Seed.patch rename patches/server/0068-Matter-Seed-Command.patch => Leaf-Server/minecraft-patches/features/0050-Matter-Seed-Command.patch (70%) create mode 100644 Leaf-Server/minecraft-patches/features/0051-Faster-Random-Generator.patch create mode 100644 Leaf-Server/minecraft-patches/features/0052-Don-t-save-primed-tnt-entity.patch create mode 100644 Leaf-Server/minecraft-patches/features/0053-Don-t-save-falling-block-entity.patch create mode 100644 Leaf-Server/minecraft-patches/features/0054-Configurable-connection-message.patch create mode 100644 Leaf-Server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch create mode 100644 Leaf-Server/minecraft-patches/features/0056-Remove-stream-in-BlockBehaviour-cache-blockstate.patch create mode 100644 Leaf-Server/minecraft-patches/features/0057-Remove-stream-in-entity-visible-effects-filter.patch rename patches/server/0077-Remove-stream-and-double-iteration-in-enough-deep-sl.patch => Leaf-Server/minecraft-patches/features/0058-Remove-stream-and-double-iteration-in-enough-deep-sl.patch (50%) create mode 100644 Leaf-Server/minecraft-patches/features/0059-Remove-stream-in-trial-spawner-ticking.patch create mode 100644 Leaf-Server/minecraft-patches/features/0060-Remove-stream-in-Brain.patch create mode 100644 Leaf-Server/minecraft-patches/features/0061-Remove-stream-in-BehaviorUtils.patch create mode 100644 Leaf-Server/minecraft-patches/features/0062-Remove-stream-in-YieldJobSite.patch rename patches/server/0082-Remove-stream-in-PlayerSensor.patch => Leaf-Server/minecraft-patches/features/0063-Remove-stream-in-PlayerSensor.patch (53%) create mode 100644 Leaf-Server/minecraft-patches/features/0064-Remove-stream-in-GolemSensor.patch create mode 100644 Leaf-Server/minecraft-patches/features/0065-Remove-stream-in-GateBehavior.patch create mode 100644 Leaf-Server/minecraft-patches/features/0066-Remove-stream-in-updateFluidOnEyes.patch rename patches/server/0086-Remove-stream-in-matchingSlot.patch => Leaf-Server/minecraft-patches/features/0067-Remove-stream-in-matchingSlot.patch (51%) rename patches/server/0087-Replace-Entity-active-effects-map-with-optimized-col.patch => Leaf-Server/minecraft-patches/features/0068-Replace-Entity-active-effects-map-with-optimized-col.patch (62%) rename patches/server/0088-Replace-criterion-map-with-optimized-collection.patch => Leaf-Server/minecraft-patches/features/0069-Replace-criterion-map-with-optimized-collection.patch (64%) rename patches/server/0090-Replace-brain-maps-with-optimized-collection.patch => Leaf-Server/minecraft-patches/features/0070-Replace-brain-maps-with-optimized-collection.patch (87%) create mode 100644 Leaf-Server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch rename patches/server/0092-Use-caffeine-cache-kickPermission-instead-of-using-g.patch => Leaf-Server/minecraft-patches/features/0072-Use-caffeine-cache-kickPermission-instead-of-using-g.patch (51%) create mode 100644 Leaf-Server/minecraft-patches/features/0073-Do-not-place-player-if-the-server-is-full.patch create mode 100644 Leaf-Server/paper-patches/features/0001-Rebrand.patch rename patches/server/0004-Leaf-Bootstrap.patch => Leaf-Server/paper-patches/features/0002-Leaf-Bootstrap.patch (61%) create mode 100644 Leaf-Server/paper-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch create mode 100644 Leaf-Server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch create mode 100644 Leaf-Server/paper-patches/features/0005-Fix-Pufferfish-and-Purpur-patches.patch rename patches/server/0014-Remove-Timings.patch => Leaf-Server/paper-patches/features/0006-Remove-Timings.patch (77%) rename patches/server/0019-KeYi-Player-Skull-API.patch => Leaf-Server/paper-patches/features/0007-KeYi-Player-Skull-API.patch (82%) create mode 100644 Leaf-Server/paper-patches/features/0008-Slice-Smooth-Teleports.patch create mode 100644 Leaf-Server/paper-patches/features/0009-Leaves-Protocol-Core.patch create mode 100644 Leaf-Server/paper-patches/features/0010-Leaves-Replay-Mod-API.patch rename patches/server/0048-Skip-event-if-no-listeners.patch => Leaf-Server/paper-patches/features/0011-Skip-event-if-no-listeners.patch (94%) rename patches/server/0052-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch => Leaf-Server/paper-patches/features/0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch (66%) rename patches/server/0053-SparklyPaper-Optimize-canSee-checks.patch => Leaf-Server/paper-patches/features/0013-SparklyPaper-Optimize-canSee-checks.patch (74%) create mode 100644 Leaf-Server/paper-patches/features/0014-Including-5s-in-getTPS.patch rename patches/server/0059-Don-t-throw-exception-on-missing-ResourceKey-value.patch => Leaf-Server/paper-patches/features/0015-Don-t-throw-exception-on-missing-ResourceKey-value.patch (100%) rename patches/server/0061-Virtual-Thread-for-async-scheduler.patch => Leaf-Server/paper-patches/features/0016-Virtual-Thread-for-async-scheduler.patch (72%) create mode 100644 Leaf-Server/paper-patches/features/0017-Mirai-Configurable-chat-message-signatures.patch create mode 100644 Leaf-Server/paper-patches/features/0018-Matter-Secure-Seed.patch create mode 100644 Leaf-Server/paper-patches/features/0019-Faster-Random-Generator.patch create mode 100644 Leaf-Server/paper-patches/features/0020-Configurable-unknown-command-message.patch rename patches/server/0089-Replace-world-map-with-optimized-collection.patch => Leaf-Server/paper-patches/features/0021-Replace-world-map-with-optimized-collection.patch (88%) create mode 100644 Leaf-Server/paper-patches/features/0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch create mode 100644 Leaf-Server/paper-patches/features/0023-Multithreaded-Tracker.patch create mode 100644 Leaf-Server/paper-patches/features/0024-Asynchronous-locator.patch rename patches/server/0119-EMC-Default-don-t-use-blockstate-snapshots.patch => Leaf-Server/paper-patches/features/0025-EMC-Default-don-t-use-blockstate-snapshots.patch (100%) create mode 100644 Leaf-Server/paper-patches/features/0026-Faster-CraftServer-getworlds-list-creation.patch create mode 100644 Leaf-Server/paper-patches/features/0027-Cache-chunk-key.patch create mode 100644 Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java create mode 100644 Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java create mode 100644 Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java create mode 100644 Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java create mode 100644 Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/LeafBootstrap.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/ConfigModules.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafConfig.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/MaxItemsStackCount.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/HiddenItemComponents.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ChatExecutor.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/misc/LagCompensation.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/item/ItemStackObfuscator.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java create mode 100644 Leaf-Server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/LeavesLogger.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/Recorder.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java create mode 100644 Leaf-Server/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java create mode 100644 Leaf-Server/src/main/java/org/purpurmc/purpur/command/AFKCommand.java create mode 100644 Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java create mode 100644 Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java create mode 100644 Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java create mode 100644 Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java create mode 100644 Leaf-Server/src/main/java/su/plo/matter/Globals.java create mode 100644 Leaf-Server/src/main/java/su/plo/matter/Hashing.java create mode 100644 Leaf-Server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java create mode 100644 Leaf-api-generator/paper-patches/features/0001-Purpur-generated-api-Changes.patch rename {patches/server => Leaf-archived-patches/hardfork}/0094-Fix-MC-65198.patch (68%) rename {patches/server => Leaf-archived-patches/hardfork}/0095-Fix-MC-200418.patch (77%) rename {patches/server => Leaf-archived-patches/hardfork}/0096-Fix-MC-119417.patch (77%) rename {patches/server => Leaf-archived-patches/hardfork}/0097-Fix-MC-223153.patch (73%) rename {patches/server => Leaf-archived-patches/hardfork}/0098-Fix-MC-177381.patch (74%) rename {patches/server => Leaf-archived-patches/hardfork}/0100-Optimize-LeavesProtocolManager-init-protocol.patch (100%) rename {patches/server => Leaf-archived-patches/hardfork}/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch (86%) rename {patches/server => Leaf-archived-patches/hardfork}/0102-Configurable-player-knockback-zombie.patch (92%) rename {patches/server => Leaf-archived-patches/hardfork}/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch (60%) rename {patches/server => Leaf-archived-patches/hardfork}/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch (88%) rename {patches/server => Leaf-archived-patches/hardfork}/0106-PaperPR-Fix-some-beacon-event-issues.patch (89%) rename {patches/server => Leaf-archived-patches/hardfork}/0107-Dont-send-useless-entity-packets.patch (65%) rename {patches/server => Leaf-archived-patches/hardfork}/0108-Don-t-spawn-if-lastSpawnState-is-null.patch (82%) rename {patches/server => Leaf-archived-patches/hardfork}/0109-Multithreaded-Tracker.patch (63%) create mode 100644 Leaf-archived-patches/hardfork/0110-Nitori-Async-playerdata-Save.patch create mode 100644 Leaf-archived-patches/hardfork/0111-Change-max-stack-count.patch rename {patches/server => Leaf-archived-patches/hardfork}/0112-Optimize-nearby-alive-players-for-spawning.patch (87%) rename {patches/server => Leaf-archived-patches/hardfork}/0113-Cache-blockstate-cache.patch (88%) rename {patches/server => Leaf-archived-patches/hardfork}/0114-Asynchronous-locator.patch (55%) rename {patches/server => Leaf-archived-patches/hardfork}/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch (93%) create mode 100644 Leaf-archived-patches/hardfork/0116-Further-reduce-memory-footprint-of-CompoundTag.patch rename {patches/server => Leaf-archived-patches/hardfork}/0117-Optimize-Entity-distanceToSqr.patch (94%) create mode 100644 Leaf-archived-patches/hardfork/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch create mode 100644 Leaf-archived-patches/hardfork/0119-EMC-Default-don-t-use-blockstate-snapshots.patch rename {patches/server => Leaf-archived-patches/hardfork}/0120-Cache-tile-entity-position.patch (89%) create mode 100644 Leaf-archived-patches/hardfork/0121-TT20-Lag-compensation.patch create mode 100644 Leaf-archived-patches/hardfork/0122-C2ME-Reduce-Allocations.patch rename {patches/server => Leaf-archived-patches/hardfork}/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch (85%) rename {patches/server => Leaf-archived-patches/hardfork}/0124-Lithium-fast-util.patch (87%) create mode 100644 Leaf-archived-patches/hardfork/0125-Lithium-CompactSineLUT.patch create mode 100644 Leaf-archived-patches/hardfork/0126-Lithium-IterateOutwardsCache.patch create mode 100644 Leaf-archived-patches/hardfork/0127-Lithium-HashedList.patch rename {patches/server => Leaf-archived-patches/hardfork}/0128-Smooth-teleport-config.patch (70%) rename {patches/server => Leaf-archived-patches/hardfork}/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch (91%) rename {patches/server => Leaf-archived-patches/hardfork}/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch (90%) rename {patches/server => Leaf-archived-patches/hardfork}/0131-Lithium-equipment-tracking.patch (66%) rename {patches/server => Leaf-archived-patches/hardfork}/0132-Faster-CraftServer-getworlds-list-creation.patch (100%) rename {patches/server => Leaf-archived-patches/hardfork}/0133-C2ME-Optimize-world-gen-math.patch (74%) rename {patches/server => Leaf-archived-patches/hardfork}/0134-Cache-chunk-key.patch (94%) rename {patches/server => Leaf-archived-patches/hardfork}/0135-Cache-random-tick-block-status.patch (90%) rename {patches/server => Leaf-archived-patches/hardfork}/0136-Cache-canHoldAnyFluid-result.patch (86%) create mode 100644 Leaf-archived-patches/hardfork/0137-Configurable-tripwire-dupe.patch rename {patches => Leaf-archived-patches/removed/hardfork}/server/0002-Decompile-fix.patch (84%) rename {patches => Leaf-archived-patches/removed/hardfork}/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch (99%) rename {patches => Leaf-archived-patches/removed/hardfork}/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch (99%) rename {patches => Leaf-archived-patches/removed/hardfork}/server/0099-Fix-MC-150224.patch (97%) rename {patches => Leaf-archived-patches/removed/hardfork}/server/0103-Hide-specified-item-components-to-clients.patch (99%) rename {patches => Leaf-archived-patches/removed/hardfork}/server/0111-Change-max-stack-count.patch (99%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/api/0007-KTP-Allow-unknown-event-thread-execution.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/api/0010-Paper-PR-Optimise-color-distance-check-in-MapPalette.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0002-Leaf-Config-v1.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0003-Leaf-Config-v2.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0004-Leaf-Config-legacy-converter.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0010-Pufferfish-Entity-TTL.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0020-KTP-Allow-unknown-event-thread-execution.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0036-Fix-Make-log4j-compatible-with-future-release.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0039-NachoSpigot-Async-Explosion.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0041-Petal-Multithreaded-Tracker.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0048-Fix-keepalive-kicked-name.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0048-PandaSpigot-Configurable-knockback.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0051-Fix-MC-26678.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0051-Fix-TerminalConsoleAppender-NPE-error-on-server-clos.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0053-LinearPurpur-Add-Linear-region-format.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0054-LinearPurpur-Just-remove-all-locks-on-region-files.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0056-Fix-MC-2025.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0060-Moonrise-Bitstorage-optimisations.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0062-Moonrise-block-counting-optimisations.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0063-Moonrise-Optimise-BiomeManager-getFiddle.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0063-Redirect-to-Gale-s-method-to-fix-plugin-incompatibil.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0064-Moonrise-Do-not-send-chunk-radius-packet-from-Player.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0065-Fix-MC-249136-lag-when-attempting-to-locate-a-buried.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0065-Moonrise-Add-direct-lookup-by-chunk-for-NearbyPlayer.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0068-Block-log4j-rce-exploit-in-chat.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0068-Fix-MC-172047.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0068-Moonrise-Optimise-countEntries-for-low-size-SimpleBi.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0069-Fix-NPE-during-creating-GUI-graph.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0069-Moonrise-fluid-method-optimisations.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0070-Configurable-bamboo-collision.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0070-Moonrise-optimise-palette-reads.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0075-Use-a-shadow-fork-that-supports-Java-21.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0081-Implement-Noisium.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0083-Ignore-terminal-provider-warning.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0083-Tracking-Optimize-Reduce-expensive-iteration.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0084-Fix-console-freeze-above-JAVA-22.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0085-Fix-console-output-display-on-Pterodactyl-panel.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0090-Remove-stream-in-RecipeManager-getRecipeFor.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0093-Optimize-check-nearby-fire-or-lava-on-entity-move.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0094-Fix-Nova-compatibility.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0102-Remove-stream-in-PlacedFeature.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0110-Fix-MC-183518.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0121-Paper-PR-Reduce-work-done-in-CraftMapCanvas.drawImag.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0125-Paper-Improve-performance-of-RecipeManager-removeRec.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0126-Paper-Fix-move-to-jline-terminal-ffm-on-java-22-and-.patch (100%) rename {patches/removed => Leaf-archived-patches/removed/legacy}/server/0151-Better-inline-world-height.patch (100%) create mode 100644 Leaf-archived-patches/ver/README.md rename {patches => Leaf-archived-patches}/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0042-Optimize-Minecart-collisions.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0044-Faster-Natural-Spawning.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0061-Moonrise-Optimize-nearby-players-for-spawning-iterat.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0112-Reduce-object-complexity-to-make-block-isValid-calls.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0122-Paper-PR-Throttle-failed-spawn-attempts.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0143-Fix-wrong-entity-behavior-in-fluid-caused-by-inconsi.patch (100%) rename {patches => Leaf-archived-patches}/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch (100%) delete mode 100644 patches/api/0006-Bump-Dependencies.patch delete mode 100644 patches/server/0001-Rebrand.patch delete mode 100644 patches/server/0003-Leaf-Config.patch delete mode 100644 patches/server/0005-Pufferfish-Utils.patch delete mode 100644 patches/server/0006-Pufferfish-Sentry.patch delete mode 100644 patches/server/0007-Pufferfish-Optimize-mob-spawning.patch delete mode 100644 patches/server/0008-Pufferfish-Dynamic-Activation-of-Brain.patch delete mode 100644 patches/server/0009-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch delete mode 100644 patches/server/0011-Fix-Pufferfish-and-Purpur-patches.patch delete mode 100644 patches/server/0012-Purpur-Configurable-server-mod-name.patch delete mode 100644 patches/server/0013-Configurable-server-GUI-name.patch delete mode 100644 patches/server/0015-Bump-Dependencies.patch delete mode 100644 patches/server/0016-Remove-vanilla-username-check.patch delete mode 100644 patches/server/0017-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch delete mode 100644 patches/server/0018-Remove-UseItemOnPacket-Too-Far-Check.patch delete mode 100644 patches/server/0021-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch delete mode 100644 patches/server/0024-Akarin-Save-Json-list-asynchronously.patch delete mode 100644 patches/server/0025-Slice-Smooth-Teleports.patch delete mode 100644 patches/server/0027-Leaves-Server-Utils.patch delete mode 100644 patches/server/0028-Leaves-Protocol-Core.patch delete mode 100644 patches/server/0029-Leaves-Jade-Protocol.patch delete mode 100644 patches/server/0030-Leaves-Appleskin-Protocol.patch delete mode 100644 patches/server/0031-Leaves-Xaero-Map-Protocol.patch delete mode 100644 patches/server/0032-Leaves-Syncmatica-Protocol.patch delete mode 100644 patches/server/0033-Chat-Image-protocol.patch delete mode 100644 patches/server/0034-Asteor-Bar-protocol.patch delete mode 100644 patches/server/0035-Leaves-Replay-Mod-API.patch delete mode 100644 patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch delete mode 100644 patches/server/0037-Faster-Random-for-xaeroMapServerID-generation.patch delete mode 100644 patches/server/0038-Petal-Async-Pathfinding.patch delete mode 100644 patches/server/0043-Faster-sequencing-of-futures-for-chunk-structure-gen.patch delete mode 100644 patches/server/0044-Reduce-items-finding-hopper-nearby-check.patch delete mode 100644 patches/server/0045-Linear-region-file-format.patch delete mode 100644 patches/server/0046-Plazma-Add-some-missing-Pufferfish-configurations.patch delete mode 100644 patches/server/0050-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch delete mode 100644 patches/server/0051-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch delete mode 100644 patches/server/0054-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch delete mode 100644 patches/server/0055-Polpot-Make-egg-and-snowball-can-knockback-player.patch delete mode 100644 patches/server/0057-Including-5s-in-getTPS.patch delete mode 100644 patches/server/0062-Virtual-thread-for-chat-executor.patch delete mode 100644 patches/server/0063-Virtual-thread-for-User-Authenticator.patch delete mode 100644 patches/server/0064-Mirai-Configurable-chat-message-signatures.patch delete mode 100644 patches/server/0065-Cache-player-profileResult.patch delete mode 100644 patches/server/0066-Prevent-change-non-editable-sign-warning-spam-in-con.patch delete mode 100644 patches/server/0067-Matter-Secure-Seed.patch delete mode 100644 patches/server/0069-Faster-Random-Generator.patch delete mode 100644 patches/server/0070-Don-t-save-primed-tnt-entity.patch delete mode 100644 patches/server/0071-Don-t-save-falling-block-entity.patch delete mode 100644 patches/server/0072-Configurable-connection-message.patch delete mode 100644 patches/server/0073-Configurable-unknown-command-message.patch delete mode 100644 patches/server/0075-Remove-stream-in-BlockBehaviour-cache-blockstate.patch delete mode 100644 patches/server/0076-Remove-stream-in-entity-visible-effects-filter.patch delete mode 100644 patches/server/0078-Remove-stream-in-trial-spawner-ticking.patch delete mode 100644 patches/server/0079-Remove-stream-in-Brain.patch delete mode 100644 patches/server/0080-Remove-stream-in-BehaviorUtils.patch delete mode 100644 patches/server/0081-Remove-stream-in-YieldJobSite.patch delete mode 100644 patches/server/0083-Remove-stream-in-GolemSensor.patch delete mode 100644 patches/server/0084-Remove-stream-in-GateBehavior.patch delete mode 100644 patches/server/0085-Remove-stream-in-updateFluidOnEyes.patch delete mode 100644 patches/server/0091-Reduce-worldgen-allocations.patch delete mode 100644 patches/server/0093-Do-not-place-player-if-the-server-is-full.patch delete mode 100644 patches/server/0110-Nitori-Async-playerdata-Save.patch delete mode 100644 patches/server/0116-Further-reduce-memory-footprint-of-CompoundTag.patch delete mode 100644 patches/server/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch delete mode 100644 patches/server/0121-TT20-Lag-compensation.patch delete mode 100644 patches/server/0122-C2ME-Reduce-Allocations.patch delete mode 100644 patches/server/0125-Lithium-CompactSineLUT.patch delete mode 100644 patches/server/0126-Lithium-IterateOutwardsCache.patch delete mode 100644 patches/server/0127-Lithium-HashedList.patch delete mode 100644 patches/server/0137-Configurable-tripwire-dupe.patch diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml index ce8e80ee..fffd5afb 100644 --- a/.github/workflows/auto-update.yml +++ b/.github/workflows/auto-update.yml @@ -45,23 +45,23 @@ jobs: - name: Grant execute permission for gradlew run: | cd Leaf - git config --global user.name "Dreeam" + git config --global user.name "Dreeam-qwq" git config --global user.email 61569423+Dreeam-qwq@users.noreply.github.com chmod +x gradlew - uses: actions/setup-java@main with: distribution: 'zulu' - java-version: 21 + java-version: '21' - name: Running tests before push run: | cd Leaf if ! git diff --quiet; then echo "Running tests...." - ./gradlew applyPatches - ./gradlew createMojmapPaperclipJar - ./gradlew rebuildPatches + ./gradlew applyAllPatches + ./gradlew build + ./gradlew rebuildAllServerPatches fi - name: Check for changes and write to repository diff --git a/.github/workflows/build-1213.yml b/.github/workflows/build-1214.yml similarity index 75% rename from .github/workflows/build-1213.yml rename to .github/workflows/build-1214.yml index 158292b3..32cf4941 100644 --- a/.github/workflows/build-1213.yml +++ b/.github/workflows/build-1214.yml @@ -1,17 +1,16 @@ -name: Build Leaf 1.21.3 +name: Build Leaf 1.21.4 on: push: - branches: [ "ver/1.21.3" ] + branches: [ "dev/1.21.4" ] pull_request: - branches: [ "ver/1.21.3" ] + branches: [ "ver/1.21.4" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@main - - uses: gradle/actions/wrapper-validation@main - name: Set up JDK uses: graalvm/setup-graalvm@main with: @@ -33,8 +32,8 @@ jobs: - name: Rename Paperclip Jar run: | - mv build/libs/leaf-paperclip-1.21.3-R0.1-SNAPSHOT-mojmap.jar ./leaf-1.21.3-mojmap.jar - mv build/libs/leaf-paperclip-1.21.3-R0.1-SNAPSHOT-reobf.jar ./leaf-1.21.3-reobf.jar + mv leaf-server/build/libs/leaf-paperclip-1.21.4-R0.1-SNAPSHOT-mojmap.jar ./leaf-1.21.4-mojmap.jar + mv leaf-server/build/libs/leaf-paperclip-1.21.4-R0.1-SNAPSHOT-reobf.jar ./leaf-1.21.4-reobf.jar - name: Publish API if: github.event_name != 'pull_request' @@ -44,18 +43,19 @@ jobs: export REPO_USER=${{ secrets.REPO_USER }} echo "REPO_PASSWORD=${{ secrets.REPO_PASSWORD }}" >> $GITHUB_ENV export REPO_PASSWORD=${{ secrets.REPO_PASSWORD }} - ./gradlew :leaf-api:publish - ./gradlew publishDevBundlePublicationToLeafRepository -PpublishDevBundle=true + ./gradlew publish + # TODO + # ./gradlew publishDevBundlePublicationToLeafRepository -PpublishDevBundle=true - name: Upload Leaf uses: actions/upload-artifact@main with: - name: Leaf 1.21.3 - path: ./leaf-1.21.3-*.jar + name: Leaf 1.21.4 + path: ./leaf-1.21.4-*.jar - - name: Rename Paperclip Jar + - name: Rename Leaf Jar run: | - mv ./leaf-1.21.3-mojmap.jar ./leaf-1.21.3.jar + mv ./leaf-1.21.4-mojmap.jar ./leaf-1.21.4.jar # TODO: marvinpinto/action-automatic-releases is archived, need to find new one # Notes: create releases every time, similar changelogs with this, all commits for changelogs @@ -63,8 +63,8 @@ jobs: if: github.event_name != 'pull_request' uses: marvinpinto/action-automatic-releases@master with: - title: "Leaf 1.21.3" - automatic_release_tag: "ver-1.21.3" + title: "Leaf 1.21.4" + automatic_release_tag: "ver-1.21.4" repo_token: "${{ secrets.GITHUB_TOKEN }}" - files: "./leaf-1.21.3.jar" - prerelease: false + files: "./leaf-1.21.4.jar" + prerelease: true diff --git a/.gitignore b/.gitignore index d616b01b..0ce9978c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,10 @@ build-data patches/todo run -Leaf-API -Leaf-MojangAPI -Leaf-Server -paper-api-generator +leaf-api/build.gradle.kts +leaf-server/build.gradle.kts +leaf-server/src/minecraft +gale-api +gale-server +paper-api +paper-server diff --git a/LICENSE.md b/LICENSE.md index 42aa1164..be435f08 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,4 +2,4 @@ Paperweight files are licensed under [MIT](https://opensource.org/licenses/MIT) (included in `license/MIT.txt`). Patches are licensed under MIT, unless indicated differently in their header (some patches are licensed under [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) (included in `license/GPL-3.0.txt`) or [LGPL-3.0](https://www.gnu.org/licenses/lgpl-3.0.html) (included in `license/LGPL-3.0.txt`)). -Binaries are licensed under GPL-3.0. \ No newline at end of file +Binaries are licensed under GPL-3.0. diff --git a/Leaf-API/build.gradle.kts.patch b/Leaf-API/build.gradle.kts.patch new file mode 100644 index 00000000..f9d522c8 --- /dev/null +++ b/Leaf-API/build.gradle.kts.patch @@ -0,0 +1,138 @@ +--- a/gale-api/build.gradle.kts ++++ b/gale-api/build.gradle.kts +@@ -12,8 +_,10 @@ + val annotationsVersion = "26.0.1" + val bungeeCordChatVersion = "1.20-R0.2" + val adventureVersion = "4.18.0" +-val slf4jVersion = "2.0.9" +-val log4jVersion = "2.17.1" ++// Leaf start - Bump Dependencies ++val slf4jVersion = "2.0.16" ++val log4jVersion = "2.24.3" ++// Leaf end - Bump Dependencies + + val apiAndDocs: Configuration by configurations.creating { + attributes { +@@ -41,9 +_,9 @@ + dependencies { + + // api dependencies are listed transitively to API consumers +- api("com.google.guava:guava:33.3.1-jre") ++ api("com.google.guava:guava:33.4.0-jre") // Leaf - Bump Dependencies + api("com.google.code.gson:gson:2.11.0") +- api("org.yaml:snakeyaml:2.2") ++ api("org.yaml:snakeyaml:2.3") // Leaf - Bump Dependencies + api("org.joml:joml:1.10.8") { + isTransitive = false // https://github.com/JOML-CI/JOML/issues/352 + } +@@ -54,6 +_,7 @@ + api("org.apache.logging.log4j:log4j-api:$log4jVersion") + api("org.slf4j:slf4j-api:$slf4jVersion") + api("com.mojang:brigadier:1.3.10") ++ api("io.sentry:sentry:8.0.0-rc.3") // Pufferfish + + // Deprecate bungeecord-chat in favor of adventure + api("net.md-5:bungeecord-chat:$bungeeCordChatVersion-deprecated+build.19") { +@@ -68,29 +_,37 @@ + apiAndDocs("net.kyori:adventure-text-serializer-plain") + apiAndDocs("net.kyori:adventure-text-logger-slf4j") + +- api("org.apache.maven:maven-resolver-provider:3.9.6") // make API dependency for Paper Plugins +- compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") +- compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") ++ // Leaf start - Bump Dependencies ++ api("org.apache.maven:maven-resolver-provider:3.9.9") // make API dependency for Paper Plugins ++ compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.22") // Dreeam TODO - Update to 2.0.1 ++ compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.22") // Dreeam TODO - Update to 2.0.1 ++ // Leaf start - Bump Dependencies + + // Annotations - Slowly migrate to jspecify + val annotations = "org.jetbrains:annotations:$annotationsVersion" + compileOnly(annotations) + testCompileOnly(annotations) + +- val checkerQual = "org.checkerframework:checker-qual:3.33.0" ++ val checkerQual = "org.checkerframework:checker-qual:3.48.4" // Leaf - Bump Dependencies + compileOnlyApi(checkerQual) + testCompileOnly(checkerQual) + + api("org.jspecify:jspecify:1.0.0") + + // Test dependencies +- testImplementation("org.apache.commons:commons-lang3:3.12.0") +- testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") +- testImplementation("org.hamcrest:hamcrest:2.2") +- testImplementation("org.mockito:mockito-core:5.14.1") ++ // Leaf start - Bump Dependencies ++ testImplementation("org.apache.commons:commons-lang3:3.17.0") ++ testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") ++ testImplementation("org.hamcrest:hamcrest:3.0") ++ testImplementation("org.mockito:mockito-core:5.15.2") + testImplementation("org.ow2.asm:asm-tree:9.7.1") +- mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // configure mockito agent that is needed in newer java versions ++ mockitoAgent("org.mockito:mockito-core:5.15.2") { isTransitive = false } // configure mockito agent that is needed in newer java versions + testRuntimeOnly("org.junit.platform:junit-platform-launcher") ++ ++ // commons-lang3 is removed in maven-resolver-provider since 3.9.8 ++ // Add this because bukkit api still need it. ++ compileOnly("org.apache.commons:commons-lang3:3.17.0") ++ // Leaf end - Bump Dependencies + } + + val generatedApiPath: java.nio.file.Path = rootProject.layout.projectDirectory.dir("paper-api/src/generated/java").asFile.toPath() // Gale - project setup +@@ -105,17 +_,21 @@ + srcDir(generatedApiPath) + // Gale start - project setup + srcDir(file("../paper-api/src/main/java")) ++ srcDir(file("../gale-api/src/main/java")) // Leaf - project setup + } + resources { + srcDir(file("../paper-api/src/main/resources")) ++ srcDir(file("../gale-api/src/main/resources")) // Leaf - project setup + } + } + test { + java { + srcDir(file("../paper-api/src/test/java")) ++ srcDir(file("../gale-api/src/test/java")) // Leaf - project setup + } + resources { + srcDir(file("../paper-api/src/test/resources")) ++ srcDir(file("../gale-api/src/test/resources")) // Leaf - project setup + // Gale end - project setup + } + } +@@ -203,8 +_,8 @@ + options.use() + options.isDocFilesSubDirs = true + options.links( +- "https://guava.dev/releases/33.3.1-jre/api/docs/", +- "https://javadoc.io/doc/org.yaml/snakeyaml/2.2/", ++ "https://guava.dev/releases/33.4.0-jre/api/docs/", // Leaf - Bump Dependencies ++ "https://javadoc.io/doc/org.yaml/snakeyaml/2.3/", // Leaf - Bump Dependencies + "https://javadoc.io/doc/org.jetbrains/annotations/$annotationsVersion/", + "https://javadoc.io/doc/org.joml/joml/1.10.8/", + "https://www.javadoc.io/doc/com.google.code.gson/gson/2.11.0", +@@ -217,8 +_,8 @@ + "https://jd.advntr.dev/text-serializer-plain/$adventureVersion/", + "https://jd.advntr.dev/text-logger-slf4j/$adventureVersion/", + "https://javadoc.io/doc/org.slf4j/slf4j-api/$slf4jVersion/", +- "https://javadoc.io/doc/org.apache.logging.log4j/log4j-api/$log4jVersion/", +- "https://javadoc.io/doc/org.apache.maven.resolver/maven-resolver-api/1.7.3", ++ "https://javadoc.io/doc/org.apache.logging.log4j/log4j-api/2.20.0/", // Leaf - Bump Dependencies ++ "https://javadoc.io/doc/org.apache.maven.resolver/maven-resolver-api/1.9.22", // Leaf - Bump Dependencies + ) + options.tags("apiNote:a:API Note:") + +@@ -276,6 +_,11 @@ + jarToScan.set(tasks.jar.flatMap { it.archiveFile }) + classpath.from(configurations.compileClasspath) + } ++// Leaf start - Bump Dependencies ++repositories { ++ mavenCentral() ++} ++// Leaf end - Bump Dependencies + tasks.check { + dependsOn(scanJarForOldGeneratedCode) + } diff --git a/patches/api/0001-Rebrand.patch b/Leaf-API/paper-patches/features/0001-Rebrand.patch similarity index 89% rename from patches/api/0001-Rebrand.patch rename to Leaf-API/paper-patches/features/0001-Rebrand.patch index 2fe34c81..a74b4259 100644 --- a/patches/api/0001-Rebrand.patch +++ b/Leaf-API/paper-patches/features/0001-Rebrand.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Rebrand diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java -index b68770f6992b044d13e67d9affa6933c90ca8fb8..bef5b7b089f33c8ce1304292804ea56d16e574c9 100644 +index b68770f6992b044d13e67d9affa6933c90ca8fb8..184ed5a41a138d14757f12acd4ec3113cb165b72 100644 --- a/src/main/java/io/papermc/paper/ServerBuildInfo.java +++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java @@ -26,6 +26,17 @@ public interface ServerBuildInfo { @@ -14,9 +14,9 @@ index b68770f6992b044d13e67d9affa6933c90ca8fb8..bef5b7b089f33c8ce1304292804ea56d + // Leaf start + /** -+ * The brand id for Purpur. ++ * The brand id for Pufferfish. + */ -+ Key BRAND_PURPUR_ID = Key.key("purpurmc", "purpur"); ++ Key BRAND_PUFFERFISH_ID = Key.key("pufferfish", "pufferfish"); + /** + * The brand id for Leaf. + */ diff --git a/patches/api/0002-Leaf-config-files.patch b/Leaf-API/paper-patches/features/0002-Leaf-config-files.patch similarity index 82% rename from patches/api/0002-Leaf-config-files.patch rename to Leaf-API/paper-patches/features/0002-Leaf-config-files.patch index 07bac7c8..f8311314 100644 --- a/patches/api/0002-Leaf-config-files.patch +++ b/Leaf-API/paper-patches/features/0002-Leaf-config-files.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Leaf config files diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index a3b84a898210b012d5fbae55f0bb739b5487505c..597b06c2cb289c235ad14009b186aa00f55cefcd 100644 +index 2dccd3f72e188a34c9cdf80816aadb0c79351279..331006b854dfe785c567baf8489afaac01a99cd0 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2336,6 +2336,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2383,6 +2383,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi // Paper end diff --git a/patches/api/0003-Pufferfish-Sentry.patch b/Leaf-API/paper-patches/features/0003-Pufferfish-Sentry.patch similarity index 94% rename from patches/api/0003-Pufferfish-Sentry.patch rename to Leaf-API/paper-patches/features/0003-Pufferfish-Sentry.patch index d7945237..dcb8c4c2 100644 --- a/patches/api/0003-Pufferfish-Sentry.patch +++ b/Leaf-API/paper-patches/features/0003-Pufferfish-Sentry.patch @@ -6,18 +6,6 @@ Subject: [PATCH] Pufferfish: Sentry Original license: GPL v3 Original project: https://github.com/pufferfish-gg/Pufferfish -diff --git a/build.gradle.kts b/build.gradle.kts -index 3c50294a0a68fd8e9fd4028d41495ad3b9781b1e..5fac022f2373105df7f9cfb292642c4a399c7db4 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -64,6 +64,7 @@ dependencies { - apiAndDocs("net.kyori:adventure-text-logger-slf4j") - api("org.apache.logging.log4j:log4j-api:$log4jVersion") - api("org.slf4j:slf4j-api:$slf4jVersion") -+ api("io.sentry:sentry:5.4.0") // Pufferfish - - implementation("org.ow2.asm:asm:9.7.1") - implementation("org.ow2.asm:asm-commons:9.7.1") diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java new file mode 100644 index 0000000000000000000000000000000000000000..c7772aac00f6db664f7a5673bc2585fa025e6aad diff --git a/patches/api/0004-Purpur-API-Changes.patch b/Leaf-API/paper-patches/features/0004-Purpur-API-Changes.patch similarity index 94% rename from patches/api/0004-Purpur-API-Changes.patch rename to Leaf-API/paper-patches/features/0004-Purpur-API-Changes.patch index 3f6c248f..93e543ba 100644 --- a/patches/api/0004-Purpur-API-Changes.patch +++ b/Leaf-API/paper-patches/features/0004-Purpur-API-Changes.patch @@ -1,22 +1,27 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Github Actions -Date: Thu, 12 Dec 2024 11:10:02 +0000 +Date: Thu, 16 Jan 2025 11:21:11 +0000 Subject: [PATCH] Purpur API Changes Original license: MIT Original project: https://github.com/PurpurMC/Purpur -Commit: 16ce24aa7eb08232030e4570e027f7baefa5f3f9 +Commit: dd4143984219cea8440913b7918322b5ba59265a -Patches below are removed in this patch: -Pufferfish-API-Changes.patch -Build-System-Changes.patch -Clean-up-version-command-output.patch -Remove-Timings.patch -Add-log-suppression-for-LibraryLoader.patch +Patches listed below are removed in this patch, They exists in Gale or Leaf: +* "co/aikar/timings/TimedEventExecutor.java.patch" +* "co/aikar/timings/Timing.java.patch" +* "co/aikar/timings/Timings.java.patch" +* "co/aikar/timings/TimingsCommand.java.patch" +* "com/destroystokyo/paper/util/VersionFetcher.java.patch" +* "org/bukkit/command/defaults/VersionCommand.java.patch" +* "org/bukkit/command/SimpleCommandMap.java.patch" (Timings changes) +* "org/bukkit/plugin/java/JavaPluginLoader.java.patch" +* "org/bukkit/plugin/java/LibraryLoader.java.patch" +* "org/spigotmc/CustomTimingsHandler.java.patch" diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java -index bef5b7b089f33c8ce1304292804ea56d16e574c9..5873b42c198d7972e4147d019c790b612ae1b735 100644 +index 184ed5a41a138d14757f12acd4ec3113cb165b72..2240bb704dd9fa62836efa1b3d6ec4d7744d9b1d 100644 --- a/src/main/java/io/papermc/paper/ServerBuildInfo.java +++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java @@ -26,6 +26,13 @@ public interface ServerBuildInfo { @@ -25,24 +30,24 @@ index bef5b7b089f33c8ce1304292804ea56d16e574c9..5873b42c198d7972e4147d019c790b61 + // Purpur start + /** -+ * The brand id for Pufferfish. ++ * The brand id for Purpur. + */ -+ Key BRAND_PUFFERFISH_ID = Key.key("pufferfish", "pufferfish"); ++ Key BRAND_PURPUR_ID = Key.key("purpurmc", "purpur"); + // Purpur end + // Leaf start /** - * The brand id for Purpur. + * The brand id for Pufferfish. diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 2ddaa049b5b14ef7de2899862b3cc69537361b00..2477a560ed345116cd9d4e90f98694b84a1faf28 100644 +index 6c24786d349b88b414a1d96e4c0415f75d8fa545..196109f427fb1169785b82b4cfb52c53329d2bea 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -3035,4 +3035,127 @@ public final class Bukkit { +@@ -3047,4 +3047,133 @@ public final class Bukkit { public static Server.Spigot spigot() { return server.spigot(); } + -+ // Purpur start ++ // Purpur start - Bring back server name + /** + * Get the name of this server + * @return the name of the server @@ -51,7 +56,9 @@ index 2ddaa049b5b14ef7de2899862b3cc69537361b00..2477a560ed345116cd9d4e90f98694b8 + public static String getServerName() { + return server.getServerName(); + } ++ // Purpur end - Bring back server name + ++ // Purpur start - Lagging threshold + /** + * Check if server is lagging according to laggy threshold setting + * @@ -60,7 +67,9 @@ index 2ddaa049b5b14ef7de2899862b3cc69537361b00..2477a560ed345116cd9d4e90f98694b8 + public static boolean isLagging() { + return server.isLagging(); + } ++ // Purpur end - Lagging threshold + ++ // Purpur start - Added the ability to add combustible items + /** + * Add an Item as fuel for furnaces + * @@ -79,7 +88,9 @@ index 2ddaa049b5b14ef7de2899862b3cc69537361b00..2477a560ed345116cd9d4e90f98694b8 + public static void removeFuel(@NotNull Material material) { + server.removeFuel(material); + } ++ // Purpur end - Added the ability to add combustible items + ++ // Purpur start - Debug Marker API + /** + * Creates debug block highlight on specified block location and show it to all players on the server. + *

@@ -163,10 +174,10 @@ index 2ddaa049b5b14ef7de2899862b3cc69537361b00..2477a560ed345116cd9d4e90f98694b8 + public static void clearBlockHighlights() { + server.clearBlockHighlights(); + } -+ // Purpur end ++ // Purpur end - Debug Marker API } diff --git a/src/main/java/org/bukkit/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java -index 918a045165cdcde264bc24082b7afebb407271de..e98d6321c5f2cdde91b54f8a74cbcc045eae75a8 100644 +index 918a045165cdcde264bc24082b7afebb407271de..f283bcabff7fe6eede6cf4344537e43048c35166 100644 --- a/src/main/java/org/bukkit/ChatColor.java +++ b/src/main/java/org/bukkit/ChatColor.java @@ -456,4 +456,77 @@ public enum ChatColor { @@ -174,7 +185,7 @@ index 918a045165cdcde264bc24082b7afebb407271de..e98d6321c5f2cdde91b54f8a74cbcc04 } } + -+ // Purpur start ++ // Purpur start - ChatColor conveniences + /** + * Convert legacy string into a string ready to be parsed by MiniMessage + * @@ -245,18 +256,18 @@ index 918a045165cdcde264bc24082b7afebb407271de..e98d6321c5f2cdde91b54f8a74cbcc04 + public static String color(@Nullable String str, boolean parseHex) { + return str != null ? net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', parseHex ? replaceHex(str) : str) : str; + } -+ // Purpur end ++ // Purpur end - ChatColor conveniences } diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 028ac35df6c4d044d07b3869751736d418c1eb0e..95c1f331f364c5fafa100860f3c9674f18888714 100644 +index 9afafc00e457c721a1b20b05c6a5d330caa40dfb..6469d4e1097e694d8bf00610ed8d34de132bd19c 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -5840,4 +5840,40 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla +@@ -5812,4 +5812,40 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla return this.asItemType().getDefaultDataTypes(); } // Paper end - data component API + -+ // Purpur start ++ // Purpur start - ItemStack convenience methods + public boolean isArmor() { + switch (this) { + // @@ -290,7 +301,7 @@ index 028ac35df6c4d044d07b3869751736d418c1eb0e..95c1f331f364c5fafa100860f3c9674f + return false; + } + } -+ // Purpur end ++ // Purpur end - ItemStack convenience methods } diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java index 5622fe3165baad8138c22cfc016ed6c3834cf702..6d31b561d915180fcd473b317721064feed28f37 100644 @@ -404,10 +415,10 @@ index 5622fe3165baad8138c22cfc016ed6c3834cf702..6d31b561d915180fcd473b317721064f + // Purpur end - OfflinePlayer API } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 597b06c2cb289c235ad14009b186aa00f55cefcd..582c1f053020dff2cde8a1a7a1a760a84e856b7c 100644 +index 331006b854dfe785c567baf8489afaac01a99cd0..335db5f0893df51c65c5aab7f11fd6aef59a191d 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2336,6 +2336,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2383,6 +2383,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi // Paper end @@ -426,26 +437,30 @@ index 597b06c2cb289c235ad14009b186aa00f55cefcd..582c1f053020dff2cde8a1a7a1a760a8 // Leaf start @NotNull public org.bukkit.configuration.file.YamlConfiguration getLeafConfig() -@@ -2693,4 +2705,105 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2740,4 +2752,111 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ long getLastTickOversleepTime(); // Gale end - YAPFA - last tick time - API + -+ // Purpur start ++ // Purpur start - Bring back server name + /** + * Get the name of this server + * @return the name of the server + */ + @NotNull + String getServerName(); ++ // Purpur end - Bring back server name + ++ // Purpur start - Lagging threshold + /** + * Check if server is lagging according to laggy threshold setting + * + * @return True if lagging + */ + boolean isLagging(); ++ // Purpur end - Lagging threshold + ++ // Purpur start - Added the ability to add combustible items + /** + * Add an Item as fuel for furnaces + * @@ -460,7 +475,9 @@ index 597b06c2cb289c235ad14009b186aa00f55cefcd..582c1f053020dff2cde8a1a7a1a760a8 + * @param material The material that will no longer be a fuel + */ + public void removeFuel(@NotNull Material material); ++ // Purpur end - Added the ability to add combustible items + ++ // Purpur start - Debug Marker API + /** + * Creates debug block highlight on specified block location and show it to all players on the server. + *

@@ -530,13 +547,13 @@ index 597b06c2cb289c235ad14009b186aa00f55cefcd..582c1f053020dff2cde8a1a7a1a760a8 + * Clears all debug block highlights for all players on the server. + */ + void clearBlockHighlights(); -+ // Purpur end ++ // Purpur end - Debug Marker API } diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index bef54a6c8290e09cbaac20b03dde8dfb902c96b0..5f7de23e419175e55459df760c7190639ea39f18 100644 +index e99fa923d35b6dda0b02968bdcf6b43552517ea4..5ee6f017101a05b568b69627b1819d63dbe8094e 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -4246,6 +4246,86 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -4238,6 +4238,86 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @Nullable public DragonBattle getEnderDragonBattle(); @@ -624,14 +641,14 @@ index bef54a6c8290e09cbaac20b03dde8dfb902c96b0..5f7de23e419175e55459df760c719063 * Get all {@link FeatureFlag} enabled in this world. * diff --git a/src/main/java/org/bukkit/block/EntityBlockStorage.java b/src/main/java/org/bukkit/block/EntityBlockStorage.java -index 739911cda33b373f99df627a3a378b37d7d461aa..51e78c22cd021722b963fe31d1d9175d141add1a 100644 +index 739911cda33b373f99df627a3a378b37d7d461aa..58e16c12b06fa11d30b67f5038844ff9990790ae 100644 --- a/src/main/java/org/bukkit/block/EntityBlockStorage.java +++ b/src/main/java/org/bukkit/block/EntityBlockStorage.java -@@ -47,6 +47,24 @@ public interface EntityBlockStorage extends TileState { +@@ -47,6 +47,25 @@ public interface EntityBlockStorage extends TileState { @NotNull List releaseEntities(); -+ // Purpur start ++ // Purpur start - Stored Bee API + /** + * Releases a stored entity, and returns the entity in the world. + * @@ -648,19 +665,20 @@ index 739911cda33b373f99df627a3a378b37d7d461aa..51e78c22cd021722b963fe31d1d9175d + */ + @NotNull + List> getEntities(); -+ //Purpur end ++ // Purpur end - Stored Bee API ++ /** * Add an entity to the block. * diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java -index 5df19bd701c67506689fc7f49d91f99ebfbc83f0..a09b5458191eb5df4787859b72a37fa1fa2bffba 100644 +index 5df19bd701c67506689fc7f49d91f99ebfbc83f0..32c81559507a8e5085c91d466cda69d0dc11327e 100644 --- a/src/main/java/org/bukkit/command/SimpleCommandMap.java +++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -153,6 +153,19 @@ public class SimpleCommandMap implements CommandMap { return false; } -+ // Purpur start ++ // Purpur start - ExecuteCommandEvent + String[] parsedArgs = Arrays.copyOfRange(args, 1, args.length); + org.purpurmc.purpur.event.ExecuteCommandEvent event = new org.purpurmc.purpur.event.ExecuteCommandEvent(sender, target, sentCommandLabel, parsedArgs); + if (!event.callEvent()) { @@ -671,7 +689,7 @@ index 5df19bd701c67506689fc7f49d91f99ebfbc83f0..a09b5458191eb5df4787859b72a37fa1 + target = event.getCommand(); + sentCommandLabel = event.getLabel(); + parsedArgs = event.getArgs(); -+ // Purpur end ++ // Purpur end - ExecuteCommandEvent + // Paper start - Plugins do weird things to workaround normal registration if (target.timings == null) { @@ -681,19 +699,19 @@ index 5df19bd701c67506689fc7f49d91f99ebfbc83f0..a09b5458191eb5df4787859b72a37fa1 try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) - target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length)); -+ target.execute(sender, sentCommandLabel, parsedArgs); // Purpur ++ target.execute(sender, sentCommandLabel, parsedArgs); // Purpur - ExecuteCommandEvent } // target.timings.stopTiming(); // Spigot // Paper } catch (CommandException ex) { server.getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java -index 6fcc15d588239481136876d117ab346a8deac1dd..13b903e785a9ef5e513cb9d6483482133cc5f25b 100644 +index 6fcc15d588239481136876d117ab346a8deac1dd..ad04cb5542f219f617bcb6ed60cf248ab53d83bc 100644 --- a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java +++ b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java -@@ -227,6 +227,28 @@ public enum EnchantmentTarget { +@@ -227,6 +227,30 @@ public enum EnchantmentTarget { public boolean includes(@NotNull Material item) { return BREAKABLE.includes(item) || (WEARABLE.includes(item) && !item.equals(Material.ELYTRA)) || item.equals(Material.COMPASS); } -+ // Purpur start ++ // Purpur start - Add enchantment target for bows and crossbows + }, + + /** @@ -704,6 +722,8 @@ index 6fcc15d588239481136876d117ab346a8deac1dd..13b903e785a9ef5e513cb9d648348213 + public boolean includes(@NotNull Material item) { + return item.equals(Material.BOW) || item.equals(Material.CROSSBOW); + } ++ // Purpur end - Add enchantment target for bows and crossbows ++ // Purpur start - Shears can have looting enchantment + }, + + /** @@ -714,7 +734,7 @@ index 6fcc15d588239481136876d117ab346a8deac1dd..13b903e785a9ef5e513cb9d648348213 + public boolean includes(@NotNull Material item) { + return WEAPON.includes(item) || item.equals(Material.SHEARS); + } -+ // Purpur end ++ // Purpur end - Shears can have looting enchantment }; /** @@ -753,15 +773,15 @@ index 7b379fb21e800a766ad022705a12dff6d42279ab..10a8d64ad2da0be2c14f34c3e7d1957c // Paper start /** diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 19272cff8d6d040e95b2644d70acdac606e06c16..076fe310d500ebb52e705a3a69e895061702f470 100644 +index ddf7829eee5e3f0ded9319a5a0a7b2e2486320a4..49d3ca54a761e08cfe1bc770cb879223bf0e21e8 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -1172,4 +1172,55 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -1196,4 +1196,59 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ void broadcastHurtAnimation(@NotNull java.util.Collection players); // Paper end - broadcast hurt animation + -+ // Purpur start ++ // Purpur start - Ridables + /** + * Get the riding player + * @@ -790,14 +810,18 @@ index 19272cff8d6d040e95b2644d70acdac606e06c16..076fe310d500ebb52e705a3a69e89506 + * @return True if ridable in water + */ + boolean isRidableInWater(); ++ // Purpur end - Ridables + ++ // Purpur start - API for any mob to burn daylight + /** + * Checks if the entity is in daylight + * + * @return True if in daylight + */ + boolean isInDaylight(); ++ // Purpur end - API for any mob to burn daylight + ++ // Purpur start - Fire Immunity API + /** + * Checks if the entity is fire immune + * @@ -810,7 +834,7 @@ index 19272cff8d6d040e95b2644d70acdac606e06c16..076fe310d500ebb52e705a3a69e89506 + * Set this to null to restore the entity type default + */ + void setImmuneToFire(@Nullable Boolean fireImmune); -+ // Purpur end ++ // Purpur end - Fire Immunity API } diff --git a/src/main/java/org/bukkit/entity/IronGolem.java b/src/main/java/org/bukkit/entity/IronGolem.java index 655e37cb3a09610a3f3df805d6dcad17d722da62..09fd716c8fc9ea34a1cbf87bcbe22df035422a51 100644 @@ -955,19 +979,19 @@ index bc84b892cae5fe7019a3ad481e9da79956efa1fe..48eb5b00c460cccde29d327cef1d63fc + // Purpur end } diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 20e3c0f856ec034b1d7a5617337ac093386b200f..347d6cbee7daba824adf798c1ec9895ae1da67ab 100644 +index 1ee59ed42de64ae1a0a580b3f640cdcb5f51c71b..886a954b1a12e895f5d54fd35a9acb36673e3733 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3921,4 +3921,123 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3902,4 +3902,123 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ void sendEntityEffect(org.bukkit.@NotNull EntityEffect effect, @NotNull Entity target); // Paper end - entity effect API + + // Purpur start + /** -+ * Allows you to get if player uses Purpur Client ++ * Allows you to get if player uses PurpurClient + * -+ * @return True if Player uses Purpur Client ++ * @return true if player uses PurpurClient + */ + public boolean usesPurpurClient(); + @@ -1152,11 +1176,11 @@ index 14543c2238b45c526dd9aebea2aa5c22f5df54dc..5312daf33405704c74e2c9e109754285 + // Purpur end } diff --git a/src/main/java/org/bukkit/entity/Wolf.java b/src/main/java/org/bukkit/entity/Wolf.java -index c73489f4b745bc84501ce94f0227b034d9768eae..a97129e71f16ec691759add664bdfd35ab90aaed 100644 +index 346fdddd2ed8f0b8b66860c969f5e80c51c6d622..6f62277c3461d436a656cbd80bc59e70cb1a6f3a 100644 --- a/src/main/java/org/bukkit/entity/Wolf.java +++ b/src/main/java/org/bukkit/entity/Wolf.java -@@ -108,4 +108,20 @@ public interface Wolf extends Tameable, Sittable, io.papermc.paper.entity.Collar - return Registry.WOLF_VARIANT.getOrThrow(NamespacedKey.minecraft(key)); +@@ -110,4 +110,20 @@ public interface Wolf extends Tameable, Sittable, io.papermc.paper.entity.Collar + return RegistryAccess.registryAccess().getRegistry(RegistryKey.WOLF_VARIANT).getOrThrow(NamespacedKey.minecraft(key)); } } + @@ -1208,7 +1232,7 @@ index 8fdfcbc7d20fe0af6b220ab94516247093637621..f6a8928408e11a5ae723366e4ea1280d * When a player gets bad omen after killing a patrol captain. * diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java -index 81118a91c2e22e02a1f774d1cc4d3e97064087ce..3ac1e4a821a5b48d3936222cbfddadd3b803deef 100644 +index 81118a91c2e22e02a1f774d1cc4d3e97064087ce..6640df827e354cc32cea5fc71b9e464443202708 100644 --- a/src/main/java/org/bukkit/event/inventory/InventoryType.java +++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java @@ -164,7 +164,7 @@ public enum InventoryType { @@ -1216,7 +1240,7 @@ index 81118a91c2e22e02a1f774d1cc4d3e97064087ce..3ac1e4a821a5b48d3936222cbfddadd3 ; - private final int size; -+ private int size; public void setDefaultSize(int size) { this.size = size; } // Purpur - remove final and add setter ++ private int size; @ApiStatus.Internal public void setDefaultSize(int size) { this.size = size; } // Purpur - remove final and add setter private final String title; private final MenuType menuType; private final boolean isCreatable; @@ -1268,29 +1292,29 @@ index f1f97a85ec713c05c882d7588f4a3e4a017f4795..813f6cd253322538bdf96eb323dd23a7 + // Purpur end } diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 8c9654cd19af8b28fa276a55c5060eb389e60c1c..875124b06d87cd4163f0ab1d4dd75f939622f8aa 100644 +index 55457c7539c08e861263333ae40cbfe9d25814f4..9b3c3d6563c94aec225a332db5d5653887ac5f4b 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java -@@ -19,6 +19,13 @@ import org.bukkit.inventory.meta.ItemMeta; +@@ -21,6 +21,13 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.material.MaterialData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -+// Purpur start ++// Purpur start - ItemStack convenience methods +import com.google.common.collect.Multimap; +import java.util.Collection; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.data.BlockData; -+// Purpur end ++// Purpur end - ItemStack convenience methods /** * Represents a stack of items. -@@ -1318,4 +1325,482 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat +@@ -1329,4 +1336,482 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat return this.craftDelegate.matchesWithoutData(item, excludeTypes, ignoreCount); } // Paper end - data component API + -+ // Purpur start ++ // Purpur start - ItemStack convenience methods + /** + * Gets the display name that is set. + *

@@ -1766,17 +1790,17 @@ index 8c9654cd19af8b28fa276a55c5060eb389e60c1c..875124b06d87cd4163f0ab1d4dd75f93 + public boolean damage(int amount, boolean ignoreUnbreaking) { + return this.craftDelegate.damage(amount, ignoreUnbreaking); + } -+ // Purpur end ++ // Purpur end - ItemStack convenience methods } diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java -index 922bb69b5f218e489a6dd5e0f207743c1f1d3d35..9b3e292be334d21eb978373f434bf3811ec4af2b 100644 +index 922bb69b5f218e489a6dd5e0f207743c1f1d3d35..3606c6457a98e0e39d6a4b171bc11b61aac73e5c 100644 --- a/src/main/java/org/bukkit/inventory/RecipeChoice.java +++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java @@ -191,6 +191,7 @@ public interface RecipeChoice extends Predicate, Cloneable { public static class ExactChoice implements RecipeChoice { private List choices; -+ private Predicate predicate; // Purpur ++ private Predicate predicate; // Purpur - Add predicate to recipe's ExactChoice ingredient public ExactChoice(@NotNull ItemStack stack) { this(Arrays.asList(stack)); @@ -1784,7 +1808,7 @@ index 922bb69b5f218e489a6dd5e0f207743c1f1d3d35..9b3e292be334d21eb978373f434bf381 @Override public boolean test(@NotNull ItemStack t) { -+ if (predicate != null) return predicate.test(t); // Purpur ++ if (predicate != null) return predicate.test(t); // Purpur - Add predicate to recipe's ExactChoice ingredient for (ItemStack match : choices) { if (t.isSimilar(match)) { return true; @@ -1792,7 +1816,7 @@ index 922bb69b5f218e489a6dd5e0f207743c1f1d3d35..9b3e292be334d21eb978373f434bf381 return false; } -+ // Purpur start ++ // Purpur start - Add predicate to recipe's ExactChoice ingredient + @org.jetbrains.annotations.Nullable + public Predicate getPredicate() { + return predicate; @@ -1801,13 +1825,13 @@ index 922bb69b5f218e489a6dd5e0f207743c1f1d3d35..9b3e292be334d21eb978373f434bf381 + public void setPredicate(@org.jetbrains.annotations.Nullable Predicate predicate) { + this.predicate = predicate; + } -+ // Purpur end ++ // Purpur end - Add predicate to recipe's ExactChoice ingredient + @Override public int hashCode() { int hash = 7; diff --git a/src/main/java/org/bukkit/inventory/view/AnvilView.java b/src/main/java/org/bukkit/inventory/view/AnvilView.java -index 3c1aa1e036bee08304c1cdca59f6a5bc0ba306c0..709fb2d1c7e3253034a651a9f68c003601b598a4 100644 +index 3c1aa1e036bee08304c1cdca59f6a5bc0ba306c0..b273e168c976fb6e185da3ba83d9d69e0b2e7920 100644 --- a/src/main/java/org/bukkit/inventory/view/AnvilView.java +++ b/src/main/java/org/bukkit/inventory/view/AnvilView.java @@ -89,4 +89,34 @@ public interface AnvilView extends InventoryView { @@ -1815,7 +1839,7 @@ index 3c1aa1e036bee08304c1cdca59f6a5bc0ba306c0..709fb2d1c7e3253034a651a9f68c0036 void bypassEnchantmentLevelRestriction(boolean bypassEnchantmentLevelRestriction); // Paper end - bypass anvil level restrictions + -+ // Purpur start ++ // Purpur start - Anvil API + /** + * Gets if the player viewing the anvil inventory can bypass experience cost + * @@ -1843,27 +1867,27 @@ index 3c1aa1e036bee08304c1cdca59f6a5bc0ba306c0..709fb2d1c7e3253034a651a9f68c0036 + * @param canDoUnsafeEnchants whether the player viewing the anvil inventory can do unsafe enchants + */ + void setDoUnsafeEnchants(boolean canDoUnsafeEnchants); -+ // Purpur end ++ // Purpur end - Anvil API } diff --git a/src/main/java/org/bukkit/map/MapRenderer.java b/src/main/java/org/bukkit/map/MapRenderer.java -index cb7040876a99a5a7e49b81684ef0f3b79584c376..22d8f31b1b8a5dbb5ab3275068642937c097abfe 100644 +index cb7040876a99a5a7e49b81684ef0f3b79584c376..63351a0fb4263e2e7ccb6896768a5b10378de297 100644 --- a/src/main/java/org/bukkit/map/MapRenderer.java +++ b/src/main/java/org/bukkit/map/MapRenderer.java @@ -54,4 +54,12 @@ public abstract class MapRenderer { */ public abstract void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player); -+ // Purpur - start ++ // Purpur - start - Explorer Map API + /** + * Check if this is an explorer (aka treasure) map. + * + * @return True if explorer map + */ + public abstract boolean isExplorerMap(); -+ // Purpur - end ++ // Purpur - end - Explorer Map API } diff --git a/src/main/java/org/bukkit/permissions/PermissibleBase.java b/src/main/java/org/bukkit/permissions/PermissibleBase.java -index 75b77cc4fe189b4b6baa1af3663dc492e992a266..30b98d1645c571ba5c18e5cc93b0bec3f74b1d3b 100644 +index 75b77cc4fe189b4b6baa1af3663dc492e992a266..1aa3c6dbf2f2a62fbec990f931d5c5d5c8e5a0bd 100644 --- a/src/main/java/org/bukkit/permissions/PermissibleBase.java +++ b/src/main/java/org/bukkit/permissions/PermissibleBase.java @@ -169,7 +169,7 @@ public class PermissibleBase implements Permissible { @@ -1871,7 +1895,7 @@ index 75b77cc4fe189b4b6baa1af3663dc492e992a266..30b98d1645c571ba5c18e5cc93b0bec3 for (Permission perm : defaults) { String name = perm.getName().toLowerCase(Locale.ROOT); - permissions.put(name, new PermissionAttachmentInfo(parent, name, null, true)); -+ permissions.put(name, new PermissionAttachmentInfo(parent, name, null, perm.getDefault().getValue(isOp()))); // Purpur ++ permissions.put(name, new PermissionAttachmentInfo(parent, name, null, perm.getDefault().getValue(isOp()))); // Purpur - Fix default permission system Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent); calculateChildPermissions(perm.getChildren(), false, null); } @@ -1880,24 +1904,24 @@ index 75b77cc4fe189b4b6baa1af3663dc492e992a266..30b98d1645c571ba5c18e5cc93b0bec3 Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); - boolean value = entry.getValue() ^ invert; -+ boolean value = (entry.getValue() == null && perm != null ? perm.getDefault().getValue(isOp()) : entry.getValue()) ^ invert; // Purpur ++ boolean value = (entry.getValue() == null && perm != null ? perm.getDefault().getValue(isOp()) : entry.getValue()) ^ invert; // Purpur - Fix default permission system String lname = name.toLowerCase(Locale.ROOT); permissions.put(lname, new PermissionAttachmentInfo(parent, lname, attachment, value)); diff --git a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java -index 7763d6101ac61900db1e2310966b99584539fd0e..d5a42707d365ffd72532bbb1a59a1ca7145f9918 100644 +index 7763d6101ac61900db1e2310966b99584539fd0e..48c67cb3a7073db85d7cf2308e5b1aae89b7a1d2 100644 --- a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java +++ b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java @@ -18,6 +18,7 @@ public final class CommandPermissions { DefaultPermissions.registerPermission(PREFIX + "plugins", "Allows the user to view the list of plugins running on this server", PermissionDefault.TRUE, commands); DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload the server settings", PermissionDefault.OP, commands); DefaultPermissions.registerPermission(PREFIX + "version", "Allows the user to view the version of the server", PermissionDefault.TRUE, commands); -+ DefaultPermissions.registerPermission(PREFIX + "purpur", "Allows the user to use the purpur command", PermissionDefault.OP, commands); // Purpur ++ DefaultPermissions.registerPermission(PREFIX + "purpur", "Allows the user to use the purpur command", PermissionDefault.OP, commands); // Purpur - Default permissions commands.recalculatePermissibles(); return commands; diff --git a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java -index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f3505a16e 100644 +index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..5329de48a739cafac98514e24edaf5f1b9d41fff 100644 --- a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java +++ b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java @@ -31,7 +31,7 @@ public final class DefaultPermissions { @@ -1905,7 +1929,7 @@ index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f if (withLegacy) { Permission legacy = new Permission(LEGACY_PREFIX + result.getName(), result.getDescription(), PermissionDefault.FALSE); - legacy.getChildren().put(result.getName(), true); -+ legacy.getChildren().put(result.getName(), null); // Purpur ++ legacy.getChildren().put(result.getName(), null); // Purpur - Fix default permission system registerPermission(perm, false); } @@ -1914,7 +1938,7 @@ index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f @NotNull public static Permission registerPermission(@NotNull Permission perm, @NotNull Permission parent) { - parent.getChildren().put(perm.getName(), true); -+ parent.getChildren().put(perm.getName(), null); // Purpur ++ parent.getChildren().put(perm.getName(), null); // Purpur - Fix default permission system return registerPermission(perm); } @@ -1923,7 +1947,7 @@ index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f public static Permission registerPermission(@NotNull String name, @Nullable String desc, @NotNull Permission parent) { Permission perm = registerPermission(name, desc); - parent.getChildren().put(perm.getName(), true); -+ parent.getChildren().put(perm.getName(), null); // Purpur ++ parent.getChildren().put(perm.getName(), null); // Purpur - Fix default permission system return perm; } @@ -1932,7 +1956,7 @@ index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @NotNull Permission parent) { Permission perm = registerPermission(name, desc, def); - parent.getChildren().put(perm.getName(), true); -+ parent.getChildren().put(perm.getName(), null); // Purpur ++ parent.getChildren().put(perm.getName(), null); // Purpur - Fix default permission system return perm; } @@ -1941,7 +1965,7 @@ index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @Nullable Map children, @NotNull Permission parent) { Permission perm = registerPermission(name, desc, def, children); - parent.getChildren().put(perm.getName(), true); -+ parent.getChildren().put(perm.getName(), null); // Purpur ++ parent.getChildren().put(perm.getName(), null); // Purpur - Fix default permission system return perm; } @@ -1949,104 +1973,11 @@ index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..10627d2a11251a8cb01bbc3f6242d66f CommandPermissions.registerPermissions(parent); BroadcastPermissions.registerPermissions(parent); -+ PurpurPermissions.registerPermissions(); // Purpur ++ org.purpurmc.purpur.util.permissions.PurpurPermissions.registerPermissions(); // Purpur - Default permissions + parent.recalculatePermissibles(); } } -diff --git a/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java -new file mode 100644 -index 0000000000000000000000000000000000000000..baec4c87d7ea4d54934ca22fd1eb7b46dd69061b ---- /dev/null -+++ b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java -@@ -0,0 +1,87 @@ -+package org.bukkit.util.permissions; -+ -+import org.bukkit.entity.Entity; -+import org.bukkit.entity.EntityType; -+import org.bukkit.entity.Mob; -+import org.bukkit.permissions.Permission; -+import org.bukkit.permissions.PermissionDefault; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class PurpurPermissions { -+ private static final String ROOT = "purpur"; -+ private static final String PREFIX = ROOT + "."; -+ private static final Set mobs = new HashSet<>(); -+ -+ static { -+ for (EntityType mob : EntityType.values()) { -+ Class clazz = mob.getEntityClass(); -+ if (clazz != null && Mob.class.isAssignableFrom(clazz)) { -+ mobs.add(mob.getName()); -+ } -+ } -+ } -+ -+ @NotNull -+ public static Permission registerPermissions() { -+ Permission purpur = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all Purpur utilities and commands", PermissionDefault.FALSE); -+ -+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.six", "Gives the user six rows of enderchest space", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.five", "Gives the user five rows of enderchest space", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.four", "Gives the user four rows of enderchest space", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.three", "Gives the user three rows of enderchest space", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.two", "Gives the user two rows of enderchest space", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.one", "Gives the user one row of enderchest space", PermissionDefault.FALSE, purpur); -+ -+ DefaultPermissions.registerPermission(PREFIX + "debug.f3n", "Allows the user to use F3+N keybind to swap gamemodes", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "joinfullserver", "Allows the user to join a full server", PermissionDefault.OP, purpur); -+ -+ DefaultPermissions.registerPermission(PREFIX + "drop.spawners", "Allows the user to drop spawner cage when broken with diamond pickaxe with silk touch", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "place.spawners", "Allows the user to place spawner cage in the world", PermissionDefault.FALSE, purpur); -+ -+ DefaultPermissions.registerPermission(PREFIX + "mending_shift_click", "Allows the user to use shift-right-click to mend items", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "inventory_totem", "Uses a totem from anywhere in the user's inventory on death", PermissionDefault.FALSE, purpur); -+ -+ Permission anvil = DefaultPermissions.registerPermission(PREFIX + "anvil", "Allows the user to use all anvil color and format abilities", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "anvil.color", "Allows the user to use color codes in an anvil", PermissionDefault.FALSE, anvil); -+ DefaultPermissions.registerPermission(PREFIX + "anvil.minimessage", "Allows the user to use minimessage tags in an anvil", PermissionDefault.FALSE, anvil); -+ DefaultPermissions.registerPermission(PREFIX + "anvil.remove_italics", "Allows the user to remove italics in an anvil", PermissionDefault.FALSE, anvil); -+ DefaultPermissions.registerPermission(PREFIX + "anvil.format", "Allows the user to use format codes in an anvil", PermissionDefault.FALSE, anvil); -+ anvil.recalculatePermissibles(); -+ -+ Permission book = DefaultPermissions.registerPermission(PREFIX + "book", "Allows the user to use color codes on books", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "book.color.edit", "Allows the user to use color codes on books when editing", PermissionDefault.FALSE, book); -+ DefaultPermissions.registerPermission(PREFIX + "book.color.sign", "Allows the user to use color codes on books when signing", PermissionDefault.FALSE, book); -+ book.recalculatePermissibles(); -+ -+ Permission sign = DefaultPermissions.registerPermission(PREFIX + "sign", "Allows the user to use all sign abilities", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission(PREFIX + "sign.edit", "Allows the user to click signs to open sign editor", PermissionDefault.FALSE, sign); -+ DefaultPermissions.registerPermission(PREFIX + "sign.color", "Allows the user to use color codes on signs", PermissionDefault.FALSE, sign); -+ DefaultPermissions.registerPermission(PREFIX + "sign.style", "Allows the user to use style codes on signs", PermissionDefault.FALSE, sign); -+ DefaultPermissions.registerPermission(PREFIX + "sign.magic", "Allows the user to use magic/obfuscate code on signs", PermissionDefault.FALSE, sign); -+ sign.recalculatePermissibles(); -+ -+ Permission ride = DefaultPermissions.registerPermission("allow.ride", "Allows the user to ride all mobs", PermissionDefault.FALSE, purpur); -+ for (String mob : mobs) { -+ DefaultPermissions.registerPermission("allow.ride." + mob, "Allows the user to ride " + mob, PermissionDefault.FALSE, ride); -+ } -+ ride.recalculatePermissibles(); -+ -+ Permission special = DefaultPermissions.registerPermission("allow.special", "Allows the user to use all mobs special abilities", PermissionDefault.FALSE, purpur); -+ for (String mob : mobs) { -+ DefaultPermissions.registerPermission("allow.special." + mob, "Allows the user to use " + mob + " special ability", PermissionDefault.FALSE, special); -+ } -+ special.recalculatePermissibles(); -+ -+ Permission powered = DefaultPermissions.registerPermission("allow.powered", "Allows the user to toggle all mobs powered state", PermissionDefault.FALSE, purpur); -+ DefaultPermissions.registerPermission("allow.powered.creeper", "Allows the user to toggle creeper powered state", PermissionDefault.FALSE, powered); -+ powered.recalculatePermissibles(); -+ -+ DefaultPermissions.registerPermission(PREFIX + "portal.instant", "Allows the user to bypass portal wait time", PermissionDefault.FALSE, purpur); -+ -+ purpur.recalculatePermissibles(); -+ return purpur; -+ } -+} diff --git a/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java b/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..29540d55532197d2381a52ea9222b5785d224ef8 @@ -3551,18 +3482,96 @@ index 0000000000000000000000000000000000000000..cbdad4cf09c170064a45644efdf7aa0b + return getOrDefault(key.translationKey()); + } +} -diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java -index f9e4b16a21d6cc6c9cbbe06d20c8af25e72e3ddb..4028b230e7fe1c78520f227a377a2a61e8381ecc 100644 ---- a/src/test/java/org/bukkit/AnnotationTest.java -+++ b/src/test/java/org/bukkit/AnnotationTest.java -@@ -47,6 +47,10 @@ public class AnnotationTest { - "org/bukkit/plugin/java/PluginClassLoader", - // Generic functional interface - "org/bukkit/util/Consumer", -+ // Purpur start -+ "gg/pufferfish/pufferfish/sentry/SentryContext", -+ "gg/pufferfish/pufferfish/sentry/SentryContext$State", -+ // Purpur end - // Paper start - "io/papermc/paper/util/TransformingRandomAccessList", - "io/papermc/paper/util/TransformingRandomAccessList$TransformedListIterator", +diff --git a/src/main/java/org/purpurmc/purpur/util/permissions/PurpurPermissions.java b/src/main/java/org/purpurmc/purpur/util/permissions/PurpurPermissions.java +new file mode 100644 +index 0000000000000000000000000000000000000000..50647252ed654fbcf71db72a283fb8080ecee6d3 +--- /dev/null ++++ b/src/main/java/org/purpurmc/purpur/util/permissions/PurpurPermissions.java +@@ -0,0 +1,87 @@ ++package org.purpurmc.purpur.util.permissions; ++ ++import org.bukkit.entity.Entity; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Mob; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.bukkit.util.permissions.DefaultPermissions; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class PurpurPermissions { ++ private static final String ROOT = "purpur"; ++ private static final String PREFIX = ROOT + "."; ++ private static final Set mobs = new HashSet<>(); ++ ++ static { ++ for (EntityType mob : EntityType.values()) { ++ Class clazz = mob.getEntityClass(); ++ if (clazz != null && Mob.class.isAssignableFrom(clazz)) { ++ mobs.add(mob.getName()); ++ } ++ } ++ } ++ ++ @NotNull ++ public static Permission registerPermissions() { ++ Permission purpur = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all Purpur utilities and commands", PermissionDefault.FALSE); ++ ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.six", "Gives the user six rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.five", "Gives the user five rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.four", "Gives the user four rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.three", "Gives the user three rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.two", "Gives the user two rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.one", "Gives the user one row of enderchest space", PermissionDefault.FALSE, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "debug.f3n", "Allows the user to use F3+N keybind to swap gamemodes", PermissionDefault.FALSE, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "joinfullserver", "Allows the user to join a full server", PermissionDefault.OP, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "bypassIdleKick", "Allows the user to bypass being kicked while idle", PermissionDefault.FALSE, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "inventory_totem", "Allows the user to use totem of undying anywhere in their inventory", PermissionDefault.FALSE, purpur); ++ ++ Permission anvil = DefaultPermissions.registerPermission(PREFIX + "anvil", "Allows the user to use all anvil color and format abilities", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "anvil.color", "Allows the user to use color codes in an anvil", PermissionDefault.FALSE, anvil); ++ DefaultPermissions.registerPermission(PREFIX + "anvil.minimessage", "Allows the user to use minimessage tags in an anvil", PermissionDefault.FALSE, anvil); ++ DefaultPermissions.registerPermission(PREFIX + "anvil.remove_italics", "Allows the user to remove italics in an anvil", PermissionDefault.FALSE, anvil); ++ DefaultPermissions.registerPermission(PREFIX + "anvil.format", "Allows the user to use format codes in an anvil", PermissionDefault.FALSE, anvil); ++ anvil.recalculatePermissibles(); ++ ++ Permission book = DefaultPermissions.registerPermission(PREFIX + "book", "Allows the user to use color codes on books", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "book.color.edit", "Allows the user to use color codes on books when editing", PermissionDefault.FALSE, book); ++ DefaultPermissions.registerPermission(PREFIX + "book.color.sign", "Allows the user to use color codes on books when signing", PermissionDefault.FALSE, book); ++ book.recalculatePermissibles(); ++ ++ Permission sign = DefaultPermissions.registerPermission(PREFIX + "sign", "Allows the user to use all sign abilities", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "sign.edit", "Allows the user to click signs to open sign editor", PermissionDefault.TRUE, sign); ++ DefaultPermissions.registerPermission(PREFIX + "sign.color", "Allows the user to use color codes on signs", PermissionDefault.FALSE, sign); ++ DefaultPermissions.registerPermission(PREFIX + "sign.style", "Allows the user to use style codes on signs", PermissionDefault.FALSE, sign); ++ DefaultPermissions.registerPermission(PREFIX + "sign.magic", "Allows the user to use magic/obfuscate code on signs", PermissionDefault.FALSE, sign); ++ sign.recalculatePermissibles(); ++ ++ Permission ride = DefaultPermissions.registerPermission("allow.ride", "Allows the user to ride all mobs", PermissionDefault.FALSE, purpur); ++ for (String mob : mobs) { ++ DefaultPermissions.registerPermission("allow.ride." + mob, "Allows the user to ride " + mob, PermissionDefault.FALSE, ride); ++ } ++ ride.recalculatePermissibles(); ++ ++ Permission special = DefaultPermissions.registerPermission("allow.special", "Allows the user to use all mobs special abilities", PermissionDefault.FALSE, purpur); ++ for (String mob : mobs) { ++ DefaultPermissions.registerPermission("allow.special." + mob, "Allows the user to use " + mob + " special ability", PermissionDefault.FALSE, special); ++ } ++ special.recalculatePermissibles(); ++ ++ Permission powered = DefaultPermissions.registerPermission("allow.powered", "Allows the user to toggle all mobs powered state", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission("allow.powered.creeper", "Allows the user to toggle creeper powered state", PermissionDefault.FALSE, powered); ++ powered.recalculatePermissibles(); ++ ++ DefaultPermissions.registerPermission(PREFIX + "portal.instant", "Allows the user to bypass portal wait time", PermissionDefault.FALSE, purpur); ++ ++ purpur.recalculatePermissibles(); ++ return purpur; ++ } ++} diff --git a/patches/generated-api/0001-Purpur-generated-api-Changes.patch b/Leaf-API/paper-patches/features/0005-Purpur-generated-api-Changes.patch similarity index 55% rename from patches/generated-api/0001-Purpur-generated-api-Changes.patch rename to Leaf-API/paper-patches/features/0005-Purpur-generated-api-Changes.patch index 8693486e..918bb27c 100644 --- a/patches/generated-api/0001-Purpur-generated-api-Changes.patch +++ b/Leaf-API/paper-patches/features/0005-Purpur-generated-api-Changes.patch @@ -1,32 +1,40 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Github Actions -Date: Fri, 21 Jun 2024 03:34:00 +0000 +Date: Thu, 16 Jan 2025 11:21:11 +0000 Subject: [PATCH] Purpur generated-api Changes Original license: MIT Original project: https://github.com/PurpurMC/Purpur -Commit: 16ce24aa7eb08232030e4570e027f7baefa5f3f9 +Commit: dd4143984219cea8440913b7918322b5ba59265a -diff --git a/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java -index 35dfd25f21ca67b7f4d69326500980f4a021ef49..a9816fbfa466b3fe3f82c19aeeeb564c660e4b6a 100644 ---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java -+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java -@@ -441,6 +441,18 @@ public interface VanillaGoal extends Goal { +diff --git a/src/generated/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/generated/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +index f15a7b4471cd31a487467ec7ecf7a186fa887a51..f09fefe6821d8b2b8c8f055985bacc2e042ca569 100644 +--- a/src/generated/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java ++++ b/src/generated/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -441,6 +441,26 @@ public interface VanillaGoal extends Goal { GoalKey ZOMBIE_ATTACK_TURTLE_EGG = create("zombie_attack_turtle_egg", Zombie.class); -+ // Purpur start ++ // Purpur start - Ridables + GoalKey MOB_HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider")); + GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); + GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider")); ++ // Purpur end - Ridables ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms + GoalKey FIND_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal")); + GoalKey ORBIT_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal")); ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms ++ // Purpur start - Add option to disable zombie aggressiveness towards villagers when lagging + GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager")); + GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager")); ++ // Purpur end - Add option to disable zombie aggressiveness towards villagers when lagging ++ // Purpur start - Configurable chance for wolves to spawn rabid + GoalKey AVOID_RABID_WOLF = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolf")); ++ // Purpur end - Configurable chance for wolves to spawn rabid ++ // Purpur start - Iron golem poppy calms anger + GoalKey RECEIVE_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("receive_flower")); -+ // Purpur end ++ // Purpur end - Iron golem poppy calms anger + private static GoalKey create(final String key, final Class type) { return GoalKey.of(type, NamespacedKey.minecraft(key)); diff --git a/patches/api/0005-Remove-Timings.patch b/Leaf-API/paper-patches/features/0006-Remove-Timings.patch similarity index 99% rename from patches/api/0005-Remove-Timings.patch rename to Leaf-API/paper-patches/features/0006-Remove-Timings.patch index 364b3a49..5b65fc1a 100644 --- a/patches/api/0005-Remove-Timings.patch +++ b/Leaf-API/paper-patches/features/0006-Remove-Timings.patch @@ -2866,7 +2866,7 @@ index 45ed63797b13e114bf3795c80a6c3967d8eb2351..00000000000000000000000000000000 - } -} diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java -index 74384a56eebbce41d431db2507c55eddbcf50a41..7d1ac11cfffbaf7d799f2a0032221fb23ee6daf5 100644 +index 71eb845a4d3b8b6ec3b816a0f20ec807e0f9a86d..a43419c23aa0f6fd809caf5a841cb138f350b7ba 100644 --- a/src/main/java/org/bukkit/command/Command.java +++ b/src/main/java/org/bukkit/command/Command.java @@ -33,16 +33,6 @@ public abstract class Command { @@ -2910,7 +2910,7 @@ index abe256e1e45ce28036da4aa1586715bc8a1a3414..9eab8024e0675865f17669847759a26d return i >= j && i <= k; } diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java -index a09b5458191eb5df4787859b72a37fa1fa2bffba..6551a74b5c900d52f162c38c2b2ca94a8fc5c444 100644 +index 32c81559507a8e5085c91d466cda69d0dc11327e..7e9f1237f197094318fc41df2e2fa58fbc81e528 100644 --- a/src/main/java/org/bukkit/command/SimpleCommandMap.java +++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -39,7 +39,6 @@ public class SimpleCommandMap implements CommandMap { @@ -2931,7 +2931,7 @@ index a09b5458191eb5df4787859b72a37fa1fa2bffba..6551a74b5c900d52f162c38c2b2ca94a boolean registered = register(label, command, false, fallbackPrefix); @@ -166,23 +164,13 @@ public class SimpleCommandMap implements CommandMap { parsedArgs = event.getArgs(); - // Purpur end + // Purpur end - ExecuteCommandEvent - // Paper start - Plugins do weird things to workaround normal registration - if (target.timings == null) { @@ -2942,7 +2942,7 @@ index a09b5458191eb5df4787859b72a37fa1fa2bffba..6551a74b5c900d52f162c38c2b2ca94a try { - try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) - target.execute(sender, sentCommandLabel, parsedArgs); // Purpur + target.execute(sender, sentCommandLabel, parsedArgs); // Purpur - ExecuteCommandEvent - } // target.timings.stopTiming(); // Spigot // Paper } catch (CommandException ex) { server.getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper @@ -3047,7 +3047,7 @@ index 2fae50a9d1f0d9ecd91036697dedd64bc56f7d3b..2daf3d072e4441778ca566387d6a80d6 } diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java deleted file mode 100644 -index 12946bd55fcf7c40d39081779a7fa30049ee6165..0000000000000000000000000000000000000000 +index 5fbacfcf108432c5187aa9a4092d00d7d5b0fd53..0000000000000000000000000000000000000000 --- a/src/main/java/org/spigotmc/CustomTimingsHandler.java +++ /dev/null @@ -1,67 +0,0 @@ @@ -3101,7 +3101,7 @@ index 12946bd55fcf7c40d39081779a7fa30049ee6165..00000000000000000000000000000000 - public CustomTimingsHandler(@NotNull String name) { - Timing timing; - -- new AuthorNagException("Deprecated use of CustomTimingsHandler. Please Switch to Timings.of ASAP").printStackTrace(); +- new AuthorNagException("Deprecated use of CustomTimingsHandler. Timings has been removed.").printStackTrace(); - try { - final Method ofSafe = TimingsManager.class.getDeclaredMethod("getHandler", String.class, String.class, Timing.class); - ofSafe.setAccessible(true); @@ -3119,10 +3119,10 @@ index 12946bd55fcf7c40d39081779a7fa30049ee6165..00000000000000000000000000000000 - -} diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java -index 4028b230e7fe1c78520f227a377a2a61e8381ecc..0f3683841bf853752f802dd1b05a081a49681992 100644 +index 37feafd626aaa17aba888d7ff13728b3c6f26d4d..e42619418c1a3e3dac22e7310bb9d64b42b9f6a7 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java +++ b/src/test/java/org/bukkit/AnnotationTest.java -@@ -54,15 +54,6 @@ public class AnnotationTest { +@@ -50,15 +50,6 @@ public class AnnotationTest { // Paper start "io/papermc/paper/util/TransformingRandomAccessList", "io/papermc/paper/util/TransformingRandomAccessList$TransformedListIterator", diff --git a/patches/api/0007-KeYi-Player-Skull-API.patch b/Leaf-API/paper-patches/features/0007-KeYi-Player-Skull-API.patch similarity index 87% rename from patches/api/0007-KeYi-Player-Skull-API.patch rename to Leaf-API/paper-patches/features/0007-KeYi-Player-Skull-API.patch index 23b4bb67..807d30af 100644 --- a/patches/api/0007-KeYi-Player-Skull-API.patch +++ b/Leaf-API/paper-patches/features/0007-KeYi-Player-Skull-API.patch @@ -7,10 +7,10 @@ Original license: MIT Original project: https://github.com/KeYiMC/KeYi diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 347d6cbee7daba824adf798c1ec9895ae1da67ab..d5e4d34ca02567105ad92f7a1d7e3cc0f579320c 100644 +index 886a954b1a12e895f5d54fd35a9acb36673e3733..02cf7995d15487a4958ba5a5a19018d24d707b80 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -4040,4 +4040,23 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -4021,4 +4021,23 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM sendDeathScreen(message); } // Purpur end diff --git a/patches/api/0008-Slice-Smooth-Teleports.patch b/Leaf-API/paper-patches/features/0008-Slice-Smooth-Teleports.patch similarity index 93% rename from patches/api/0008-Slice-Smooth-Teleports.patch rename to Leaf-API/paper-patches/features/0008-Slice-Smooth-Teleports.patch index bd0c82d0..ba6a9b91 100644 --- a/patches/api/0008-Slice-Smooth-Teleports.patch +++ b/Leaf-API/paper-patches/features/0008-Slice-Smooth-Teleports.patch @@ -9,10 +9,10 @@ Original project: https://github.com/Cryptite/Slice Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index d5e4d34ca02567105ad92f7a1d7e3cc0f579320c..acb8975fc2598e69c21be8d97a42a61c3944649d 100644 +index 02cf7995d15487a4958ba5a5a19018d24d707b80..31fcd0512b8b9ad2a36d8338c581972a450cda7c 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3731,6 +3731,33 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3732,6 +3732,33 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM String getClientBrandName(); // Paper end diff --git a/patches/api/0009-Configurable-LibraryLoader-maven-repos.patch b/Leaf-API/paper-patches/features/0009-Configurable-LibraryLoader-maven-repos.patch similarity index 100% rename from patches/api/0009-Configurable-LibraryLoader-maven-repos.patch rename to Leaf-API/paper-patches/features/0009-Configurable-LibraryLoader-maven-repos.patch diff --git a/patches/api/0010-Leaves-Replay-Mod-API.patch b/Leaf-API/paper-patches/features/0010-Leaves-Replay-Mod-API.patch similarity index 88% rename from patches/api/0010-Leaves-Replay-Mod-API.patch rename to Leaf-API/paper-patches/features/0010-Leaves-Replay-Mod-API.patch index b88e31b7..7f74d5a5 100644 --- a/patches/api/0010-Leaves-Replay-Mod-API.patch +++ b/Leaf-API/paper-patches/features/0010-Leaves-Replay-Mod-API.patch @@ -11,13 +11,21 @@ Original project: https://github.com/LeavesMC/Leaves This patch is Powered by ReplayMod(https://github.com/ReplayMod) diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 2477a560ed345116cd9d4e90f98694b84a1faf28..17d3f7839de56821d51a3008c73e78f1bedc98ad 100644 +index 196109f427fb1169785b82b4cfb52c53329d2bea..3e999a4805fbf733dc1530e7dbf23aa10642cb39 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -3158,4 +3158,10 @@ public final class Bukkit { +@@ -3048,6 +3048,7 @@ public final class Bukkit { + return server.spigot(); + } + ++ + // Purpur start - Bring back server name + /** + * Get the name of this server +@@ -3176,4 +3177,10 @@ public final class Bukkit { server.clearBlockHighlights(); } - // Purpur end + // Purpur end - Debug Marker API + + // Leaves start - Photographer API + public static @NotNull org.leavesmc.leaves.entity.PhotographerManager getPhotographerManager() { @@ -26,10 +34,10 @@ index 2477a560ed345116cd9d4e90f98694b84a1faf28..17d3f7839de56821d51a3008c73e78f1 + // Leaves end - Photographer API } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 582c1f053020dff2cde8a1a7a1a760a84e856b7c..b3f3e70382f0c1e497d71c4490ee06740d7fa27a 100644 +index 335db5f0893df51c65c5aab7f11fd6aef59a191d..78ddb4f8e90d797920ed7664055ba81d3c7cde41 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -62,6 +62,7 @@ import org.jetbrains.annotations.ApiStatus; +@@ -66,6 +66,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,10 +45,10 @@ index 582c1f053020dff2cde8a1a7a1a760a84e856b7c..b3f3e70382f0c1e497d71c4490ee0674 /** * Represents a server implementation. -@@ -2806,4 +2807,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2859,4 +2860,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ void clearBlockHighlights(); - // Purpur end + // Purpur end - Debug Marker API + + // Leaves start - Photographer API + @NotNull PhotographerManager getPhotographerManager(); diff --git a/Leaf-Server/build.gradle.kts.patch b/Leaf-Server/build.gradle.kts.patch new file mode 100644 index 00000000..a82cf5b3 --- /dev/null +++ b/Leaf-Server/build.gradle.kts.patch @@ -0,0 +1,189 @@ +--- a/gale-server/build.gradle.kts ++++ b/gale-server/build.gradle.kts +@@ -9,10 +_,11 @@ + } + + val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/" ++val leafMavenPublicUrl = "https://maven.nostal.ink/repository/maven-snapshots/" // Leaf - project setup - Add publish repo + + dependencies { + mache("io.papermc:mache:1.21.4+build.7") +- paperclip("io.papermc:paperclip:3.0.3") ++ paperclip("cn.dreeam:quantumleaper:1.0.0-SNAPSHOT") // Leaf - project setup - Use own paperclip fork + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + } + +@@ -31,7 +_,30 @@ + } + } + +- activeFork = gale ++ // Leaf start - project setup ++ val leaf = forks.register("leaf") { ++ forks = gale ++ upstream.patchRepo("paperServer") { ++ upstreamRepo = gale.patchedRepo("paperServer") ++ patchesDir = rootDirectory.dir("leaf-server/paper-patches") ++ outputDir = rootDirectory.dir("paper-server") ++ } ++ upstream.patchDir("galeServer") { ++ upstreamPath = "gale-server" ++ excludes = setOf( ++ "src/minecraft", ++ "paper-patches", ++ "minecraft-patches", ++ "build.gradle.kts", ++ "build.gradle.kts.patch" ++ ) ++ patchesDir = rootDirectory.dir("leaf-server/gale-patches") ++ outputDir = rootDirectory.dir("gale-server") ++ } ++ } ++ ++ activeFork = leaf ++ // Leaf end - project setup + // Gale end - project setup + + spigot { +@@ -56,6 +_,7 @@ + libraryRepositories.addAll( + "https://repo.maven.apache.org/maven2/", + paperMavenPublicUrl, ++ leafMavenPublicUrl // Leaf - project setup - Add publish repo + ) + } + +@@ -119,10 +_,14 @@ + main { + java { srcDir("../paper-server/src/main/java") } + resources { srcDir("../paper-server/src/main/resources") } ++ java { srcDir("../gale-server/src/main/java") } // Leaf - project setup ++ resources { srcDir("../gale-server/src/main/resources") } // Leaf - project setup + } + test { + java { srcDir("../paper-server/src/test/java") } + resources { srcDir("../paper-server/src/test/resources") } ++ java { srcDir("../gale-server/src/test/java") } // Leaf - project setup ++ resources { srcDir("../gale-server/src/test/resources") } // Leaf - project setup + } + } + +@@ -147,10 +_,19 @@ + } + + dependencies { +- implementation(project(":gale-api")) // Gale - project setup - Depend on own API ++ implementation(project(":leaf-api")) // Gale - project setup - Depend on own API // Leaf - project setup ++ ++ // Leaf start - Libraries ++ implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.2") { // Leaf Config ++ exclude(group = "org.yaml", module = "snakeyaml") ++ } ++ implementation("com.github.luben:zstd-jni:1.5.6-9") // LinearPaper ++ implementation("org.lz4:lz4-java:1.8.0") // LinearPaper ++ implementation("com.github.ben-manes.caffeine:caffeine:3.1.8") ++ // Leaf end - Libraries + implementation("ca.spottedleaf:concurrentutil:0.0.3") +- implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ +- implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 ++ implementation("org.jline:jline-terminal-ffm:3.28.0") // use ffm on java 22+ // Leaf - Bump Dependencies ++ implementation("org.jline:jline-terminal-jni:3.28.0") // fall back to jni on java 21 // Leaf - Bump Dependencies + implementation("net.minecrell:terminalconsoleappender:1.3.0") + implementation("net.kyori:adventure-text-serializer-ansi:4.18.0") // Keep in sync with adventureVersion from Paper-API build file + +@@ -160,35 +_,47 @@ + 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.19.0") +- log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Needed to generate meta for our Log4j plugins ++ // Leaf start - Bump Dependencies ++ implementation("org.apache.logging.log4j:log4j-core:2.24.3") ++ log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.24.3") // Needed to generate meta for our Log4j plugins + runtimeOnly(log4jPlugins.output) + alsoShade(log4jPlugins.output) + +- implementation("com.velocitypowered:velocity-native:3.3.0-SNAPSHOT") { ++ implementation("com.velocitypowered:velocity-native:3.4.0-SNAPSHOT") { + isTransitive = false + } +- implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Add support for proxy protocol +- implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1") ++ implementation("io.netty:netty-codec-haproxy:4.1.116.Final") // Add support for proxy protocol ++ implementation("org.apache.logging.log4j:log4j-iostreams:2.24.3") ++ // Leaf end - Bump Dependencies + implementation("org.ow2.asm:asm-commons:9.7.1") + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") + implementation("commons-lang:commons-lang:2.6") +- runtimeOnly("org.xerial:sqlite-jdbc:3.47.0.0") ++ runtimeOnly("org.xerial:sqlite-jdbc:3.47.2.0") // Leaf - Bump Dependencies + runtimeOnly("com.mysql:mysql-connector-j:9.1.0") +- runtimeOnly("com.lmax:disruptor:3.4.4") +- +- runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") +- runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") +- runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") +- +- testImplementation("io.github.classgraph:classgraph:4.8.47") // For mob goal test +- testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") +- testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") +- testImplementation("org.hamcrest:hamcrest:2.2") +- testImplementation("org.mockito:mockito-core:5.14.1") +- mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Configure mockito agent that is needed in newer java versions ++ runtimeOnly("com.lmax:disruptor:3.4.4") // Dreeam TODO - Waiting Log4j 3.x to support disruptor 4.0.0 ++ ++ // Leaf start - Bump Dependencies ++ runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.9") ++ runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.22") // Dreeam TODO - Update to 2.0.1 ++ runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.22") // Dreeam TODO - Update to 2.0.1 ++ // Leaf end - Bump Dependencies ++ ++ // Purpur start ++ implementation("org.mozilla:rhino-runtime:1.7.15") ++ implementation("org.mozilla:rhino-engine:1.7.15") ++ implementation("dev.omega24:upnp4j:1.0") ++ // Purpur end ++ ++ // Leaf start - Bump Dependencies ++ testImplementation("io.github.classgraph:classgraph:4.8.179") // For mob goal test ++ testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") ++ testImplementation("org.junit.platform:junit-platform-suite-engine:1.11.4") ++ testImplementation("org.hamcrest:hamcrest:3.0") ++ testImplementation("org.mockito:mockito-core:5.15.2") ++ mockitoAgent("org.mockito:mockito-core:5.15.2") { isTransitive = false } // Configure mockito agent that is needed in newer java versions + testImplementation("org.ow2.asm:asm-tree:9.7.1") +- testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // CartesianTest ++ testImplementation("org.junit-pioneer:junit-pioneer:2.3.0") // CartesianTest ++ // Leaf end - Bump Dependencies + + implementation("net.neoforged:srgutils:1.0.9") // Mappings handling + implementation("net.neoforged:AutoRenamingTool:2.0.3") // Remap plugins +@@ -202,6 +_,8 @@ + // Spark + implementation("me.lucko:spark-api:0.1-20240720.200737-2") + implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") ++ ++ implementation("io.netty:netty-all:4.1.116.Final") // Leaf - Bump Dependencies // Dreeam TODO - Update to 4.2.0 + } + + // Gale start - hide irrelevant compilation warnings +@@ -226,14 +_,14 @@ + val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim() + attributes( + "Main-Class" to "org.bukkit.craftbukkit.Main", +- "Implementation-Title" to "Gale", // Gale - branding changes ++ "Implementation-Title" to "Leaf", // Gale - branding changes // Leaf - Rebrand + "Implementation-Version" to implementationVersion, + "Implementation-Vendor" to date, +- "Specification-Title" to "Gale", // Gale - branding changes ++ "Specification-Title" to "Leaf", // Gale - branding changes // Leaf - Rebrand + "Specification-Version" to project.version, +- "Specification-Vendor" to "GaleMC Team", // Gale - branding changes +- "Brand-Id" to "galemc:gale", // Gale - branding changes +- "Brand-Name" to "Gale", // Gale - branding changes ++ "Specification-Vendor" to "Winds Studio", // Gale - branding changes // Leaf - Rebrand ++ "Brand-Id" to "winds-studio:leaf", // Gale - branding changes // Leaf - Rebrand ++ "Brand-Name" to "Leaf", // Gale - branding changes // Leaf - Rebrand + "Build-Number" to (build ?: ""), + "Build-Time" to buildTime.toString(), + "Git-Branch" to gitBranch, diff --git a/Leaf-Server/gale-patches/features/0001-Fix-Pufferfish-and-Purpur-patches.patch b/Leaf-Server/gale-patches/features/0001-Fix-Pufferfish-and-Purpur-patches.patch new file mode 100644 index 00000000..a0bbeb3a --- /dev/null +++ b/Leaf-Server/gale-patches/features/0001-Fix-Pufferfish-and-Purpur-patches.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 29 Apr 2024 14:18:58 -0400 +Subject: [PATCH] Fix Pufferfish and Purpur patches + + +diff --git a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java +index fab5d5af9ec6a20810ce5e437dd617684cc5768f..d0a031014ec410142d59c8edd577bf035b7e407b 100644 +--- a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java ++++ b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java +@@ -101,10 +101,10 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { + // Gale end - branding changes - version fetcher + + return switch (distance) { +- case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW); +- case 0 -> text("You are running the latest version", NamedTextColor.GREEN); +- case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW); +- default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) ++ case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur ++ case 0 -> text("* You are running the latest version", NamedTextColor.GREEN); // Purpur ++ case DISTANCE_UNKNOWN -> text("* Unknown version", NamedTextColor.YELLOW); // Purpur ++ default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur + .append(Component.newline()) + .append(text("Download the new version at: ") + .append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher +@@ -149,6 +149,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { + return null; + } + +- return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); ++ return text("Previous: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); // Purpur + } + } diff --git a/patches/server/0020-KeYi-Disable-arrow-despawn-counter-by-default.patch b/Leaf-Server/gale-patches/features/0002-KeYi-Disable-arrow-despawn-counter-by-default.patch similarity index 89% rename from patches/server/0020-KeYi-Disable-arrow-despawn-counter-by-default.patch rename to Leaf-Server/gale-patches/features/0002-KeYi-Disable-arrow-despawn-counter-by-default.patch index ad02cf9c..3012a6fa 100644 --- a/patches/server/0020-KeYi-Disable-arrow-despawn-counter-by-default.patch +++ b/Leaf-Server/gale-patches/features/0002-KeYi-Disable-arrow-despawn-counter-by-default.patch @@ -7,10 +7,10 @@ Original license: MIT Original project: https://github.com/KeYiMC/KeYi diff --git a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -index 5b9e55bc9c7a67487400f59ba6bfaa9f68c6504a..838d15a8c81f168b6d94adb602a996123313aaea 100644 +index a12b0fa0df84fc44861cebbfbdc4a06a990accce..37896174667b884fcdc83b0a613686d76b66f247 100644 --- a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java +++ b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -@@ -126,7 +126,7 @@ public class GaleWorldConfiguration extends ConfigurationPart { +@@ -120,7 +120,7 @@ public class GaleWorldConfiguration extends ConfigurationPart { } diff --git a/Leaf-Server/gale-patches/features/0003-Reduce-items-finding-hopper-nearby-check.patch b/Leaf-Server/gale-patches/features/0003-Reduce-items-finding-hopper-nearby-check.patch new file mode 100644 index 00000000..2bd4f609 --- /dev/null +++ b/Leaf-Server/gale-patches/features/0003-Reduce-items-finding-hopper-nearby-check.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 15 Jan 2024 10:53:10 -0500 +Subject: [PATCH] Reduce items finding hopper nearby check + +This patch add a toggle for items checking MinecraftHopper nearby, + +But still recommend to turn-off `checkForMinecartNearItemWhileActive` +Since `Reduce-hopper-item-checks.patch` will cause lag under massive dropped items + +diff --git a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java +index 37896174667b884fcdc83b0a613686d76b66f247..c449e8cbe66a751800072f4b77186dc1ee09865e 100644 +--- a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java ++++ b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java +@@ -78,6 +78,7 @@ public class GaleWorldConfiguration extends ConfigurationPart { + public int duration = 100; + public int nearbyItemMaxAge = 1200; + public int checkForMinecartNearItemInterval = 20; ++ public boolean checkForMinecartNearItemWhileActive = false; // Leaf - Make it configurable and reorder code + public boolean checkForMinecartNearItemWhileInactive = true; + public double maxItemHorizontalDistance = 24.0; + public double maxItemVerticalDistance = 4.0; diff --git a/Leaf-Server/minecraft-patches/features/0001-Rebrand.patch b/Leaf-Server/minecraft-patches/features/0001-Rebrand.patch new file mode 100644 index 00000000..1659cd08 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0001-Rebrand.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Encode42 +Date: Thu, 16 Sep 2021 20:39:45 -0400 +Subject: [PATCH] Rebrand + + +diff --git a/net/minecraft/CrashReport.java b/net/minecraft/CrashReport.java +index 3e0e88afcf010d9a3d46e48bca5cbdf98fe97544..60afaa49e54ff1a52bc52b99ad160682a68dcd97 100644 +--- a/net/minecraft/CrashReport.java ++++ b/net/minecraft/CrashReport.java +@@ -30,6 +30,7 @@ public class CrashReport { + private boolean trackingStackTrace = true; + private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; + private final SystemReport systemReport = new SystemReport(); ++ private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER OR GALE! REPORT TO LEAF INSTEAD!", ""); // Leaf - Purpur + + public CrashReport(String title, Throwable exception) { + io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper +@@ -130,7 +131,7 @@ public class CrashReport { + } + + public String getFriendlyReport(ReportType type) { +- return this.getFriendlyReport(type, List.of()); ++ return this.getFriendlyReport(type, extraInfo); // Leaf - Purpur + } + + @Nullable +@@ -161,7 +162,7 @@ public class CrashReport { + } + + public boolean saveToFile(Path path, ReportType type) { +- return this.saveToFile(path, type, List.of()); ++ return this.saveToFile(path, type, extraInfo); // Leaf - Purpur + } + + public SystemReport getSystemReport() { +diff --git a/net/minecraft/world/damagesource/DamageSource.java b/net/minecraft/world/damagesource/DamageSource.java +index aea139e47a0866c63c0fc6728840e5ad92a26403..3d8d7460ab31e9183e26ada76ad05378f8bb925d 100644 +--- a/net/minecraft/world/damagesource/DamageSource.java ++++ b/net/minecraft/world/damagesource/DamageSource.java +@@ -66,7 +66,7 @@ public class DamageSource { + + public DamageSource customEventDamager(Entity entity) { + if (this.directEntity != null) { +- throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper, if you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues)"); // Gale - branding changes ++ throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper, if you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues)"); // Gale - branding changes // Leaf + } + DamageSource damageSource = this.cloneInstance(); + damageSource.customEventDamager = entity; +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 3abb8aefcca325e28f0af07bd859be74f0aee08d..7fff86a4956f59b2f4a9f7e283256879c034c1b8 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -288,7 +288,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + We do not want people to report thread issues to Paper, + but we do want people to report thread issues to Gale. + */ +- org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER OR GALE - You may ask for help on Discord, but do not file an issue. These error messages can not be removed. - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues)"); ++ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER OR LEAF - You may ask for help on Discord, but do not file an issue. These error messages can not be removed. - If you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues)"); // Leaf + // Gale end - branding changes + } + diff --git a/Leaf-Server/minecraft-patches/features/0002-Leaf-Config.patch b/Leaf-Server/minecraft-patches/features/0002-Leaf-Config.patch new file mode 100644 index 00000000..c0b7b02e --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0002-Leaf-Config.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 12 Oct 2022 10:42:15 -0400 +Subject: [PATCH] Leaf Config + +Leaf Config v3 +including load config, backup old or outdated config, and add config to spark profiler automatically. + +TODO - Dreeam: +Add per world config +Add config reload + +diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java +index 9aa664537cc37e44db46d5a2a64ae3116938c681..841546cef2a98427ae78ca2a07693391eb0d3035 100644 +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -112,6 +112,7 @@ public class Main { + Bootstrap.bootStrap(); + Bootstrap.validate(); + Util.startTimerHackThread(); ++ org.dreeam.leaf.config.LeafConfig.loadConfig(); // Leaf + Path path1 = Paths.get("server.properties"); + DedicatedServerSettings dedicatedServerSettings = new DedicatedServerSettings(optionSet); // CraftBukkit - CLI argument support + dedicatedServerSettings.forceSave(); +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 0da1eeab6a2f8336283a62ebcb3aa0d1b1933c85..8794ae06ac574b399cc2dbb3fa61b6ef51d8062d 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1175,6 +1175,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 10 Nov 2021 00:37:03 -0500 +Subject: [PATCH] Pufferfish: Optimize mob spawning + +Original license: GPL v3 +Original project: https://github.com/pufferfish-gg/Pufferfish + +Co-authored-by: booky10 + +This patch aims to reduce the main-thread impact of mob spawning by +offloading as much work as possible to other threads. It is possible for +inconsistencies to come up, but when they happen they never interfere +with the server's operation (they don't produce errors), and side +effects are limited to more or less mobs being spawned in any particular +tick. + +It is possible to disable this optimization if it is not required or if +it interferes with any plugins. On servers with thousands of entities, +this can result in performance gains of up to 15%, which is significant +and, in my opinion, worth the low risk of minor mob-spawning-related +inconsistencies. + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 8794ae06ac574b399cc2dbb3fa61b6ef51d8062d..5f0fa04a4ed2b0073240edbf39aeed9007911e63 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -287,6 +287,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping + public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation ++ public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index f2e24d52240a84ff7ca69ad2c8ec0d1c197467c0..30dc45b2201bf7435d7f38866dfa4b5fbbf44957 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -362,6 +362,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + LOGGER.info("JMX monitoring enabled"); + } + ++ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish ++ + return true; + } + } +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index b6127fff62c49809cfa54e39a35c89f45f46c66c..fde7de56f46502a1cc268318684e67ac2fbd5157 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -179,6 +179,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + // Paper end - chunk tick iteration optimisations + ++ public boolean firstRunSpawnCounts = true; // Pufferfish ++ public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs + + public ServerChunkCache( + ServerLevel level, +@@ -513,6 +515,43 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + + this.broadcastChangedChunks(); // Gale - Purpur - remove vanilla profiler + } ++ ++ // Pufferfish start - optimize mob spawning ++ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { ++ for (ServerPlayer player : this.level.players) { ++ // Paper start - per player mob spawning backoff ++ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { ++ player.mobCounts[ii] = 0; ++ ++ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? ++ if (newBackoff < 0) { ++ newBackoff = 0; ++ } ++ player.mobBackoffCounts[ii] = newBackoff; ++ } ++ // Paper end - per player mob spawning backoff ++ } ++ if (firstRunSpawnCounts) { ++ firstRunSpawnCounts = false; ++ _pufferfish_spawnCountsReady.set(true); ++ } ++ if (_pufferfish_spawnCountsReady.getAndSet(false)) { ++ net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> { ++ int mapped = distanceManager.getNaturalSpawnChunkCount(); ++ ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator objectiterator = ++ level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); ++ try { ++ gg.pufferfish.pufferfish.util.IterableWrapper wrappedIterator = ++ new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator); ++ lastSpawnState = NaturalSpawner.createState(mapped, wrappedIterator, this::getFullChunk, null, true); ++ } finally { ++ objectiterator.finishedIterating(); ++ } ++ _pufferfish_spawnCountsReady.set(true); ++ }); ++ } ++ } ++ // Pufferfish end + } + + private void broadcastChangedChunks() { // Gale - Purpur - remove vanilla profiler +@@ -559,6 +598,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount(); + // Paper start - Optional per player mob spawns + if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled ++ if (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { // Pufferfish - moved down when async processing + // re-set mob counts + for (ServerPlayer player : this.level.players) { + // Paper start - per player mob spawning backoff +@@ -573,12 +613,16 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + // Paper end - per player mob spawning backoff + } +- spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); ++ lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); // Pufferfish - async mob spawning ++ } // Pufferfish - (endif) moved down when async processing + } else { +- spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); ++ // Pufferfish start - async mob spawning ++ lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); ++ _pufferfish_spawnCountsReady.set(true); ++ // Pufferfish end + } + // Paper end - Optional per player mob spawns +- this.lastSpawnState = spawnState; ++ //this.lastSpawnState = spawnState; // Pufferfish - this is managed asynchronously + // Gale start - MultiPaper - skip unnecessary mob spawning computations + } else { + spawnState = null; +@@ -596,7 +640,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + // Paper end - PlayerNaturallySpawnCreaturesEvent + boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit +- filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit ++ filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit // Pufferfish + } else { + filteredSpawningCategories = List.of(); + } +@@ -604,8 +648,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + for (LevelChunk levelChunk : chunks) { + ChunkPos pos = levelChunk.getPos(); + levelChunk.incrementInhabitedTime(timeInhabited); +- if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot +- NaturalSpawner.spawnForChunk(this.level, levelChunk, spawnState, filteredSpawningCategories); ++ if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot // Pufferfish ++ NaturalSpawner.spawnForChunk(this.level, levelChunk, lastSpawnState, filteredSpawningCategories); // Pufferfish + } + + if (true) { // Paper - rewrite chunk system +diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java +index 423779a2b690f387a4f0bd07b97b50e0baefda76..dec51066fc3f57b7bdc56195313c219f45a7fbee 100644 +--- a/net/minecraft/world/level/entity/EntityTickList.java ++++ b/net/minecraft/world/level/entity/EntityTickList.java +@@ -9,7 +9,7 @@ import javax.annotation.Nullable; + import net.minecraft.world.entity.Entity; + + public class EntityTickList { +- private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system ++ public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public + + private void ensureActiveIsNotIterated() { + // Paper - rewrite chunk system diff --git a/Leaf-Server/minecraft-patches/features/0004-Pufferfish-Dynamic-Activation-of-Brain.patch b/Leaf-Server/minecraft-patches/features/0004-Pufferfish-Dynamic-Activation-of-Brain.patch new file mode 100644 index 00000000..d4d545f3 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0004-Pufferfish-Dynamic-Activation-of-Brain.patch @@ -0,0 +1,334 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Fri, 15 Jan 2021 19:05:01 -0600 +Subject: [PATCH] Pufferfish: Dynamic Activation of Brain + +Dreeam TODO: waiting Paper dealing with the newGoalRate + +Original license: GPL v3 +Original project: https://github.com/pufferfish-gg/Pufferfish + +This replaces the current method of ticking an inactive entity's +pathfinder 1/4 times with a new method that's dynamic based off how far +away it is from a player. If an entity is within 32 blocks, it gets +ticked every tick. If it's within 45 blocks, it gets ticked every other +tick. If it's within 55 blocks, it gets ticked once every three ticks. +(these numbers have since been changed, but the idea is the same.) + +Airplane +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java +index a04302728819f28ab724d474a3d06a916f3d1d99..47463babd445763c861ac5c4713b5bd3be5cbeec 100644 +--- a/io/papermc/paper/entity/activation/ActivationRange.java ++++ b/io/papermc/paper/entity/activation/ActivationRange.java +@@ -171,6 +171,22 @@ public final class ActivationRange { + } + + ActivationRange.activateEntity(entity); ++ ++ // Pufferfish start ++ if (org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.enabled && entity.getType().dabEnabled && ++ (!org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.dontEnableIfInWater || entity.getType().is(net.minecraft.tags.EntityTypeTags.CAN_BREATHE_UNDER_WATER) || !entity.isInWaterOrBubble())) { // Leaf - Option for dontEnableIfInWater ++ if (!entity.activatedPriorityReset) { ++ entity.activatedPriorityReset = true; ++ entity.activatedPriority = org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.maximumActivationPrio; ++ } ++ int squaredDistance = (int) player.distanceToSqr(entity); ++ entity.activatedPriority = squaredDistance > org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.startDistanceSquared ? ++ Math.max(1, Math.min(squaredDistance >> org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.activationDistanceMod, entity.activatedPriority)) : ++ 1; ++ } else { ++ entity.activatedPriority = 1; ++ } ++ // Pufferfish end + } + } + } +@@ -182,11 +198,11 @@ public final class ActivationRange { + */ + private static void activateEntity(final Entity entity) { + if (MinecraftServer.currentTick > entity.activatedTick) { +- if (entity.defaultActivationState) { ++ if (entity.defaultActivationState) { // Pufferfish - diff on change + entity.activatedTick = MinecraftServer.currentTick; + return; + } +- if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) { ++ if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) { // Pufferfish - diff on change + entity.activatedTick = MinecraftServer.currentTick; + } + } +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index ba0b5961535f0dbffda5216a6e4657ea85f30eb7..e52d976f0c1c5eacdc8608b204c3a178b9b17446 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -781,6 +781,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.entityTickList + .forEach( + entity -> { ++ entity.activatedPriorityReset = false; // Pufferfish - DAB + if (!entity.isRemoved()) { + if (!tickRateManager.isEntityFrozen(entity)) { + entity.checkDespawn(); +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 8470698181040b5d164a353aeb1dfa907765b80e..36f5c28fa2fb3ba2cb5a4b2614c6b8d934659892 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -335,6 +335,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public boolean freezeLocked = false; // Paper - Freeze Tick Lock API + public boolean fixedPose = false; // Paper - Expand Pose API + private final int despawnTime; // Paper - entity despawn time limit ++ public boolean activatedPriorityReset = false; // Pufferfish - DAB ++ public int activatedPriority = org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.maximumActivationPrio; // Pufferfish - DAB (golf score) + public final io.papermc.paper.entity.activation.ActivationType activationType = io.papermc.paper.entity.activation.ActivationType.activationTypeFor(this); // Paper - EAR 2/tracking ranges + // Paper start - EAR 2 + public final boolean defaultActivationState; +diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java +index 13cfe61fe500e6798b9129c7c559a7af65378a22..303bd2d3ea5c313477c8ab48359a01f230327447 100644 +--- a/net/minecraft/world/entity/EntityType.java ++++ b/net/minecraft/world/entity/EntityType.java +@@ -1063,6 +1063,7 @@ public class EntityType implements FeatureElement, EntityTypeT + private final boolean canSpawnFarFromPlayer; + private final int clientTrackingRange; + private final int updateInterval; ++ public boolean dabEnabled = false; // Pufferfish + private final String descriptionId; + @Nullable + private Component description; +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index f3eec8b75fcda47e6632a2a7db9a238b515bc8a0..cc452ca41c336891473fae98b8681768c52f822d 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -217,10 +217,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + @Override + public void inactiveTick() { + super.inactiveTick(); +- if (this.goalSelector.inactiveTick()) { ++ if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity + this.goalSelector.tick(); + } +- if (this.targetSelector.inactiveTick()) { ++ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority + this.targetSelector.tick(); + } + } +@@ -847,10 +847,14 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + this.sensing.tick(); + int i = this.tickCount + this.getId(); + if (i % 2 != 0 && this.tickCount > 1) { ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tickRunningGoals(false); ++ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tickRunningGoals(false); + } else { ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tick(); ++ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tick(); + } + +diff --git a/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java b/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java +index f6c673b1abe53afcb14fd68d590431027ed29f67..21deb221b87ecb70c8a0dc963ab79124b26ac930 100644 +--- a/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java ++++ b/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java +@@ -36,7 +36,11 @@ public class VillagerPanicTrigger extends Behavior { + + @Override + protected void tick(ServerLevel level, Villager owner, long gameTime) { +- if (gameTime % 100L == 0L) { ++ // Pufferfish start ++ if (owner.nextGolemPanic < 0) owner.nextGolemPanic = gameTime + 100; ++ if (--owner.nextGolemPanic < gameTime) { ++ owner.nextGolemPanic = -1; ++ // Pufferfish end + owner.spawnGolemIfNeeded(level, gameTime, 3); + } + } +diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java +index b816b2de8eb327060ca6ea7c4afc17373fa77ff6..e82e32407cec6109b9c3b0106295217f4a3f4aa2 100644 +--- a/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -36,9 +36,13 @@ public class GoalSelector { + } + + // Paper start - EAR 2 +- public boolean inactiveTick() { ++ public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start ++ if (inactive && !org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.enabled) tickRate = 4; // reset to Paper's ++ tickRate = Math.min(tickRate, 3); // Dreeam TODO - Waiting Paper + this.curRate++; +- return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct ++ //return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct ++ return this.curRate % tickRate == 0; ++ // Pufferfish end + } + + public boolean hasTasks() { +diff --git a/net/minecraft/world/entity/animal/allay/Allay.java b/net/minecraft/world/entity/animal/allay/Allay.java +index 0c863f8b4683516916d51a0c49921c6bb5608e9f..8f1d66d005413fe4eadb993b61568fa84336345a 100644 +--- a/net/minecraft/world/entity/animal/allay/Allay.java ++++ b/net/minecraft/world/entity/animal/allay/Allay.java +@@ -241,8 +241,10 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS + return 0.4F; + } + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + AllayAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/net/minecraft/world/entity/animal/axolotl/Axolotl.java +index c351b0808422221b5358d6e546a206ef75e8173f..4fb36e2a6d71b79219e10f5089eb0daebf830ee7 100644 +--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java ++++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java +@@ -298,8 +298,10 @@ public class Axolotl extends Animal implements VariantHolder, B + return true; + } + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + AxolotlAi.updateActivity(this); + if (!this.isNoAi()) { +diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java +index 67df4c0f47b2809c912f1dfb52124ca5e2c30b7b..10a0779bf8611ade19e64031bb00beb277e98598 100644 +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java +@@ -182,8 +182,10 @@ public class Frog extends Animal implements VariantHolder> { + .ifPresent(this::setVariant); + } + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + FrogAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java +index a04d71967976731b4858d44ac138b7ac390ef7e7..77691e10f7c511eca4384f2974e538d78d55c2ca 100644 +--- a/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -93,8 +93,10 @@ public class Tadpole extends AbstractFish { + return SoundEvents.TADPOLE_FLOP; + } + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + TadpoleAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/animal/goat/Goat.java b/net/minecraft/world/entity/animal/goat/Goat.java +index 25c0c27979f49f08d0cc150de9afe6112f115666..35d492106506c28412fea5c59c7b67c809ce231c 100644 +--- a/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/net/minecraft/world/entity/animal/goat/Goat.java +@@ -182,8 +182,10 @@ public class Goat extends Animal { + return (Brain)super.getBrain(); + } + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + GoatAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/net/minecraft/world/entity/monster/hoglin/Hoglin.java +index fe0cd6790875631cb98a73457d53d782b369bf1d..f93d6564c59ae9a144b56ea3355c4c7425b99eeb 100644 +--- a/net/minecraft/world/entity/monster/hoglin/Hoglin.java ++++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java +@@ -154,8 +154,10 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { + return (Brain)super.getBrain(); + } + ++ private int behaviorTick; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + HoglinAi.updateActivity(this); + if (this.isConverting()) { +diff --git a/net/minecraft/world/entity/monster/piglin/Piglin.java b/net/minecraft/world/entity/monster/piglin/Piglin.java +index daef9043d0eacea948e39b1daa2618287aa40f14..4c30f967c12e11c2e7ae24977509762747dd36de 100644 +--- a/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -340,8 +340,10 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento + return !this.cannotHunt; + } + ++ private int behaviorTick; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + PiglinAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java +index 1c56355fe9c216a7cc8afbbbe94988a0079c8244..f7b9824519fc22b35a7b5f4f0ef9f9891162a493 100644 +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -280,8 +280,10 @@ public class Warden extends Monster implements VibrationSystem { + } + } + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); + super.customServerAiStep(level); + if ((this.tickCount + this.getId()) % 120 == 0) { +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index ee7b4080a9e1e51273f4b48f61caaa21ad7e59d9..a02cd34bcd643c7abad3a355043cb88d035143a0 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -178,6 +178,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + (villager, holder) -> holder.is(PoiTypes.MEETING) + ); + ++ public long nextGolemPanic = -1; // Pufferfish ++ + public Villager(EntityType entityType, Level level) { + this(entityType, level, VillagerType.PLAINS); + } +@@ -282,6 +284,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + // Paper end - EAR 2 + ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel level) { + // Paper start - EAR 2 +@@ -289,7 +292,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + protected void customServerAiStep(ServerLevel level, final boolean inactive) { + // Paper end - EAR 2 +- if (!inactive) this.getBrain().tick(level, this); // Paper - EAR 2 ++ // Pufferfish start ++ if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) { ++ this.getBrain().tick(level, this); // Paper - EAR 2 ++ } ++ // Pufferfish end + if (this.assignProfessionWhenSpawned) { + this.assignProfessionWhenSpawned = false; + } diff --git a/Leaf-Server/minecraft-patches/features/0005-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch b/Leaf-Server/minecraft-patches/features/0005-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch new file mode 100644 index 00000000..4c6d83a9 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0005-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Raneri +Date: Sat, 11 Dec 2021 22:20:45 -0500 +Subject: [PATCH] Pufferfish: Throttle goal selector during inactive ticking + +Original license: GPL v3 +Original project: https://github.com/pufferfish-gg/Pufferfish + +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index cc452ca41c336891473fae98b8681768c52f822d..1b74114d0833eb9ca2c854122727d4bf76a11071 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -213,11 +213,13 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + return this.lookControl; + } + ++ int _pufferfish_inactiveTickDisableCounter = 0; // Pufferfish - throttle inactive goal selector ticking + // Paper start + @Override + public void inactiveTick() { + super.inactiveTick(); +- if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity ++ boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking ++ if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking + this.goalSelector.tick(); + } + if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority diff --git a/patches/server/0010-Purpur-Server-Changes.patch b/Leaf-Server/minecraft-patches/features/0006-Purpur-Server-Minecraft-Changes.patch similarity index 61% rename from patches/server/0010-Purpur-Server-Changes.patch rename to Leaf-Server/minecraft-patches/features/0006-Purpur-Server-Minecraft-Changes.patch index 80e4d4a8..39587d33 100644 --- a/patches/server/0010-Purpur-Server-Changes.patch +++ b/Leaf-Server/minecraft-patches/features/0006-Purpur-Server-Minecraft-Changes.patch @@ -1,364 +1,76 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Github Actions -Date: Thu, 12 Dec 2024 11:10:14 +0000 -Subject: [PATCH] Purpur Server Changes +Date: Thu, 16 Jan 2025 11:21:12 +0000 +Subject: [PATCH] Purpur Server Minecraft Changes Original license: MIT Original project: https://github.com/PurpurMC/Purpur -Commit: 16ce24aa7eb08232030e4570e027f7baefa5f3f9 +Commit: dd4143984219cea8440913b7918322b5ba59265a -Patches below are removed in this patch: -Pufferfish-Server-Changes.patch -Fix-pufferfish-issues.patch -Brand changes in Rebrand.patch -Metrics changes in Purpur-config-files.patch -Fix-decompile-errors.patch -Configurable-server-mod-name.patch -Alternative-Keepalive-Handling.patch -Logger-settings-suppressing-pointless-logs.patch -Add-log-suppression-for-LibraryLoader.patch -Fix-outdated-server-showing-in-ping-before-server-fu.patch -Fix-cow-rotation-when-shearing-mooshroom.patch -Skip-events-if-there-s-no-listeners.patch -Add-5-second-tps-average-in-tps.patch -Arrows-should-not-reset-despawn-counter.patch -MC-238526-Fix-spawner-not-spawning-water-animals-cor.patch -Option-to-disable-kick-for-out-of-order-chat.patch -Make-pufferfish-config-relocatable.patch -MC-121706-Fix-mobs-not-looking-up-and-down-when-stra.patch +Patches listed below are removed in this patch, They exists in Gale or Leaf: +* "net/minecraft/CrashReport.java.patch" + - Rebrand +* "net/minecraft/commands/Commands.java.patch" + - Skip events if there's no listeners +* "net/minecraft/network/chat/SignedMessageChain.java.patch" + - Option to disable kick for out of order chat +* "net/minecraft/server/MinecraftServer.java.patch" + - Add 5 second tps average in /tps + - Configurable server mod name +* "net/minecraft/server/PlayerAdvancements.java.patch" + - Logger settings (suppressing pointless logs) +* "net/minecraft/server/gui/StatsComponent.java.patch" + - Add 5 second tps average in /tps +* "net/minecraft/server/level/WorldGenRegion.java.patch" + - Logger settings (suppressing pointless logs) +* "net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch" + - Alternative Keepalive Handling +* "net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch" + - Fix 'outdated server' showing in ping before server fully boots +* "net/minecraft/stats/ServerRecipeBook.java.patch" + - Logger settings (suppressing pointless logs) +* "net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java.patch" + - MC-121706 - Fix mobs not looking up and down when strafing +* "net/minecraft/world/entity/animal/MushroomCow.java.patch" + - Fix cow rotation when shearing mooshroom +* "net/minecraft/world/entity/animal/WaterAnimal.java.patch" + - MC-238526 - Fix spawner not spawning water animals correctly +* "net/minecraft/world/entity/projectile/AbstractArrow.java.patch" + - Arrows should not reset despawn counter +* "net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch" + - Rebrand -diff --git a/build.gradle.kts b/build.gradle.kts -index 2fddb2f371b5b901be668fb60dbf871830ec571f..3b0cd45cb07d9563c84901729f1f7edc498653bd 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -67,6 +67,12 @@ dependencies { - runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") - runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") - -+ // Purpur start -+ implementation("org.mozilla:rhino-runtime:1.7.15") -+ implementation("org.mozilla:rhino-engine:1.7.15") -+ implementation("dev.omega24:upnp4j:1.0") -+ // Purpur end -+ - testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test - testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") - testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") -@@ -189,7 +195,7 @@ fun TaskContainer.registerRunTask( - name: String, - block: JavaExec.() -> Unit - ): TaskProvider = register(name) { -- group = "paper" -+ group = "paperweight" // Purpur - mainClass.set("org.bukkit.craftbukkit.Main") - standardInput = System.`in` - workingDir = rootProject.layout.projectDirectory -diff --git a/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c ---- /dev/null -+++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java -@@ -0,0 +1,85 @@ -+package org.purpurmc.purpur.gui.util; -+ -+import org.apache.logging.log4j.Level; -+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 java.util.List; -+ -+@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY) -+@ConverterKeys({"highlightGUIError"}) -+@PerformanceSensitive("allocation") -+public final class HighlightErrorConverter extends LogEventPatternConverter { -+ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red -+ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow -+ -+ private final List formatters; -+ -+ private HighlightErrorConverter(List formatters) { -+ super("highlightGUIError", null); -+ this.formatters = formatters; -+ } -+ -+ @Override -+ public void format(LogEvent event, StringBuilder toAppendTo) { -+ Level level = event.getLevel(); -+ if (level.isMoreSpecificThan(Level.ERROR)) { -+ format(ERROR, event, toAppendTo); -+ return; -+ } else if (level.isMoreSpecificThan(Level.WARN)) { -+ format(WARN, event, toAppendTo); -+ return; -+ } -+ for (PatternFormatter formatter : formatters) { -+ formatter.format(event, toAppendTo); -+ } -+ } -+ -+ private void format(String style, LogEvent event, StringBuilder toAppendTo) { -+ int start = toAppendTo.length(); -+ toAppendTo.append(style); -+ int end = toAppendTo.length(); -+ -+ for (PatternFormatter formatter : formatters) { -+ formatter.format(event, toAppendTo); -+ } -+ -+ if (toAppendTo.length() == end) { -+ toAppendTo.setLength(start); -+ } -+ } -+ -+ @Override -+ public boolean handlesThrowable() { -+ for (final PatternFormatter formatter : formatters) { -+ if (formatter.handlesThrowable()) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public static HighlightErrorConverter newInstance(Configuration config, String[] options) { -+ if (options.length != 1) { -+ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length); -+ return null; -+ } -+ -+ if (options[0] == null) { -+ LOGGER.error("No pattern supplied on highlightGUIError"); -+ return null; -+ } -+ -+ PatternParser parser = PatternLayout.createPatternParser(config); -+ List formatters = parser.parse(options[0]); -+ return new HighlightErrorConverter(formatters); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -index 3470720466fc81f977c18e3a97bb918926025a22..c8651af322927c46d075f88890fcd0476bd85440 100644 ---- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -@@ -136,6 +136,10 @@ public class MobGoalHelper { - static { - // TODO these kinda should be checked on each release, in case obfuscation changes - deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); -+ // Purpur start -+ deobfuscationMap.put("zombie_1", "zombie_attack_villager"); -+ deobfuscationMap.put("drowned_1", "drowned_attack_villager"); -+ // Purpur end - - ignored.add("goal_selector_1"); - ignored.add("goal_selector_2"); -diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -index 00470a690b4b0fc8996a03ecd21af8163094184d..23609a71a993fc91271578ee0a541a9c6ec7354f 100644 ---- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -+++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -@@ -32,6 +32,7 @@ public record ServerBuildInfoImpl( - - private static final String BRAND_PAPER_NAME = "Paper"; - private static final String BRAND_GALE_NAME = "Gale"; // Gale - branding changes -+ private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur - private static final String BRAND_LEAF_NAME = "Leaf"; // Leaf - - private static final String BUILD_DEV = "DEV"; -diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -index f0fce4113fb07c64adbec029d177c236cbdcbae8..865dc183276720d54d31d2a54d1bb5c845e80598 100644 ---- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -@@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand { - this.setAliases(Arrays.asList("pl")); - } - -- private static List formatProviders(TreeMap> plugins) { -+ private static List formatProviders(TreeMap> plugins, @NotNull CommandSender sender) { // Purpur - List components = new ArrayList<>(plugins.size()); - for (PluginProvider entry : plugins.values()) { -- components.add(formatProvider(entry)); -+ components.add(formatProvider(entry, sender)); // Purpur - } - - boolean isFirst = true; -@@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand { - return formattedSublists; - } - -- private static Component formatProvider(PluginProvider provider) { -+ private static Component formatProvider(PluginProvider provider, @NotNull CommandSender sender) { // Purpur - TextComponent.Builder builder = Component.text(); - if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) { - builder.append(LEGACY_PLUGIN_STAR); -@@ -117,13 +117,65 @@ public class PaperPluginsCommand extends BukkitCommand { - - String name = provider.getMeta().getName(); - Component pluginName = Component.text(name, fromStatus(provider)) -- .clickEvent(ClickEvent.runCommand("/version " + name)); -+ // Purpur start -+ .clickEvent(ClickEvent.suggestCommand("/version " + name)); -+ -+ if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) { -+ // Event components -+ String description = provider.getMeta().getDescription(); -+ TextComponent.Builder hover = Component.text(); -+ hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN)); -+ -+ if (description != null) { -+ hover.append(Component.newline()) -+ .append(Component.text("Description: ", NamedTextColor.WHITE)) -+ .append(Component.text(description, NamedTextColor.GREEN)); -+ } -+ -+ if (provider.getMeta().getWebsite() != null) { -+ hover.append(Component.newline()) -+ .append(Component.text("Website: ", NamedTextColor.WHITE)) -+ .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN)); -+ } -+ -+ if (!provider.getMeta().getAuthors().isEmpty()) { -+ hover.append(Component.newline()); -+ if (provider.getMeta().getAuthors().size() == 1) { -+ hover.append(Component.text("Author: ")); -+ } else { -+ hover.append(Component.text("Authors: ")); -+ } -+ -+ hover.append(getAuthors(provider.getMeta())); -+ } -+ -+ pluginName.hoverEvent(hover.build()); -+ } -+ // Purpur end - - builder.append(pluginName); - - return builder.build(); - } - -+ // Purpur start -+ @NotNull -+ private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) { -+ TextComponent.Builder builder = Component.text(); -+ List authors = pluginMeta.getAuthors(); -+ -+ for (int i = 0; i < authors.size(); i++) { -+ if (i > 0) { -+ builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE)); -+ } -+ -+ builder.append(Component.text(authors.get(i), NamedTextColor.GREEN)); -+ } -+ -+ return builder.build(); -+ } -+ // Purpur end -+ - private static Component asPlainComponents(String strings) { - net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); - for (String string : strings.split("\n")) { -@@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand { +diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java +index 47463babd445763c861ac5c4713b5bd3be5cbeec..f2e8ca24f4a9beb071415db858d80bc7e5e59321 100644 +--- a/io/papermc/paper/entity/activation/ActivationRange.java ++++ b/io/papermc/paper/entity/activation/ActivationRange.java +@@ -153,6 +153,8 @@ public final class ActivationRange { + continue; } - } -- Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); -+ //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); - //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs - -- sender.sendMessage(infoMessage); -+ //sender.sendMessage(infoMessage); // Purpur - -- if (!paperPlugins.isEmpty()) { -- sender.sendMessage(PAPER_HEADER); -- } -+ //if (!paperPlugins.isEmpty()) { // Purpur -+ sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur -+ //} // Purpur - -- for (Component component : formatProviders(paperPlugins)) { -+ for (Component component : formatProviders(paperPlugins, sender)) { // Purpur - sender.sendMessage(component); - } - -- if (!spigotPlugins.isEmpty()) { -- sender.sendMessage(BUKKIT_HEADER); -- } -+ //if (!spigotPlugins.isEmpty()) { // Purpur -+ sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur -+ //} // Purpur - -- for (Component component : formatProviders(spigotPlugins)) { -+ for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur - sender.sendMessage(component); - } - -diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index b993d3e2e9d987115129067e3a51060880453ee2..a9d5bad94d499ea8c63beff268713261cbdea216 100644 ---- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -+++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -@@ -263,6 +263,7 @@ public class PaperConfigurations extends Configurations 0 || SysoutCatcher.NAG_TIMEOUT > 0) { -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index 13bd145b1e8006a53c22f5dc0c78f29b540c7663..0d133cd7993eb40b19e2aabe8e2bfcdcf5352398 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -211,6 +211,19 @@ public class CommandSourceStack implements ExecutionCommandSource io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps); + } -+ // Purpur end ++ // Purpur end - Purpur config files + - public void sendSuccess(Supplier feedbackSupplier, boolean broadcastToOps) { - boolean flag1 = this.source.acceptsSuccess() && !this.silent; - boolean flag2 = broadcastToOps && this.source.shouldInformAdmins() && !this.silent; -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 8f0162ffa1461e7444b51917001d8a27ca1f3dae..6191930c133758936ffd3cc2588c3f8713145508 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -223,8 +223,8 @@ public class Commands { + public void sendSuccess(Supplier messageSupplier, boolean allowLogging) { + boolean flag = this.source.acceptsSuccess() && !this.silent; + boolean flag1 = allowLogging && this.source.shouldInformAdmins() && !this.silent; +diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java +index 60cfa713ffc778d0cd11dfb2ea1a52db806119b2..a5cc373dbe034560865e196ce12a8361932ea085 100644 +--- a/net/minecraft/commands/Commands.java ++++ b/net/minecraft/commands/Commands.java +@@ -135,6 +135,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler; + import net.minecraft.world.flag.FeatureFlagSet; + import net.minecraft.world.flag.FeatureFlags; + import net.minecraft.world.level.GameRules; ++import org.bukkit.event.player.PlayerCommandSendEvent; + import org.slf4j.Logger; + + public class Commands { +@@ -213,8 +214,8 @@ public class Commands { JfrCommand.register(this.dispatcher); } - if (SharedConstants.IS_RUNNING_IN_IDE) { - TestCommand.register(this.dispatcher); -+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur -+ if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur - RaidCommand.register(this.dispatcher, commandRegistryAccess); ++ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur - register minecraft debug commands ++ if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur - register minecraft debug commands + RaidCommand.register(this.dispatcher, context); DebugPathCommand.register(this.dispatcher); DebugMobSpawningCommand.register(this.dispatcher); -@@ -252,6 +252,14 @@ public class Commands { +@@ -242,6 +243,14 @@ public class Commands { StopCommand.register(this.dispatcher); TransferCommand.register(this.dispatcher); WhitelistCommand.register(this.dispatcher); -+ org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur -+ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur ++ org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur - Add credits command ++ org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur - Add demo command ++ org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur - Add ping command ++ org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur - Add uptime command ++ org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur - Implement TPSBar ++ org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur - Add compass command ++ org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur - Add rambar command ++ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur - Add ram command } - if (environment.includeIntegrated) { -diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -index c8d39e6e1c570c9219f6066da273dc0130920519..b455c7e9d18bac3654daa8510f85cc21202e254b 100644 ---- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -@@ -198,10 +198,10 @@ public class EntitySelector { - + if (selection.includeIntegrated) { +diff --git a/net/minecraft/commands/arguments/selector/EntitySelector.java b/net/minecraft/commands/arguments/selector/EntitySelector.java +index 514f8fbdeb776087608665c35de95294aadf5cf0..b305ba9bab617bf4e52d0e6ddf160bacc5751a94 100644 +--- a/net/minecraft/commands/arguments/selector/EntitySelector.java ++++ b/net/minecraft/commands/arguments/selector/EntitySelector.java +@@ -192,26 +192,27 @@ public class EntitySelector { + this.checkPermissions(source); if (this.playerName != null) { - entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); -- return entityplayer == null ? List.of() : List.of(entityplayer); -+ return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur + ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName); +- return playerByName == null ? List.of() : List.of(playerByName); ++ return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector } else if (this.entityUUID != null) { - entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); -- return entityplayer == null ? List.of() : List.of(entityplayer); -+ return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur + ServerPlayer playerByName = source.getServer().getPlayerList().getPlayer(this.entityUUID); +- return playerByName == null ? List.of() : List.of(playerByName); ++ return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector } else { - Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); - AABB axisalignedbb = this.getAbsoluteAabb(vec3d); -@@ -214,7 +214,7 @@ public class EntitySelector { - ServerPlayer entityplayer1 = (ServerPlayer) entity; - - if (predicate.test(entityplayer1)) { -- return List.of(entityplayer1); -+ return !canSee(source, entityplayer1) ? List.of() : List.of(entityplayer1); // Purpur - } - } - -@@ -225,6 +225,7 @@ public class EntitySelector { - + Vec3 vec3 = this.position.apply(source.getPosition()); + AABB absoluteAabb = this.getAbsoluteAabb(vec3); + Predicate predicate = this.getPredicate(vec3, absoluteAabb, null); + if (this.currentEntity) { +- return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) ? List.of(serverPlayer) : List.of(); ++ return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) && canSee(source, serverPlayer) ? List.of(serverPlayer) : List.of(); // Purpur - Hide hidden players from entity selector + } else { + int resultLimit = this.getResultLimit(); + List players; if (this.isWorldLimited()) { - object = source.getLevel().getPlayers(predicate, i); -+ ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur + players = source.getLevel().getPlayers(predicate, resultLimit); ++ players.removeIf(entityplayer3 -> !canSee(source, entityplayer3)); // Purpur - Hide hidden players from entity selector } else { - object = new ObjectArrayList(); - Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator(); -@@ -232,7 +233,7 @@ public class EntitySelector { - while (iterator.hasNext()) { - ServerPlayer entityplayer2 = (ServerPlayer) iterator.next(); + players = new ObjectArrayList<>(); -- if (predicate.test(entityplayer2)) { -+ if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur - ((List) object).add(entityplayer2); - if (((List) object).size() >= i) { - return (List) object; -@@ -299,4 +300,10 @@ public class EntitySelector { - public static Component joinNames(List entities) { - return ComponentUtils.formatList(entities, Entity::getDisplayName); + for (ServerPlayer serverPlayer1 : source.getServer().getPlayerList().getPlayers()) { +- if (predicate.test(serverPlayer1)) { ++ if (predicate.test(serverPlayer1) && canSee(source, serverPlayer1)) { // Purpur - Hide hidden players from entity selector + players.add(serverPlayer1); + if (players.size() >= resultLimit) { + return players; +@@ -270,4 +271,10 @@ public class EntitySelector { + public static Component joinNames(List names) { + return ComponentUtils.formatList(names, Entity::getDisplayName); } + -+ // Purpur start ++ // Purpur start - Hide hidden players from entity selector + private boolean canSee(CommandSourceStack sender, ServerPlayer target) { + return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity()); + } -+ // Purpur end ++ // Purpur end - Hide hidden players from entity selector } -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index f58a94efafbc01d402cd03a108bb90f60930a316..21ea63da99c5b3e2e1ab9cc1049c903bba6cf288 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -62,6 +62,12 @@ public class BlockPos extends Vec3i { +diff --git a/net/minecraft/core/BlockPos.java b/net/minecraft/core/BlockPos.java +index a81694a22e94cca6f7110f7d5b205d1303f4e071..6518d3fff6daf331b24a7bf5b39fa1920b73711d 100644 +--- a/net/minecraft/core/BlockPos.java ++++ b/net/minecraft/core/BlockPos.java +@@ -63,6 +63,12 @@ public class BlockPos extends Vec3i { public static final int MAX_HORIZONTAL_COORDINATE = 33554431; // Paper end - Optimize Bit Operations by inlining -+ // Purpur start ++ // Purpur start - Ridables + public BlockPos(net.minecraft.world.entity.Entity entity) { + super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ()); + } -+ // Purpur end ++ // Purpur end - Ridables + public BlockPos(int x, int y, int z) { super(x, y, z); } -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 0d12605dc84dad49faa18bf1fd058c3c168623ee..c6490554a3025f4de3f3218178fad76cd1848a19 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -930,5 +930,22 @@ public interface DispenseItemBehavior { +diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 717c84165d5e25cd384f56b7cb976abf6669b6f0..f576449e8bc6fd92963cbe3954b0c853a02def3c 100644 +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -892,5 +892,22 @@ public interface DispenseItemBehavior { DispenserBlock.registerBehavior(Items.TNT_MINECART, new MinecartDispenseItemBehavior(EntityType.TNT_MINECART)); DispenserBlock.registerBehavior(Items.HOPPER_MINECART, new MinecartDispenseItemBehavior(EntityType.HOPPER_MINECART)); DispenserBlock.registerBehavior(Items.COMMAND_BLOCK_MINECART, new MinecartDispenseItemBehavior(EntityType.COMMAND_BLOCK_MINECART)); -+ // Purpur start ++ // Purpur start - Dispensers place anvils option + DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() { + @Override + public ItemStack execute(BlockSource dispenser, ItemStack stack) { @@ -530,90 +243,106 @@ index 0d12605dc84dad49faa18bf1fd058c3c168623ee..c6490554a3025f4de3f3218178fad76c + return stack; + } + })); -+ // Purpur end ++ // Purpur end - Dispensers place anvils option } } -diff --git a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -index bf8c511739265c6a9cd277752e844481598f8966..ffe2399ab6b1f311536475d8216238b5b01c5dab 100644 ---- a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -@@ -41,7 +41,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { +diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +index 3595bbd05fb3e8fe57e38d4e2df5c6237046b726..b91b2f5ea6a1da0477541dc65fdfbfa57b9af475 100644 +--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -31,7 +31,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { return false; } else { - LivingEntity entityliving = (LivingEntity) list.getFirst(); -- EquipmentSlot enumitemslot = entityliving.getEquipmentSlotForItem(stack); -+ EquipmentSlot enumitemslot = pointer.level().purpurConfig.dispenserApplyCursedArmor ? entityliving.getEquipmentSlotForItem(stack) : entityliving.getEquipmentSlotForDispenserItem(stack); if (enumitemslot == null) return false; // Purpur - Dispenser curse of binding protection - ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event - + LivingEntity livingEntity = entitiesOfClass.getFirst(); +- EquipmentSlot equipmentSlotForItem = livingEntity.getEquipmentSlotForItem(item); ++ EquipmentSlot equipmentSlotForItem = blockSource.level().purpurConfig.dispenserApplyCursedArmor ? livingEntity.getEquipmentSlotForItem(item) : livingEntity.getEquipmentSlotForDispenserItem(item); if (equipmentSlotForItem == null) return false; // Purpur - Dispenser curse of binding protection + ItemStack itemStack = item.copyWithCount(1); // Paper - shrink below and single item in event // CraftBukkit start -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 1900da381257a00fc3b96d397e298caf2f8c861f..ebef8e06835e3ef29fad60006c570f791e4a3937 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -617,11 +617,20 @@ public class Connection extends SimpleChannelInboundHandler> { + net.minecraft.world.level.Level world = blockSource.level(); +diff --git a/net/minecraft/gametest/framework/GameTestHelper.java b/net/minecraft/gametest/framework/GameTestHelper.java +index fe4ae6bcdcbb55c47e9f9a4d63ead4c39e6d63cf..ec0998369158286fccb38c8e10c3cfa2a653a8aa 100644 +--- a/net/minecraft/gametest/framework/GameTestHelper.java ++++ b/net/minecraft/gametest/framework/GameTestHelper.java +@@ -279,6 +279,10 @@ public class GameTestHelper { + return gameType.isCreative(); + } + ++ public void setAfk(final boolean afk) {} // Purpur - AFK API ++ ++ public void resetLastActionTime() {} // Purpur - Ridables ++ + @Override + public boolean isLocalPlayer() { + return true; +diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java +index 208efae06c7c44f220d4192219a86ec55c98a2fe..3baf29818d82aa7ba1cc565aaeb26288e5a23a33 100644 +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -588,11 +588,20 @@ public class Connection extends SimpleChannelInboundHandler> { private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world private static int joinAttemptsThisTick; // Paper - Buffer joins to world private static int currTick; // Paper - Buffer joins to world -+ private static int tickSecond; // Purpur ++ private static int tickSecond; // Purpur - Max joins per second public void tick() { this.flushQueue(); // Paper start - Buffer joins to world if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; -+ // Purpur start ++ // Purpur start - Max joins per second + if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) { + if (++Connection.tickSecond > 20) { + Connection.tickSecond = 0; + Connection.joinAttemptsThisTick = 0; + } + } else -+ // Purpur end ++ // Purpur end - Max joins per second Connection.joinAttemptsThisTick = 0; } // Paper end - Buffer joins to world -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index c7b5429910df7e3c4cfe1ac2f095845fd493c9b1..e0c2e52801421845c74da938b20692a68d838cdf 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -120,6 +120,12 @@ public class Main { +diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java +index 841546cef2a98427ae78ca2a07693391eb0d3035..d6ab395d1081196fb690dc872b243524e684e70c 100644 +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -108,6 +108,12 @@ public class Main { JvmProfiler.INSTANCE.start(Environment.SERVER); } -+ // Purpur start - load config files early -+ org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("purpur-settings")); ++ // Purpur start - Add toggle for enchant level clamping - load config files early ++ org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("purpur-settings")); + org.purpurmc.purpur.PurpurConfig.clampEnchantLevels = purpurConfiguration.getBoolean("settings.enchantment.clamp-levels", true); -+ org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); -+ // Purpur end - load config files early ++ org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); // Purpur - register minecraft debug commands ++ // Purpur end - Add toggle for enchant level clamping - load config files early + - io.papermc.paper.plugin.PluginInitializerManager.load(optionset); // Paper + io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper Bootstrap.bootStrap(); Bootstrap.validate(); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index e084dd8f6e299e2ec71d7d5741c92b0e171f34f6..a624cd8a0fa9ad05f8f69c50f3fdf961713b1791 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -298,6 +298,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; // Paper - don't store the vanilla dispatcher -@@ -313,6 +314,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping ++ public boolean lagging = false; // Purpur - Lagging threshold + public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation ++ protected boolean upnp = false; // Purpur - UPnP Port Forwarding + public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning - public volatile Thread shutdownThread; // Paper - public volatile boolean abnormalExit = false; // Paper -@@ -1038,6 +1041,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { +@@ -989,6 +992,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - Add EntityMoveEvent - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers - worldserver.updateLagCompensationTick(); // Paper - lag compensation -+ worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - +@@ -1684,6 +1725,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - Add EntityMoveEvent + serverLevel.updateLagCompensationTick(); // Paper - lag compensation + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers ++ serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables /* Drop global time updates if (this.tickCount % 20 == 0) { -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 66e7fa8a01d5364f7a82ed36f41edc6735b0b5ef..67f7c397328c8fbdbd30e1f3b94821a785ff1036 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -249,6 +249,7 @@ public class PlayerAdvancements { - advancement.value().display().ifPresent((advancementdisplay) -> { + this.synchronizeTime(serverLevel); +diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java +index ecb7edc1b10d23bb1979152341cd4a2b89613a65..792ba93b531e9586e26aafa00830022a8996fc04 100644 +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -195,6 +195,7 @@ public class PlayerAdvancements { + advancement.value().display().ifPresent(displayInfo -> { // Paper start - Add Adventure message to PlayerAdvancementDoneEvent if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { -+ if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur ++ if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur - Configurable broadcast settings this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); // Paper end } -diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java -index cf0a5943f457c532958f40b4989fa18f967abae6..2ab8ff8ca51eb841932ccca4a348acc0141264a8 100644 ---- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java -+++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java +diff --git a/net/minecraft/server/commands/EnchantCommand.java b/net/minecraft/server/commands/EnchantCommand.java +index fe86823f1a02d66df143756f00ee56fb9f634475..53accb9ec72100c8b95bbfe083009f4ad9c9e4de 100644 +--- a/net/minecraft/server/commands/EnchantCommand.java ++++ b/net/minecraft/server/commands/EnchantCommand.java @@ -70,7 +70,7 @@ public class EnchantCommand { private static int enchant(CommandSourceStack source, Collection targets, Holder enchantment, int level) throws CommandSyntaxException { - Enchantment enchantment2 = enchantment.value(); -- if (level > enchantment2.getMaxLevel()) { -+ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment2.getMaxLevel()) { // Purpur - Config to allow unsafe enchants - throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel()); + Enchantment enchantment1 = enchantment.value(); +- if (level > enchantment1.getMaxLevel()) { ++ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment1.getMaxLevel()) { // Purpur - Config to allow unsafe enchants + throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment1.getMaxLevel()); } else { int i = 0; @@ -81,7 +81,7 @@ public class EnchantCommand { - ItemStack itemStack = livingEntity.getMainHandItem(); - if (!itemStack.isEmpty()) { - if (enchantment2.canEnchant(itemStack) -- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(itemStack).keySet(), enchantment)) { -+ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(itemStack).keySet(), enchantment) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants - itemStack.enchant(enchantment, level); + ItemStack mainHandItem = livingEntity.getMainHandItem(); + if (!mainHandItem.isEmpty()) { + if (enchantment1.canEnchant(mainHandItem) +- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment)) { ++ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !mainHandItem.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants + mainHandItem.enchant(enchantment, level); i++; } else if (targets.size() == 1) { -diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -index d1da3600dc07107309b20ebe6e7c0c4da0e8de76..244b4719c689f153fa36381a60acc280bb0bd9b3 100644 ---- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -@@ -57,6 +57,18 @@ public class GameModeCommand { +diff --git a/net/minecraft/server/commands/GameModeCommand.java b/net/minecraft/server/commands/GameModeCommand.java +index c44cdbbdc06b25bd20a208386545a10af9b96df8..a88b8f999b181071ebb492bc1afa2d72fff3748e 100644 +--- a/net/minecraft/server/commands/GameModeCommand.java ++++ b/net/minecraft/server/commands/GameModeCommand.java +@@ -51,6 +51,18 @@ public class GameModeCommand { } - private static int setMode(CommandContext context, Collection targets, GameType gameMode) { -+ // Purpur start + private static int setMode(CommandContext source, Collection players, GameType gameType) { ++ // Purpur start - Gamemode extra permissions + if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) { -+ String gamemode = gameMode.getName(); -+ CommandSourceStack sender = context.getSource(); ++ String gamemode = gameType.getName(); ++ CommandSourceStack sender = source.getSource(); + if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) { + return 0; + } -+ if (sender.getEntity() instanceof ServerPlayer player && (targets.size() > 1 || !targets.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) { ++ if (sender.getEntity() instanceof ServerPlayer player && (players.size() > 1 || !players.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) { + return 0; + } + } -+ // Purpur end ++ // Purpur end - Gamemode extra permissions int i = 0; - for (ServerPlayer serverPlayer : targets) { -diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java -index 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..2f7897744f4aea718170698881773e9031a58a51 100644 ---- a/src/main/java/net/minecraft/server/commands/GiveCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java -@@ -60,6 +60,7 @@ public class GiveCommand { - boolean flag = entityplayer.getInventory().add(itemstack1); - ItemEntity entityitem; - + for (ServerPlayer serverPlayer : players) { +diff --git a/net/minecraft/server/commands/GiveCommand.java b/net/minecraft/server/commands/GiveCommand.java +index 8b7af734ca4ed3cafa810460b2cea6c1e6342a69..c394e4ea9b066895a8ad370615383a4a58d11c19 100644 +--- a/net/minecraft/server/commands/GiveCommand.java ++++ b/net/minecraft/server/commands/GiveCommand.java +@@ -66,6 +66,7 @@ public class GiveCommand { + i1 -= min; + ItemStack itemStack1 = item.createItemStack(min, false); + boolean flag = serverPlayer.getInventory().add(itemStack1); + if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping - if (flag && itemstack1.isEmpty()) { - entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event - if (entityitem != null) { -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 535dc6414c6b14ebd652695615e5a62d82f34171..940fdcfe0e1bd68891903f33d61d63c2d72fc3df 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -111,6 +111,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - return; - } + if (flag && itemStack1.isEmpty()) { + ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event + if (itemEntity != null) { +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 30dc45b2201bf7435d7f38866dfa4b5fbbf44957..22b0f33dc3ef9f51ba2ca3cb665b07a16bd1c9d9 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -106,6 +106,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + // CraftBukkit start + if (!org.bukkit.craftbukkit.Main.useConsole) return; // Paper start - Use TerminalConsoleAppender -+ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click) ++ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - GUI Improvements - has no GUI or has console (did not double-click) new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); /* jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader; -@@ -219,6 +220,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -208,6 +209,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.spigotmc.SpigotConfig.registerCommands(); // Spigot end io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. -+ // Purpur start ++ // Purpur start - Configurable void damage height and damage + try { + org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); + } catch (Exception e) { @@ -778,15 +521,31 @@ index 535dc6414c6b14ebd652695615e5a62d82f34171..940fdcfe0e1bd68891903f33d61d63c2 + return false; + } + org.purpurmc.purpur.PurpurConfig.registerCommands(); -+ // Purpur end ++ // Purpur end - Configurable void damage height and damage // Paper start - initialize global and world-defaults configuration this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); -@@ -295,6 +305,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error +@@ -229,6 +239,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + org.galemc.gale.command.GaleCommands.registerCommands(this); // Gale - Gale commands - register commands + this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics ++ // Purpur start - Purpur config files ++ try { ++ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); ++ } catch (Exception e) { ++ DedicatedServer.LOGGER.error("Unable to load server configuration", e); ++ return false; ++ } ++ org.purpurmc.purpur.PurpurConfig.registerCommands(); ++ // Purpur end - Purpur config files + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + + // Gale start - Pufferfish - SIMD support +@@ -283,6 +302,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + if (true) throw new IllegalStateException("Failed to bind to port", var10); // Paper - Propagate failed to bind to port error return false; } -+ // Purpur start ++ // Purpur start - UPnP Port Forwarding + if (org.purpurmc.purpur.PurpurConfig.useUPnP) { + LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service..."); + if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) { @@ -809,157 +568,155 @@ index 535dc6414c6b14ebd652695615e5a62d82f34171..940fdcfe0e1bd68891903f33d61d63c2 + LOGGER.error("[UPnP] Service is unavailable"); + } + } -+ // Purpur end ++ // Purpur end - UPnP Port Forwarding // CraftBukkit start // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up -@@ -369,6 +403,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } +@@ -364,6 +407,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish -+ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur -+ if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur + ++ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur - Implement TPSBar ++ if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur - Give bee counts in beehives to Purpur clients return true; } } -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -index c3ec370b83b895be0f03662e3884fa4a2442a2a6..05e16103af3fd276f0196ddf1a2e5b729b025c34 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -@@ -56,6 +56,7 @@ public class DedicatedServerProperties extends Settings finalizers = Lists.newArrayList(); final AtomicBoolean isClosing = new AtomicBoolean(); -+ // Purpur start ++ // Purpur start - GUI Improvements + private final CommandHistory history = new CommandHistory(); + private String currentCommand = ""; + private int historyIndex = 0; -+ // Purpur end ++ // Purpur end - GUI Improvements public static MinecraftServerGui showFrameFor(final DedicatedServer server) { try { -@@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent { - ; +@@ -46,7 +51,7 @@ public class MinecraftServerGui extends JComponent { + } catch (Exception var3) { } -- final JFrame jframe = new JFrame("Minecraft server"); -+ final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur - final MinecraftServerGui servergui = new MinecraftServerGui(server); - - jframe.setDefaultCloseOperation(2); -@@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent { - jframe.pack(); - jframe.setLocationRelativeTo((Component) null); - jframe.setVisible(true); -- jframe.setName("Minecraft server"); // Paper - Improve ServerGUI -+ jframe.setName("Purpur Minecraft server"); // Paper - Improve ServerGUI // Purpur - +- final JFrame jFrame = new JFrame("Minecraft server"); ++ final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI + final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server); + jFrame.setDefaultCloseOperation(2); + jFrame.add(minecraftServerGui); +@@ -54,7 +59,7 @@ public class MinecraftServerGui extends JComponent { + jFrame.setLocationRelativeTo(null); + jFrame.setVisible(true); // Paper start - Improve ServerGUI +- jFrame.setName("Minecraft server"); ++ jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI try { -@@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent { - jframe.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent windowevent) { - if (!servergui.isClosing.getAndSet(true)) { -- jframe.setTitle("Minecraft server - shutting down!"); -+ jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur + jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); + } catch (java.io.IOException ignore) { +@@ -64,7 +69,7 @@ public class MinecraftServerGui extends JComponent { + @Override + public void windowClosing(WindowEvent event) { + if (!minecraftServerGui.isClosing.getAndSet(true)) { +- jFrame.setTitle("Minecraft server - shutting down!"); ++ jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI server.halt(true); - servergui.runFinalizers(); + minecraftServerGui.runFinalizers(); } -@@ -159,7 +164,7 @@ public class MinecraftServerGui extends JComponent { +@@ -112,7 +117,7 @@ public class MinecraftServerGui extends JComponent { private JComponent buildChatPanel() { - JPanel jpanel = new JPanel(new BorderLayout()); -- JTextArea jtextarea = new JTextArea(); -+ org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); - - jtextarea.setEditable(false); -@@ -171,10 +176,43 @@ public class MinecraftServerGui extends JComponent { - - if (!s.isEmpty()) { - this.server.handleConsoleInput(s, this.server.createCommandSourceStack()); -+ // Purpur start -+ history.add(s); + JPanel jPanel = new JPanel(new BorderLayout()); +- JTextArea jTextArea = new JTextArea(); ++ org.purpurmc.purpur.gui.JColorTextPane jTextArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements + JScrollPane jScrollPane = new JScrollPane(jTextArea, 22, 30); + jTextArea.setEditable(false); + jTextArea.setFont(MONOSPACED); +@@ -121,10 +126,43 @@ public class MinecraftServerGui extends JComponent { + String trimmed = jTextField.getText().trim(); + if (!trimmed.isEmpty()) { + this.server.handleConsoleInput(trimmed, this.server.createCommandSourceStack()); ++ // Purpur start - GUI Improvements ++ history.add(trimmed); + historyIndex = -1; -+ // Purpur end ++ // Purpur end - GUI Improvements } - jtextfield.setText(""); + jTextField.setText(""); }); -+ // Purpur start -+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); -+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); -+ jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() { ++ // Purpur start - GUI Improvements ++ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); ++ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); ++ jTextField.getActionMap().put("up", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex < 0) { -+ currentCommand = jtextfield.getText(); ++ currentCommand = jTextField.getText(); + } + if (historyIndex < history.size() - 1) { -+ jtextfield.setText(history.get(++historyIndex)); ++ jTextField.setText(history.get(++historyIndex)); + } + } + }); -+ jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() { ++ jTextField.getActionMap().put("down", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex >= 0) { + if (historyIndex == 0) { + --historyIndex; -+ jtextfield.setText(currentCommand); ++ jTextField.setText(currentCommand); + } else { + --historyIndex; -+ jtextfield.setText(history.get(historyIndex)); ++ jTextField.setText(history.get(historyIndex)); + } + } + } + }); -+ // Purpur end - jtextarea.addFocusListener(new FocusAdapter() { // CraftBukkit - decompile error - public void focusGained(FocusEvent focusevent) {} - }); -@@ -210,7 +248,7 @@ public class MinecraftServerGui extends JComponent { ++ // Purpur end - GUI Improvements + jTextArea.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent event) { +@@ -159,7 +197,7 @@ public class MinecraftServerGui extends JComponent { } private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper -- public void print(JTextArea textArea, JScrollPane scrollPane, String message) { -+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur +- public void print(JTextArea textArea, JScrollPane scrollPane, String line) { ++ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String line) { // Purpur - GUI Improvements if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> { - this.print(textArea, scrollPane, message); -@@ -224,11 +262,14 @@ public class MinecraftServerGui extends JComponent { - flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); + SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line)); + } else { +@@ -170,10 +208,11 @@ public class MinecraftServerGui extends JComponent { + flag = verticalScrollBar.getValue() + verticalScrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > verticalScrollBar.getMaximum(); } -+ /* // Purpur - try { - document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit - } catch (BadLocationException badlocationexception) { - ; - } -+ */ // Purpur -+ textArea.append(message); // Purpur +- try { ++ /*try { // Purpur - GUI Improvements + document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit + } catch (BadLocationException var8) { +- } ++ }*/ // Purpur - GUI Improvements ++ textArea.append(line); // Purpur - GUI Improvements if (flag) { - jscrollbar.setValue(Integer.MAX_VALUE); -@@ -236,4 +277,16 @@ public class MinecraftServerGui extends JComponent { - + verticalScrollBar.setValue(Integer.MAX_VALUE); +@@ -181,6 +220,18 @@ public class MinecraftServerGui extends JComponent { } } -+ -+ // Purpur start + ++ // Purpur start - GUI Improvements + public static class CommandHistory extends java.util.LinkedList { + @Override + public boolean add(String command) { @@ -969,128 +726,117 @@ index 759062d219ff490a3cb19e710c4d18e3e08288e0..8f74c2ec5252b6265549589310d74233 + return super.offerFirst(command); + } + } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index bf813380d5767ce05cdeca7084e6f19aa106803a..796322fc35da0b47654e60388ec93cae7b999766 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -81,7 +81,7 @@ public class ServerEntity { - @Nullable - private List> trackedDataValues; - // CraftBukkit start -- private final Set trackedPlayers; -+ public final Set trackedPlayers; // Purpur - private -> public - - public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { - this.trackedPlayers = trackedPlayers; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index ea586b6c382ac49ffce0675442cc5dfb5c638628..7d3d42e4fb09ac466cd41df3830fbcffc92d2737 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -221,6 +221,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe ++ // Purpur end - GUI Improvements ++ + // Paper start - Add onboarding message for initial server start + private JComponent buildOnboardingPanel() { + String onboardingLink = "https://docs.papermc.io/paper/next-steps"; +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index e52d976f0c1c5eacdc8608b204c3a178b9b17446..a13aa7b896a998975d2ee14eafb86a85db988e69 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -205,6 +205,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private final StructureManager structureManager; private final StructureCheck structureCheck; private final boolean tickTime; -+ private double preciseTime; // Purpur -+ private boolean forceTime; // Purpur ++ private double preciseTime; // Purpur - Configurable daylight cycle ++ private boolean forceTime; // Purpur - Configurable daylight cycle private final RandomSequences randomSequences; // CraftBukkit start -@@ -229,6 +231,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -213,6 +215,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) -+ public boolean hasRidableMoveEvent = false; // Purpur ++ public boolean hasRidableMoveEvent = false; // Purpur - Ridables public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -602,7 +605,24 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -593,7 +596,24 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit end - this.tickTime = flag1; - this.server = minecraftserver; -- this.customSpawners = list; -+ // Purpur start - enable/disable MobSpawners per world -+ this.customSpawners = Lists.newArrayList(); + this.tickTime = tickTime; + this.server = server; +- this.customSpawners = customSpawners; ++ // Purpur start - Allow toggling special MobSpawners per world ++ this.customSpawners = new ArrayList<>(); + if (purpurConfig.phantomSpawning) { -+ customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner()); ++ this.customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner()); + } + if (purpurConfig.patrolSpawning) { -+ customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner()); ++ this.customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner()); + } + if (purpurConfig.catSpawning) { -+ customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner()); ++ this.customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner()); + } + if (purpurConfig.villageSiegeSpawning) { -+ customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege()); ++ this.customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege()); + } + if (purpurConfig.villagerTraderSpawning) { -+ customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(iworlddataserver)); ++ this.customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(serverLevelData)); + } -+ // Purpur end - this.serverLevelData = iworlddataserver; - ChunkGenerator chunkgenerator = worlddimension.generator(); ++ // Purpur end - Allow toggling special MobSpawners per world + this.serverLevelData = serverLevelData; + ChunkGenerator chunkGenerator = levelStem.generator(); // CraftBukkit start -@@ -674,6 +694,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -679,6 +699,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); // Paper end - rewrite chunk system this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit -+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur ++ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle } // Paper start -@@ -720,7 +741,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); - long j; +@@ -721,7 +742,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } -- if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { -+ if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { - // CraftBukkit start - j = this.levelData.getDayTime() + 24000L; - TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime()); -@@ -828,6 +849,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - this.serverLevelData.setGameTime(i); - this.serverLevelData.getScheduledEvents().tick(this.server, i); + int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); +- if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { ++ if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { // Purpur - Config for skipping night + // Paper start - create time skip event - move up calculations + final long newDayTime = this.levelData.getDayTime() + 24000L; + org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( +@@ -833,6 +854,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.serverLevelData.setGameTime(l); + this.serverLevelData.getScheduledEvents().tick(this.server, l); if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { -+ // Purpur start ++ // Purpur start - Configurable daylight cycle + int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks; + if (incrementTicks != 12000) { + this.preciseTime += 12000 / (double) incrementTicks; + this.setDayTime(this.preciseTime); + } else -+ // Purpur end ++ // Purpur end - Configurable daylight cycle this.setDayTime(this.levelData.getDayTime() + 1L); } + } +@@ -840,8 +868,22 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe -@@ -836,8 +864,22 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - public void setDayTime(long timeOfDay) { - this.serverLevelData.setDayTime(timeOfDay); -+ // Purpur start -+ this.preciseTime = timeOfDay; + public void setDayTime(long time) { + this.serverLevelData.setDayTime(time); ++ // Purpur start - Configurable daylight cycle ++ this.preciseTime = time; + this.forceTime = false; + } + public void setDayTime(double i) { + this.serverLevelData.setDayTime((long) i); + this.forceTime = true; -+ // Purpur end ++ // Purpur end - Configurable daylight cycle } -+ // Purpur start ++ // Purpur start - Configurable daylight cycle + public boolean isForceTime() { + return this.forceTime; + } -+ // Purpur end ++ // Purpur end - Configurable daylight cycle + - public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) { - Iterator iterator = this.customSpawners.iterator(); - -@@ -923,10 +965,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses - - if (flag1) { -- SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); -+ // Purpur start + public void tickCustomSpawners(boolean spawnEnemies, boolean spawnFriendlies) { + for (CustomSpawner customSpawner : this.customSpawners) { + customSpawner.tick(this, spawnEnemies, spawnFriendlies); +@@ -919,9 +961,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01) // Paper - Configurable spawn chances for skeleton horses + && !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD); + if (flag) { ++ // Purpur start - Special mobs naturally spawn + net.minecraft.world.entity.animal.horse.AbstractHorse entityhorseskeleton; + if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) { + entityhorseskeleton = EntityType.ZOMBIE_HORSE.create(this, EntitySpawnReason.EVENT); @@ -1098,204 +844,205 @@ index ea586b6c382ac49ffce0675442cc5dfb5c638628..7d3d42e4fb09ac466cd41df3830fbcff + entityhorseskeleton = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); + if (entityhorseskeleton != null) ((SkeletonHorse) entityhorseskeleton).setTrap(true); + } -+ // Purpur end - - if (entityhorseskeleton != null) { -- entityhorseskeleton.setTrap(true); -+ //entityhorseskeleton.setTrap(true); // Purpur - moved up - entityhorseskeleton.setAge(0); - entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); - this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit -@@ -1002,7 +1052,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - return holder.is(PoiTypes.LIGHTNING_ROD); - }, (blockposition1) -> { - return blockposition1.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockposition1.getX(), blockposition1.getZ()) - 1; -- }, pos, 128, PoiManager.Occupancy.ANY); -+ }, pos, org.purpurmc.purpur.PurpurConfig.lightningRodRange, PoiManager.Occupancy.ANY); - - return optional.map((blockposition1) -> { - return blockposition1.above(1); -@@ -1051,11 +1101,27 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - if (this.canSleepThroughNights()) { - if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) { - int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); -- MutableComponent ichatmutablecomponent; -+ Component ichatmutablecomponent; - - if (this.sleepStatus.areEnoughSleeping(i)) { -+ // Purpur start ++ // Purpur end - Special mobs naturally spawn + SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); + if (skeletonHorse != null) { +- skeletonHorse.setTrap(true); ++ //skeletonHorse.setTrap(true); // Purpur - Special mobs naturally spawn - moved up + skeletonHorse.setAge(0); + skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + this.addFreshEntity(skeletonHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit +@@ -989,7 +1040,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + pointOfInterestType -> pointOfInterestType.is(PoiTypes.LIGHTNING_ROD), + blockPos -> blockPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockPos.getX(), blockPos.getZ()) - 1, + pos, +- 128, ++ org.purpurmc.purpur.PurpurConfig.lightningRodRange, // Purpur - Make lightning rod range configurable + PoiManager.Occupancy.ANY + ); + return optional.map(blockPos -> blockPos.above(1)); +@@ -1037,8 +1088,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); + Component component; + if (this.sleepStatus.areEnoughSleeping(_int)) { ++ // Purpur start - Customizable sleeping actionbar messages + if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) { -+ ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight)); ++ component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight)); + } else - ichatmutablecomponent = Component.translatable("sleep.skipping_night"); ++ // Purpur end - Customizable sleeping actionbar messages + component = Component.translatable("sleep.skipping_night"); } else { ++ // Purpur start - Customizable sleeping actionbar messages + if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) { -+ ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent, -+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())), -+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(i))))); ++ component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent, ++ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())), ++ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(_int))))); + } else -+ // Purpur end - ichatmutablecomponent = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(i)); ++ // Purpur end - Customizable sleeping actionbar messages + component = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(_int)); } -@@ -1195,6 +1261,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1171,6 +1240,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @VisibleForTesting public void resetWeatherCycle() { // CraftBukkit start -+ if (this.purpurConfig.rainStopsAfterSleep) // Purpur ++ if (this.purpurConfig.rainStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // If we stop due to everyone sleeping we should reset the weather duration to some other random value. // Not that everyone ever manages to get the whole server to sleep at the same time.... -@@ -1202,6 +1269,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1178,6 +1248,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.serverLevelData.setRainTime(0); } // CraftBukkit end -+ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur ++ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // CraftBukkit start // If we stop due to everyone sleeping we should reset the weather duration to some other random value. -@@ -2741,7 +2809,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // Spigot Start +@@ -2656,7 +2727,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // 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 - Fix merchant inventory not closing on entity removal - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { -+ if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur ++ if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur - Allow void trading merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); } // Paper end - Fix merchant inventory not closing on entity removal -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae5b4ee2fa 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -328,6 +328,10 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index e56e930df980613a13f92d771f1036eba82b1d19..b7829a91a7ef79706ec6d90b8b2673fd369b9931 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -392,6 +392,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent public @Nullable String clientBrandName = null; // Paper - Brand support public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event -+ public boolean purpurClient = false; // Purpur -+ private boolean tpsBar = false; // Purpur -+ private boolean compassBar = false; // Purpur -+ private boolean ramBar = false; // Purpur ++ public boolean purpurClient = false; // Purpur - Purpur client support ++ private boolean tpsBar = false; // Purpur - Implement TPSBar ++ private boolean compassBar = false; // Purpur - Add compass command ++ private boolean ramBar = false; // Purpur - Implement rambar commands // Paper start - rewrite chunk system private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; -@@ -690,6 +694,9 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - }); +@@ -560,6 +564,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + if (tag != null) { + BlockPos.CODEC.parse(NbtOps.INSTANCE, tag).resultOrPartial(LOGGER::error).ifPresent(pos -> this.raidOmenPosition = pos); } - -+ if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur -+ if (nbt.contains("Purpur.CompassBar")) { this.compassBar = nbt.getBoolean("Purpur.CompassBar"); } // Purpur -+ if (nbt.contains("Purpur.RamBar")) { this.ramBar = nbt.getBoolean("Purpur.RamBar"); } // Purpur ++ ++ if (compound.contains("Purpur.TPSBar")) { this.tpsBar = compound.getBoolean("Purpur.TPSBar"); } // Purpur - Implement TPSBar ++ if (compound.contains("Purpur.CompassBar")) { this.compassBar = compound.getBoolean("Purpur.CompassBar"); } // Purpur - Add compass command ++ if (compound.contains("Purpur.RamBar")) { this.ramBar = compound.getBoolean("Purpur.RamBar"); } // Purpur - Implement rambar command } @Override -@@ -742,6 +749,9 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple +@@ -604,6 +612,9 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc } - this.saveEnderPearls(nbt); -+ nbt.putBoolean("Purpur.RamBar", this.ramBar); // Purpur -+ nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur -+ nbt.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur + this.saveEnderPearls(compound); ++ compound.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur - Implement TPSBar ++ compound.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur - Add compass command ++ compound.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur - Add rambar command } - private void saveParentVehicle(CompoundTag nbt) { -@@ -1031,6 +1041,15 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple + private void saveParentVehicle(CompoundTag tag) { +@@ -836,6 +847,15 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc this.trackEnteredOrExitedLavaOnVehicle(); this.updatePlayerAttributes(); this.advancements.flushDirty(this); + -+ // Purpur start ++ // Purpur start - Ridables + if (this.level().purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && this.level().getGameTime() % 100 == 0) { // 5 seconds + MobEffectInstance nightVision = this.getEffect(MobEffects.NIGHT_VISION); + if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds + this.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds + } + } -+ // Purpur end ++ // Purpur end - Ridables } private void updatePlayerAttributes() { -@@ -1328,6 +1347,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - })); - PlayerTeam scoreboardteam = this.getTeam(); - -+ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur - if (scoreboardteam != null && scoreboardteam.getDeathMessageVisibility() != Team.Visibility.ALWAYS) { - if (scoreboardteam.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) { - this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent); -@@ -1431,6 +1451,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - if (this.isInvulnerableTo(world, source)) { +@@ -1123,6 +1143,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + ) + ); + Team team = this.getTeam(); ++ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(deathMessage); else // Purpur - Configurable broadcast settings + if (team == null || team.getDeathMessageVisibility() == Team.Visibility.ALWAYS) { + this.server.getPlayerList().broadcastSystemMessage(deathMessage, false); + } else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) { +@@ -1216,6 +1237,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + if (this.isInvulnerableTo(level, damageSource)) { return false; } else { -+ // Purpur start -+ if (source.is(DamageTypeTags.IS_FALL)) { // Purpur ++ // Purpur start - Add boat fall damage config ++ if (damageSource.is(net.minecraft.tags.DamageTypeTags.IS_FALL)) { ++ // Purpur start - Minecart settings and WASD controls + if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.AbstractMinecart && level().purpurConfig.minecartControllable && !level().purpurConfig.minecartControllableFallDamage) { + return false; + } ++ // Purpur end - Minecart settings and WASD controls + if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.Boat && !level().purpurConfig.boatsDoFallDamage) { + return false; + } + } -+ // Purpur end - boolean flag = this.server.isDedicatedServer() && this.isPvpAllowed() && source.is(DamageTypeTags.IS_FALL); - - if (!flag && this.spawnInvulnerableTime > 0 && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { -@@ -1658,6 +1688,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); ++ // Purpur end - Add boat fall damage config + Entity entity = damageSource.getEntity(); + if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false. + !(entity instanceof Player player && !this.canHarmPlayer(player)) +@@ -1441,6 +1474,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); // CraftBukkit end + this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur - Fix stuck in portals - this.setServerLevel(worldserver); - this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); // CraftBukkit - use internal teleport without event + this.setServerLevel(level); + this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); // CraftBukkit - use internal teleport without event this.connection.resetPosition(); -@@ -1767,7 +1798,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - return entitymonster.isPreventingPlayerRest(this.serverLevel(), this); - }); - -- if (!list.isEmpty()) { -+ if (!this.level().purpurConfig.playerSleepNearMonsters && !list.isEmpty()) { // Purpur - return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE); - } +@@ -1558,7 +1592,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + new AABB(vec3.x() - 8.0, vec3.y() - 5.0, vec3.z() - 8.0, vec3.x() + 8.0, vec3.y() + 5.0, vec3.z() + 8.0), + monster -> monster.isPreventingPlayerRest(this.serverLevel(), this) + ); +- if (!entitiesOfClass.isEmpty()) { ++ if (!this.level().purpurConfig.playerSleepNearMonsters && !entitiesOfClass.isEmpty()) { // Purpur - Config to ignore nearby mobs when sleeping + return Either.left(Player.BedSleepingProblem.NOT_SAFE); } -@@ -1807,7 +1838,19 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - }); + } +@@ -1595,7 +1629,19 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + CriteriaTriggers.SLEPT_IN_BED.trigger(this); + }); + if (!this.serverLevel().canSleepThroughNights()) { +- this.displayClientMessage(Component.translatable("sleep.not_possible"), true); ++ // Purpur start - Customizable sleeping actionbar messages ++ Component clientMessage; ++ if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) { ++ clientMessage = null; ++ } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) { ++ clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible)); ++ } else { ++ clientMessage = Component.translatable("sleep.not_possible"); ++ } ++ if (clientMessage != null) { ++ this.displayClientMessage(clientMessage, true); ++ } ++ // Purpur end - Customizable sleeping actionbar messages + } - if (!this.serverLevel().canSleepThroughNights()) { -- this.displayClientMessage(Component.translatable("sleep.not_possible"), true); -+ // Purpur start -+ Component clientMessage; -+ if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) { -+ clientMessage = null; -+ } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) { -+ clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible)); -+ } else { -+ clientMessage = Component.translatable("sleep.not_possible"); -+ } -+ if (clientMessage != null) { -+ this.displayClientMessage(clientMessage, true); -+ } -+ // Purpur end - } - - ((ServerLevel) this.level()).updateSleepingPlayerList(); -@@ -1929,6 +1972,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple + ((ServerLevel)this.level()).updateSleepingPlayerList(); +@@ -1703,6 +1749,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @Override - public void openTextEdit(SignBlockEntity sign, boolean front) { -+ if (level().purpurConfig.signAllowColors) this.connection.send(sign.getTranslatedUpdatePacket(textFilteringEnabled, front)); // Purpur - this.connection.send(new ClientboundBlockUpdatePacket(this.level(), sign.getBlockPos())); - this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front)); + public void openTextEdit(SignBlockEntity signEntity, boolean isFrontText) { ++ if (level().purpurConfig.signAllowColors) this.connection.send(signEntity.getTranslatedUpdatePacket(textFilteringEnabled, isFrontText)); // Purpur - Signs allow color codes + this.connection.send(new ClientboundBlockUpdatePacket(this.level(), signEntity.getBlockPos())); + this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText)); } -@@ -2245,6 +2289,26 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple +@@ -2008,6 +2055,26 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc this.lastSentExp = -1; // CraftBukkit - Added to reset } -+ // Purpur start ++ // Purpur start - Component related conveniences + public void sendActionBarMessage(@Nullable String message) { + if (message != null && !message.isEmpty()) { + sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message)); @@ -1313,16 +1060,16 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + displayClientMessage(message, true); + } + } -+ // Purpur end ++ // Purpur end - Component related conveniences + @Override - public void displayClientMessage(Component message, boolean overlay) { - this.sendSystemMessage(message, overlay); -@@ -2475,6 +2539,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - return new CommandSourceStack(this.commandSource(), this.position(), this.getRotationVector(), this.serverLevel(), this.getPermissionLevel(), this.getName().getString(), this.getDisplayName(), this.server, this); + public void displayClientMessage(Component chatComponent, boolean actionBar) { + this.sendSystemMessage(chatComponent, actionBar); +@@ -2235,6 +2302,20 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + ); } -+ // Purpur Start ++ // Purpur start - Component related conveniences + public void sendMiniMessage(@Nullable String message) { + if (message != null && !message.isEmpty()) { + this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message)); @@ -1334,19 +1081,19 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message)); + } + } -+ // Purpur end ++ // Purpur end - Component related conveniences + - public void sendSystemMessage(Component message) { - this.sendSystemMessage(message, false); + public void sendSystemMessage(Component mesage) { + this.sendSystemMessage(mesage, false); } -@@ -2596,8 +2674,68 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple +@@ -2373,8 +2454,68 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc public void resetLastActionTime() { this.lastActionTime = Util.getMillis(); -+ this.setAfk(false); // Purpur ++ this.setAfk(false); // Purpur - AFK API } -+ // Purpur Start ++ // Purpur start - AFK API + private boolean isAfk = false; + + @Override @@ -1357,7 +1104,7 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + + String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack; + -+ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread()); ++ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !org.bukkit.Bukkit.isPrimaryThread()); + if (!event.callEvent() || event.shouldKick()) { + return; + } @@ -1403,18 +1150,18 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + public boolean canBeCollidedWith() { + return !this.isAfk() && super.canBeCollidedWith(); + } -+ // Purpur End ++ // Purpur end - AFK API + public ServerStatsCounter getStats() { return this.stats; } -@@ -3304,4 +3442,50 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - return (CraftPlayer) super.getBukkitEntity(); +@@ -3078,4 +3219,56 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity(); } // CraftBukkit end + -+ // Purpur start -+ public void teleport(Location to) { ++ // Purpur start - Add option to teleport to spawn if outside world border ++ public void teleport(org.bukkit.Location to) { + this.ejectPassengers(); + this.stopRiding(true); + @@ -1426,14 +1173,16 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); + } + -+ ServerLevel toLevel = ((CraftWorld) to.getWorld()).getHandle(); ++ ServerLevel toLevel = ((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle(); + if (this.level() == toLevel) { + this.connection.teleport(to); + } else { + this.server.getPlayerList().respawn(this, true, RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH, to); + } + } ++ // Purpur end - Add option to teleport to spawn if outside world border + ++ // Purpur start - Implement TPSBar + public boolean tpsBar() { + return this.tpsBar; + } @@ -1441,7 +1190,9 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + public void tpsBar(boolean tpsBar) { + this.tpsBar = tpsBar; + } ++ // Purpur end - Implement TPSBar + ++ // Purpur start - Add compass command + public boolean compassBar() { + return this.compassBar; + } @@ -1449,7 +1200,9 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + public void compassBar(boolean compassBar) { + this.compassBar = compassBar; + } ++ // Purpur end - Add compass command + ++ // Purpur start - Add rambar command + public boolean ramBar() { + return this.ramBar; + } @@ -1457,43 +1210,43 @@ index 7e06a80e6deb80df865f7798588a92b88084411b..61a990d17adaa9f4144d8a1010e764ae + public void ramBar(boolean ramBar) { + this.ramBar = ramBar; + } -+ // Purpur end ++ // Purpur end - Add rambar command } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index a96f859a5d0c6ec692d4627a69f3c9ee49199dbc..88eb3774f688bcff383efa7f113bd0b1b97d8a11 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -403,6 +403,7 @@ public class ServerPlayerGameMode { - } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction +diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java +index 623c069f1fe079e020c6391a3db1a3d95cd3dbf5..a660bad3dfdb442c6aca5eb939ee103e14d37b4b 100644 +--- a/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -351,6 +351,7 @@ public class ServerPlayerGameMode { + } return false; } -+ if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && iblockdata.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) iblockdata.getBlock()).halfBreak(iblockdata, pos, this.player)) return true; // Purpur ++ if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && blockState.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) blockState.getBlock()).halfBreak(blockState, pos, this.player)) return true; // Purpur - Break individual slabs when sneaking } // CraftBukkit end -@@ -523,6 +524,7 @@ public class ServerPlayerGameMode { +@@ -464,6 +465,7 @@ public class ServerPlayerGameMode { public InteractionHand interactHand; public ItemStack interactItemStack; - public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { -+ if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur - BlockPos blockposition = hitResult.getBlockPos(); - BlockState iblockdata = world.getBlockState(blockposition); + public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { ++ if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur - Shift right click to use exp for mending + BlockPos blockPos = hitResult.getBlockPos(); + BlockState blockState = level.getBlockState(blockPos); boolean cancelledBlock = false; -@@ -583,7 +585,7 @@ public class ServerPlayerGameMode { - ItemStack itemstack1 = stack.copy(); - InteractionResult enuminteractionresult; - +@@ -506,7 +508,7 @@ public class ServerPlayerGameMode { + boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty(); + boolean flag1 = player.isSecondaryUseActive() && flag; + ItemStack itemStack = stack.copy(); - if (!flag1) { -+ if (!flag1 || (player.level().purpurConfig.composterBulkProcess && iblockdata.is(Blocks.COMPOSTER))) { // Purpur - InteractionResult enuminteractionresult1 = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); - - if (enuminteractionresult1.consumesAction()) { -@@ -631,4 +633,18 @@ public class ServerPlayerGameMode { - public void setLevel(ServerLevel world) { - this.level = world; ++ if (!flag1 || (player.level().purpurConfig.composterBulkProcess && blockState.is(net.minecraft.world.level.block.Blocks.COMPOSTER))) { // Purpur - Sneak to bulk process composter + InteractionResult interactionResult = blockState.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult); + if (interactionResult.consumesAction()) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockPos, itemStack); +@@ -552,4 +554,18 @@ public class ServerPlayerGameMode { + public void setLevel(ServerLevel serverLevel) { + this.level = serverLevel; } + -+ // Purpur start ++ // Purpur start - Shift right click to use exp for mending + public boolean shiftClickMended(ItemStack itemstack) { + if (this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) { + int points = Math.min(this.player.totalExperience, this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints); @@ -1505,177 +1258,165 @@ index a96f859a5d0c6ec692d4627a69f3c9ee49199dbc..88eb3774f688bcff383efa7f113bd0b1 + } + return false; + } -+ // Purpur end ++ // Purpur end - Shift right click to use exp for mending } -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 92815589da3c2a1cb768ac8081660c9c2ccb2b14..32ab2e0f7bae7d0a54cebdd46c95e574c41ad1e3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -88,6 +88,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index ea34bb4913e7357f5b76a64443f7e744abdf7b5e..de115ee71fa240440b54c553e0d3ddaf4c0dfca0 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -54,6 +54,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack private static final long KEEPALIVE_LIMIT = KEEPALIVE_LIMIT_IN_SECONDS * 1000; // Gale end - Purpur - send multiple keep-alive packets - protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support -+ protected static final ResourceLocation PURPUR_CLIENT = ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur + protected static final net.minecraft.resources.ResourceLocation MINECRAFT_BRAND = net.minecraft.resources.ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support ++ protected static final net.minecraft.resources.ResourceLocation PURPUR_CLIENT = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur - Purpur client support - public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit - this.server = minecraftserver; -@@ -193,6 +194,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); - this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit + this.server = server; +@@ -173,6 +174,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex); + this.disconnect(Component.literal("Invalid payload REGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause } -+ // Purpur start ++ // Purpur start - Purpur client support + } else if (identifier.equals(PURPUR_CLIENT)) { + try { + player.purpurClient = true; + } catch (Exception ignore) { + } -+ // Purpur end ++ // Purpur end - Purpur client support } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { try { String channels = payload.toString(com.google.common.base.Charsets.UTF_8); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 3455b7f21fee42d21182b8b5f8af7a9a238f646c..eb8ea4df3996a22d46a6fdc1df2fa1f26aa95aec 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -342,6 +342,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private boolean justTeleported = false; - // CraftBukkit end +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f5a36446bc1fd0f5fbbfccb5d331b916d466ea55..1a9664a3b00dcb8171145c3bb3f3f704272aaa73 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -326,6 +326,20 @@ public class ServerGamePacketListenerImpl + this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat + } -+ // Purpur start -+ private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() ++ // Purpur start - AFK API ++ private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES) + .build( + new com.google.common.cache.CacheLoader<>() { + @Override -+ public Boolean load(CraftPlayer player) { ++ public Boolean load(org.bukkit.craftbukkit.entity.CraftPlayer player) { + return player.hasPermission("purpur.bypassIdleKick"); + } + } + ); -+ // Purpur end ++ // Purpur end - AFK API + @Override public void tick() { if (this.ackBlockChangesUpTo > -1) { -@@ -398,6 +412,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.recipeSpamPackets.tick(); // Paper - auto recipe limit - this.dropSpamThrottler.tick(); - if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits -+ // Purpur start +@@ -384,6 +398,12 @@ public class ServerGamePacketListenerImpl + if (this.player.getLastActionTime() > 0L + && this.server.getPlayerIdleTimeout() > 0 + && Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits ++ // Purpur start - AFK API + this.player.setAfk(true); + if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) { + return; + } -+ // Purpur end ++ // Purpur end - AFK API this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 - this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause } -@@ -663,6 +683,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -629,6 +649,8 @@ public class ServerGamePacketListenerImpl this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); -+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur ++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API + Location oldTo = to.clone(); PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.cserver.getPluginManager().callEvent(event); -@@ -739,6 +761,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -709,6 +731,7 @@ public class ServerGamePacketListenerImpl PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (packet.getId() == this.awaitingTeleport) { if (this.awaitingPositionFromClient == null) { -+ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur - this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause ++ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur - Add more logger output for invalid movement kicks + this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause return; } -@@ -1185,6 +1208,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1174,6 +1197,10 @@ public class ServerGamePacketListenerImpl final int maxBookPageSize = pageMax.intValue(); final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D); long byteAllowed = maxBookPageSize; -+ // Purpur start ++ // Purpur start - PlayerBookTooLargeEvent + int slot = packet.slot(); + ItemStack itemstack = Inventory.isHotbarSlot(slot) || slot == Inventory.SLOT_OFFHAND ? this.player.getInventory().getItem(slot) : ItemStack.EMPTY; -+ // Purpur end ++ // Purpur end - PlayerBookTooLargeEvent for (final String page : pageList) { final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; byteTotal += byteLength; -@@ -1209,7 +1236,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1198,7 +1225,8 @@ public class ServerGamePacketListenerImpl } if (byteTotal > byteAllowed) { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); -+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur ++ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - PlayerBookTooLargeEvent this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect return; } -@@ -1231,10 +1259,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - Objects.requireNonNull(list); +@@ -1217,31 +1245,45 @@ public class ServerGamePacketListenerImpl + Optional optional = packet.title(); optional.ifPresent(list::add); list.addAll(packet.pages()); -+ // Purpur start ++ // Purpur start - Allow color codes in books + boolean hasEditPerm = getCraftPlayer().hasPermission("purpur.book.color.edit"); + boolean hasSignPerm = hasEditPerm || getCraftPlayer().hasPermission("purpur.book.color.sign"); -+ // Purpur end - Consumer> consumer = optional.isPresent() ? (list1) -> { -- this.signBook((FilteredText) list1.get(0), list1.subList(1, list1.size()), i); -+ this.signBook((FilteredText) list1.get(0), list1.subList(1, list1.size()), i, hasSignPerm); // Purpur - } : (list1) -> { -- this.updateBookContents(list1, i); -+ this.updateBookContents(list1, i, hasEditPerm); // Purpur - }; - - this.filterTextPacket((List) list).thenAcceptAsync(consumer, this.server); -@@ -1242,13 +1274,18 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - private void updateBookContents(List pages, int slotId) { -+ // Purpur start -+ updateBookContents(pages, slotId, false); -+ } -+ private void updateBookContents(List pages, int slotId, boolean hasPerm) { -+ // Purpur end - // CraftBukkit start - ItemStack handItem = this.player.getInventory().getItem(slotId); - ItemStack itemstack = handItem.copy(); - // CraftBukkit end - - if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { -- List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); -+ List> list1 = pages.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur - - itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); - this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) -@@ -1256,6 +1293,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - private void signBook(FilteredText title, List pages, int slotId) { -+ // Purpur start -+ signBook(title, pages, slotId, false); -+ } -+ private void signBook(FilteredText title, List pages, int slotId, boolean hasPerm) { -+ // Purpur end - ItemStack itemstack = this.player.getInventory().getItem(slotId); - - if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { -@@ -1263,10 +1305,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - itemstack1.remove(DataComponents.WRITABLE_BOOK_CONTENT); - List> list1 = (List>) (List) pages.stream().map((filteredtext1) -> { // CraftBukkit - decompile error -- return this.filterableFromOutgoing(filteredtext1).map(Component::literal); -+ return this.filterableFromOutgoing(filteredtext1).map(s -> hexColor(s, hasPerm)); // Purpur - }).toList(); - -- itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list1, true)); -+ itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title).map(s -> color(s, hasPerm)), this.player.getName().getString(), 0, list1, true)); // Purpur - CraftEventFactory.handleEditBookEvent(this.player, slotId, itemstack, itemstack1); // CraftBukkit - this.player.getInventory().setItem(slotId, itemstack); // CraftBukkit - event factory updates the hand book ++ // Purpur end - Allow color codes in books + Consumer> consumer = optional.isPresent() +- ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot) +- : texts -> this.updateBookContents(texts, slot); ++ ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot, hasSignPerm) // Purpur - Allow color codes in books ++ : texts -> this.updateBookContents(texts, slot, hasEditPerm); // Purpur - Allow color codes in books + this.filterTextPacket(list).thenAcceptAsync(consumer, this.server); } -@@ -1276,6 +1318,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return this.player.isTextFilteringEnabled() ? Filterable.passThrough(message.filteredOrEmpty()) : Filterable.from(message); } -+ // Purpur start + private void updateBookContents(List pages, int index) { ++ // Purpur start - Allow color codes in books ++ updateBookContents(pages, index, false); ++ } ++ private void updateBookContents(List pages, int index, boolean hasPerm) { ++ // Purpur end - Allow color codes in books + // CraftBukkit start + ItemStack handItem = this.player.getInventory().getItem(index); + ItemStack item = handItem.copy(); + // CraftBukkit end + if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) { +- List> list = pages.stream().map(this::filterableFromOutgoing).toList(); ++ List> list = pages.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur - Allow color codes in books + item.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list)); + this.player.getInventory().setItem(index, CraftEventFactory.handleEditBookEvent(this.player, index, handItem, item)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + } + + private void signBook(FilteredText title, List pages, int index) { ++ // Purpur start - Allow color codes in books ++ signBook(title, pages, index, false); ++ } ++ private void signBook(FilteredText title, List pages, int index, boolean hasPerm) { ++ // Purpur end - Allow color codes in books + ItemStack item = this.player.getInventory().getItem(index); + if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) { + ItemStack itemStack = item.transmuteCopy(Items.WRITTEN_BOOK); + itemStack.remove(DataComponents.WRITABLE_BOOK_CONTENT); +- List> list = pages.stream().map(filteredText -> this.filterableFromOutgoing(filteredText).map(Component::literal)).toList(); ++ List> list = pages.stream().map((filteredText) -> this.filterableFromOutgoing(filteredText).map(s -> hexColor(s, hasPerm))).toList(); // Purpur - Allow color codes in books + itemStack.set( + DataComponents.WRITTEN_BOOK_CONTENT, + new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list, true) +@@ -1255,6 +1297,16 @@ public class ServerGamePacketListenerImpl + return this.player.isTextFilteringEnabled() ? Filterable.passThrough(filteredText.filteredOrEmpty()) : Filterable.from(filteredText); + } + ++ // Purpur start - Allow color codes in books + private Component hexColor(String str, boolean hasPerm) { + return hasPerm ? PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().deserialize(str)) : Component.literal(str); + } @@ -1683,147 +1424,150 @@ index 3455b7f21fee42d21182b8b5f8af7a9a238f646c..eb8ea4df3996a22d46a6fdc1df2fa1f2 + private String color(String str, boolean hasPerm) { + return hasPerm ? org.bukkit.ChatColor.color(str, false) : str; + } -+ // Purpur end ++ // Purpur end - Allow color codes in books + @Override public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -@@ -1325,7 +1377,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1290,7 +1342,15 @@ public class ServerGamePacketListenerImpl @Override public void handleMovePlayer(ServerboundMovePlayerPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -- if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { -+ // Purpur start -+ boolean invalidX = Double.isNaN(packet.getX(0.0D)); -+ boolean invalidY = Double.isNaN(packet.getY(0.0D)); -+ boolean invalidZ = Double.isNaN(packet.getZ(0.0D)); +- if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) { ++ // Purpur start - Add more logger output for invalid movement kicks ++ boolean invalidX = Double.isNaN(packet.getX(0.0)); ++ boolean invalidY = Double.isNaN(packet.getY(0.0)); ++ boolean invalidZ = Double.isNaN(packet.getZ(0.0)); + boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F)); + boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F)); + if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) { -+ ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch)); // Purpur -+ // Purpur end - this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause ++ ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch)); ++ // Purpur end - Add more logger output for invalid movement kicks + this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause } else { - ServerLevel worldserver = this.player.serverLevel(); -@@ -1505,7 +1565,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + ServerLevel serverLevel = this.player.serverLevel(); +@@ -1465,7 +1525,7 @@ public class ServerGamePacketListenerImpl movedWrongly = true; if (event.getLogWarning()) - // Paper end -- ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); -+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur + // Paper end +- LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); ++ LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), verticalDelta); // Purpur - AFK API } // Paper } -@@ -1573,6 +1633,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1531,6 +1591,8 @@ public class ServerGamePacketListenerImpl this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); -+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur ++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API + Location oldTo = to.clone(); PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.cserver.getPluginManager().callEvent(event); -@@ -1618,6 +1680,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1587,6 +1649,13 @@ public class ServerGamePacketListenerImpl this.player.tryResetCurrentImpulseContext(); } -+ // Purpur Start -+ if (this.player.level().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.level().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.level().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissor(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissor(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) { -+ this.player.hurt(this.player.damageSources().scissors(), (float) this.player.level().purpurConfig.scissorsRunningDamage); ++ // Purpur start - Dont run with scissors! ++ if (this.player.serverLevel().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.serverLevel().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.serverLevel().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissors(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissors(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) { ++ this.player.hurtServer(this.player.serverLevel(), this.player.damageSources().scissors(), (float) this.player.serverLevel().purpurConfig.scissorsRunningDamage); + if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors); + } -+ // Purpur End ++ // Purpur end - Dont run with scissors! + - this.player.checkMovementStatistics(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5); + this.player.checkMovementStatistics(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z); this.lastGoodX = this.player.getX(); this.lastGoodY = this.player.getY(); -@@ -1657,6 +1726,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1635,6 +1704,17 @@ public class ServerGamePacketListenerImpl } } -+ // Purpur start -+ public boolean isScissor(ItemStack stack) { ++ // Purpur start - Dont run with scissors! ++ public boolean isScissors(ItemStack stack) { + if (!stack.is(Items.SHEARS)) return false; -+ net.minecraft.world.item.component.CustomModelData customModelData = stack.get(net.minecraft.core.component.DataComponents.CUSTOM_MODEL_DATA); -+ return customModelData == null || customModelData.value() == 0; ++ ++ ResourceLocation itemModelReference = stack.get(net.minecraft.core.component.DataComponents.ITEM_MODEL); ++ if (itemModelReference != null && itemModelReference.equals(this.player.serverLevel().purpurConfig.dontRunWithScissorsItemModelReference)) return true; ++ ++ return stack.getOrDefault(DataComponents.CUSTOM_MODEL_DATA, net.minecraft.world.item.component.CustomModelData.EMPTY).equals(net.minecraft.world.item.component.CustomModelData.EMPTY); + } -+ // Purpur end ++ // Purpur end - Dont run with scissors! + // Paper start - optimise out extra getCubes - private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { + private boolean hasNewCollision(final ServerLevel level, final Entity entity, final AABB oldBox, final AABB newBox) { final List collisionsBB = new java.util.ArrayList<>(); -@@ -2030,6 +2107,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1999,6 +2079,7 @@ public class ServerGamePacketListenerImpl - boolean cancelled; - if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) { -+ if (this.player.gameMode.shiftClickMended(itemstack)) return; // Purpur - org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); - cancelled = event.useItemInHand() == Event.Result.DENY; - } else { -@@ -2813,6 +2891,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - AABB axisalignedbb = entity.getBoundingBox(); + boolean cancelled; + if (hitResult == null || hitResult.getType() != HitResult.Type.BLOCK) { ++ if (this.player.gameMode.shiftClickMended(itemInHand)) return; // Purpur - Shift right click to use exp for mending + org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemInHand, hand); + cancelled = event.useItemInHand() == Event.Result.DENY; + } else { +@@ -2739,6 +2820,7 @@ public class ServerGamePacketListenerImpl - if (this.player.canInteractWithEntity(axisalignedbb, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range -+ if (entity instanceof Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur - packet.dispatch(new ServerboundInteractPacket.Handler() { - private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit - ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); -@@ -2826,6 +2905,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + AABB boundingBox = target.getBoundingBox(); + if (this.player.canInteractWithEntity(boundingBox, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0))) { // Paper - configurable lenience value for interact range ++ if (target instanceof net.minecraft.world.entity.Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan + packet.dispatch( + new ServerboundInteractPacket.Handler() { + private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction, PlayerInteractEntityEvent event) { // CraftBukkit +@@ -2751,6 +2833,8 @@ public class ServerGamePacketListenerImpl - ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); + ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); -+ player.processClick(enumhand); // Purpur ++ player.processClick(hand); // Purpur - Ridables + - // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a - if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { - entity.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 70534d9d58a209c0c8fd6f4d3ba65773f420f559..d5acf191e3f34c84b3711c0ba77e7cb001aee507 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -335,7 +335,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a + if ((target instanceof Bucketable && target instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { + target.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index c3c85970a5d4bf85fa138a90a33a69f0336334a8..634933a6c98a0043cfe3ff4122dfc53e3c20a192 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -318,7 +318,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!"); - ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot + ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot } else { - ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); -+ ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur - ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1); ++ ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur - Config for unverified username message + ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", string1); } - } catch (AuthenticationUnavailableException authenticationunavailableexception) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 99521abc925e0c7754d9e5aaad52e4fa70383946..3d6a728c6f9542109e7466d590bb8f015b8c4ac1 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -430,6 +430,7 @@ public abstract class PlayerList { + } catch (AuthenticationUnavailableException var4) { +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 61d7d50d2a2e566d2f955b22c95908c6e4ef5274..b4f2b794ca0c6e04da0355e02c19493c892ebccf 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -407,6 +407,7 @@ public abstract class PlayerList { scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); } // Paper end - Configurable player collision -+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur ++ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur - Implement TPSBar if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable - PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); + PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), loggableAddress, player.getId(), serverLevel.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); // Gale start - JettPack - make logging login location configurable -@@ -557,6 +558,7 @@ public abstract class PlayerList { +@@ -518,6 +519,7 @@ public abstract class PlayerList { } - public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { + public net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) { // Paper end - Fix kick event leave message not being sent -+ org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur - ServerLevel worldserver = entityplayer.serverLevel(); - - entityplayer.awardStat(Stats.LEAVE_GAME); -@@ -727,7 +729,7 @@ public abstract class PlayerList { - event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure - } else { - // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; -- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { -+ if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur - event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure ++ org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar + ServerLevel serverLevel = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it +@@ -683,7 +685,7 @@ public abstract class PlayerList { + // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile) + // ? Component.translatable("multiplayer.disconnect.server_full") + // : null; +- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)) { ++ if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission + event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure } } -@@ -1050,6 +1052,20 @@ public abstract class PlayerList { +@@ -982,6 +984,20 @@ public abstract class PlayerList { + } } - // CraftBukkit end -+ // Purpur Start ++ // Purpur start - Component related conveniences + public void broadcastMiniMessage(@Nullable String message, boolean overlay) { + if (message != null && !message.isEmpty()) { + this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay); @@ -1835,25 +1579,25 @@ index 99521abc925e0c7754d9e5aaad52e4fa70383946..3d6a728c6f9542109e7466d590bb8f01 + this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay); + } + } -+ // Purpur end ++ // Purpur end - Component related conveniences + public void broadcastAll(Packet packet, ResourceKey dimension) { - Iterator iterator = this.players.iterator(); - -@@ -1153,6 +1169,7 @@ public abstract class PlayerList { + for (ServerPlayer serverPlayer : this.players) { + if (serverPlayer.level().dimension() == dimension) { +@@ -1065,6 +1081,7 @@ public abstract class PlayerList { } else { - b0 = (byte) (24 + permissionLevel); + b = (byte)(24 + permLevel); } -+ if (b0 < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b0 = 28; // Purpur ++ if (b < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b = 28; // Purpur - Add permission for F3+N debug - player.connection.send(new ClientboundEntityEventPacket(player, b0)); + player.connection.send(new ClientboundEntityEventPacket(player, b)); } -@@ -1161,6 +1178,27 @@ public abstract class PlayerList { +@@ -1073,6 +1090,27 @@ public abstract class PlayerList { player.getBukkitEntity().recalculatePermissions(); // CraftBukkit this.server.getCommands().sendCommands(player); } // Paper - Add sendOpLevel API + -+ // Purpur start ++ // Purpur start - Barrels and enderchests 6 rows + if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity(); + if (bukkit.hasPermission("purpur.enderchest.rows.six")) { @@ -1872,111 +1616,113 @@ index 99521abc925e0c7754d9e5aaad52e4fa70383946..3d6a728c6f9542109e7466d590bb8f01 + } else { + player.sixRowEnderchestSlotCount = -1; + } -+ //Purpur end ++ // Purpur end - Barrels and enderchests 6 rows } public boolean isWhiteListed(GameProfile profile) { -diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java -index 823efad652d8ff9e96b99375b102fef6f017716e..caa8a69bde0c212c36dd990a67836ac2f95548c0 100644 ---- a/src/main/java/net/minecraft/server/players/SleepStatus.java -+++ b/src/main/java/net/minecraft/server/players/SleepStatus.java -@@ -19,7 +19,7 @@ public class SleepStatus { +diff --git a/net/minecraft/server/players/SleepStatus.java b/net/minecraft/server/players/SleepStatus.java +index 2a7ae521654ad5c9f392baa5562e64bb71b13097..3a3e6992563236141db687084aeec9684437a7db 100644 +--- a/net/minecraft/server/players/SleepStatus.java ++++ b/net/minecraft/server/players/SleepStatus.java +@@ -15,7 +15,7 @@ public class SleepStatus { - public boolean areEnoughDeepSleeping(int percentage, List players) { + public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List sleepingPlayers) { // CraftBukkit start -- int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count(); -+ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur - boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); - - return anyDeepSleep && j >= this.sleepersNeeded(percentage); -@@ -52,7 +52,7 @@ public class SleepStatus { - - if (!entityplayer.isSpectator()) { - ++this.activePlayers; -- if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit -+ if ((entityplayer.isSleeping() || entityplayer.fauxSleeping) || (entityplayer.level().purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur - ++this.sleepingPlayers; +- int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping).count(); ++ int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())).count(); // Purpur - AFK API + boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough); + return anyDeepSleep && i >= this.sleepersNeeded(requiredSleepPercentage); + // CraftBukkit end +@@ -43,7 +43,7 @@ public class SleepStatus { + for (ServerPlayer serverPlayer : players) { + if (!serverPlayer.isSpectator()) { + this.activePlayers++; +- if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping) { // CraftBukkit ++ if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping || (serverPlayer.level().purpurConfig.idleTimeoutCountAsSleeping && serverPlayer.isAfk())) { // CraftBukkit // Purpur - AFK API + this.sleepingPlayers++; } // CraftBukkit start -diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java -index 6c33002dc8bbb3759c3156302ab7d1f26ce5e8ee..c89fc375aff548a2b03eaf4da3b6a075012df012 100644 ---- a/src/main/java/net/minecraft/util/StringUtil.java -+++ b/src/main/java/net/minecraft/util/StringUtil.java -@@ -69,6 +69,7 @@ public class StringUtil { +diff --git a/net/minecraft/util/StringUtil.java b/net/minecraft/util/StringUtil.java +index 77947e6915facee44588943fcd3e5b513de37e77..c3a99fe7b49858bc0ca9a7f800b0db40465f6901 100644 +--- a/net/minecraft/util/StringUtil.java ++++ b/net/minecraft/util/StringUtil.java +@@ -87,6 +87,7 @@ public class StringUtil { // Paper start - Username validation public static boolean isReasonablePlayerName(final String name) { -+ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur ++ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur - Configurable valid characters for usernames if (name.isEmpty() || name.length() > 16) { return false; } -diff --git a/src/main/java/net/minecraft/world/damagesource/CombatRules.java b/src/main/java/net/minecraft/world/damagesource/CombatRules.java -index 064c1e33f3feee77837bb57887877ae1ca39548d..ffd009bca3fdbfd0b14df78072ef8d472a57cd65 100644 ---- a/src/main/java/net/minecraft/world/damagesource/CombatRules.java -+++ b/src/main/java/net/minecraft/world/damagesource/CombatRules.java +diff --git a/net/minecraft/world/damagesource/CombatRules.java b/net/minecraft/world/damagesource/CombatRules.java +index d5524038314591a10c9f08a68e2ac91f6079a897..bf82de45bf98e8605a1fdb69803f75f471c4af43 100644 +--- a/net/minecraft/world/damagesource/CombatRules.java ++++ b/net/minecraft/world/damagesource/CombatRules.java @@ -15,7 +15,7 @@ public class CombatRules { - public static float getDamageAfterAbsorb(LivingEntity armorWearer, float damageAmount, DamageSource damageSource, float armor, float armorToughness) { + public static float getDamageAfterAbsorb(LivingEntity entity, float damage, DamageSource damageSource, float armorValue, float armorToughness) { float f = 2.0F + armorToughness / 4.0F; -- float g = Mth.clamp(armor - damageAmount / f, armor * 0.2F, 20.0F); -+ float g = Mth.clamp(armor - damageAmount / f, armor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - float h = g / 25.0F; - ItemStack itemStack = damageSource.getWeaponItem(); - float i; +- float f1 = Mth.clamp(armorValue - damage / f, armorValue * 0.2F, 20.0F); ++ float f1 = Mth.clamp(armorValue - damage / f, armorValue * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config + float f2 = f1 / 25.0F; + ItemStack weaponItem = damageSource.getWeaponItem(); + float f3; @@ -30,7 +30,7 @@ public class CombatRules { } - public static float getDamageAfterMagicAbsorb(float damageDealt, float protection) { -- float f = Mth.clamp(protection, 0.0F, 20.0F); -+ float f = Mth.clamp(protection, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - return damageDealt * (1.0F - f / 25.0F); + public static float getDamageAfterMagicAbsorb(float damage, float enchantModifiers) { +- float f = Mth.clamp(enchantModifiers, 0.0F, 20.0F); ++ float f = Mth.clamp(enchantModifiers, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config + return damage * (1.0F - f / 25.0F); } } -diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -index 99a7e9eb75231c15bd8bb24fbb4e296bc9fdedff..4fb025a63628eb60509d90b680922a0220104bcb 100644 ---- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -+++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +diff --git a/net/minecraft/world/damagesource/CombatTracker.java b/net/minecraft/world/damagesource/CombatTracker.java +index d3de87eaf0eb84af77165391c7b94085d425f21d..edaa6f66f33b6a9bfb4862ec5557080bf702f4bd 100644 +--- a/net/minecraft/world/damagesource/CombatTracker.java ++++ b/net/minecraft/world/damagesource/CombatTracker.java @@ -54,7 +54,7 @@ public class CombatTracker { - private Component getMessageForAssistedFall(Entity attacker, Component attackerDisplayName, String itemDeathTranslationKey, String deathTranslationKey) { - ItemStack itemStack = attacker instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY; + private Component getMessageForAssistedFall(Entity entity, Component entityDisplayName, String hasWeaponTranslationKey, String noWeaponTranslationKey) { + ItemStack itemStack = entity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY; - return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME) -+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) - : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName); ++ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages + ? Component.translatable(hasWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName, itemStack.getDisplayName()) + : Component.translatable(noWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName); } -@@ -98,6 +98,13 @@ public class CombatTracker { +@@ -98,6 +98,15 @@ public class CombatTracker { Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable(string + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE); return Component.translatable(string + ".message", this.mob.getDisplayName(), component); } else { -+ // Purpur start ++ // Purpur start - Dont run with scissors! + if (damageSource.isScissors()) { + return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob); ++ // Purpur start - Stonecutter damage + } else if (damageSource.isStonecutter()) { + return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob); ++ // Purpur end - Stonecutter damage + } -+ // Purpur end ++ // Purpur end - Dont run with scissors! return damageSource.getLocalizedDeathMessage(this.mob); } } -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index 379b36944ddb149e2f48221aa39ad090bbd2faeb..91a5be2eaedb0fa94580de60a9625f94ae724235 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -29,6 +29,8 @@ public class DamageSource { +diff --git a/net/minecraft/world/damagesource/DamageSource.java b/net/minecraft/world/damagesource/DamageSource.java +index 3d8d7460ab31e9183e26ada76ad05378f8bb925d..ee2ef8ba44d699647216ab67d3b1ad76a63a9704 100644 +--- a/net/minecraft/world/damagesource/DamageSource.java ++++ b/net/minecraft/world/damagesource/DamageSource.java +@@ -28,6 +28,8 @@ public class DamageSource { private boolean sweep = false; private boolean melting = false; private boolean poison = false; -+ private boolean scissors = false; // Purpur -+ private boolean stonecutter = false; // Purpur ++ private boolean scissors = false; // Purpur - Dont run with scissors! ++ private boolean stonecutter = false; // Purpur - Stonecutter damage @Nullable private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API -@@ -59,6 +61,26 @@ public class DamageSource { +@@ -58,6 +60,27 @@ public class DamageSource { return this.poison; } -+ // Purpur start ++ // Purpur start - Dont run with scissors! + public DamageSource scissors() { + this.scissors = true; + return this; @@ -1985,7 +1731,8 @@ index 379b36944ddb149e2f48221aa39ad090bbd2faeb..91a5be2eaedb0fa94580de60a9625f94 + public boolean isScissors() { + return this.scissors; + } -+ ++ // Purpur end - Dont run with scissors! ++ // Purpur start - - Stonecutter damage + public DamageSource stonecutter() { + this.stonecutter = true; + return this; @@ -1994,201 +1741,213 @@ index 379b36944ddb149e2f48221aa39ad090bbd2faeb..91a5be2eaedb0fa94580de60a9625f94 + public boolean isStonecutter() { + return this.stonecutter; + } -+ // Purpur end ++ // Purpur end - Stonecutter damage + // Paper start - fix DamageSource API @Nullable public Entity getCustomEventDamager() { -@@ -117,6 +139,8 @@ public class DamageSource { +@@ -118,6 +141,8 @@ public class DamageSource { damageSource.sweep = this.isSweep(); damageSource.poison = this.isPoison(); damageSource.melting = this.isMelting(); -+ damageSource.scissors = this.isScissors(); // Purpur -+ damageSource.stonecutter = this.isStonecutter(); // Purpur ++ damageSource.scissors = this.isScissors(); // Purpur - Dont run with scissors! ++ damageSource.stonecutter = this.isStonecutter(); // Purpur - Stonecutter damage return damageSource; } // CraftBukkit end -@@ -194,10 +218,19 @@ public class DamageSource { - - ItemStack itemstack1 = itemstack; - -- return !itemstack1.isEmpty() && itemstack1.has(DataComponents.CUSTOM_NAME) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); -+ return !itemstack1.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemstack1.has(DataComponents.CUSTOM_NAME)) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); +@@ -184,12 +209,21 @@ public class DamageSource { + } else { + Component component = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName(); + ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 ? livingEntity1.getMainHandItem() : ItemStack.EMPTY; +- return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME) ++ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages + ? Component.translatable(string + ".item", livingEntity.getDisplayName(), component, itemStack.getDisplayName()) + : Component.translatable(string, livingEntity.getDisplayName(), component); } } -+ // Purpur start ++ // Purpur start - Component related conveniences + public Component getLocalizedDeathMessage(String str, LivingEntity entity) { + net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName()); + net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name); + net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template); + return io.papermc.paper.adventure.PaperAdventure.asVanilla(component); + } -+ // Purpur end ++ // Purpur end - Component related conveniences + public String getMsgId() { return this.type().msgId(); } -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -index be87cb3cfa15a7d889118cdc4b87232e30749023..d343fd5c9f31073f1b3a0f91b8ea61a67077cc12 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -@@ -46,11 +46,15 @@ public class DamageSources { +diff --git a/net/minecraft/world/damagesource/DamageSources.java b/net/minecraft/world/damagesource/DamageSources.java +index b1054b6b2bff568a8805fc0f610c431f3f349a04..a6660ba22847911c661bb7a608a96e1d059c4ae2 100644 +--- a/net/minecraft/world/damagesource/DamageSources.java ++++ b/net/minecraft/world/damagesource/DamageSources.java +@@ -45,11 +45,15 @@ public class DamageSources { // CraftBukkit start private final DamageSource melting; private final DamageSource poison; -+ private final DamageSource scissors; // Purpur -+ private final DamageSource stonecutter; // Purpur ++ private final DamageSource scissors; // Purpur - Dont run with scissors! ++ private final DamageSource stonecutter; // Purpur - Stonecutter damage - public DamageSources(RegistryAccess registryManager) { - this.damageTypes = registryManager.lookupOrThrow(Registries.DAMAGE_TYPE); + public DamageSources(RegistryAccess registry) { + this.damageTypes = registry.lookupOrThrow(Registries.DAMAGE_TYPE); this.melting = this.source(DamageTypes.ON_FIRE).melting(); this.poison = this.source(DamageTypes.MAGIC).poison(); -+ this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur -+ this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur ++ this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur - Dont run with scissors! ++ this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur - Stonecutter damage // CraftBukkit end this.inFire = this.source(DamageTypes.IN_FIRE); this.campfire = this.source(DamageTypes.CAMPFIRE); -@@ -101,6 +105,15 @@ public class DamageSources { +@@ -100,6 +104,17 @@ public class DamageSources { } // CraftBukkit end -+ // Purpur start ++ // Purpur start - Dont run with scissors! + public DamageSource scissors() { + return this.scissors; + } ++ // Purpur end - Dont run with scissors! ++ ++ // Purpur start - Stonecutter damage + public DamageSource stonecutter() { + return this.stonecutter; + } -+ // Purpur end -+ ++ // Purpur end - Stonecutter damage public DamageSource inFire() { return this.inFire; } -diff --git a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java -index 11d1ee8fae7670f02cb3f5d57f4774dbde77f48c..88cf1353892a7ead4e0f16822216b151726ac0e4 100644 ---- a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java -@@ -13,7 +13,7 @@ class HungerMobEffect extends MobEffect { +diff --git a/net/minecraft/world/effect/HungerMobEffect.java b/net/minecraft/world/effect/HungerMobEffect.java +index 0890694ae96b6cd60079c34066e7a6e288f038e8..6c0e6bd2a171edc57dec71af178764454de73313 100644 +--- a/net/minecraft/world/effect/HungerMobEffect.java ++++ b/net/minecraft/world/effect/HungerMobEffect.java +@@ -12,7 +12,7 @@ class HungerMobEffect extends MobEffect { @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { - if (entity instanceof Player entityhuman) { -- entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent -+ entityhuman.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur + public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { + if (entity instanceof Player player) { +- player.causeFoodExhaustion(0.005F * (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent ++ player.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur - Config MobEffect by world } return true; -diff --git a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java -index 83c6d17f75c3f0b531bdfd5b5f9bc2a5b3eb7a2c..df43c007e7b9fee58b2cd0892b966ce88cb1f890 100644 ---- a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java -@@ -11,8 +11,8 @@ class PoisonMobEffect extends MobEffect { +diff --git a/net/minecraft/world/effect/PoisonMobEffect.java b/net/minecraft/world/effect/PoisonMobEffect.java +index 522e11a98ccd9186e7b00f6d7e28516cf33b830c..a33402b06ce99767fa0a5d4bf6881077bd1bd107 100644 +--- a/net/minecraft/world/effect/PoisonMobEffect.java ++++ b/net/minecraft/world/effect/PoisonMobEffect.java +@@ -12,8 +12,8 @@ public class PoisonMobEffect extends MobEffect { @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { - if (entity.getHealth() > 1.0F) { -- entity.hurtServer(world, entity.damageSources().poison(), 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON +- entity.hurtServer(level, entity.damageSources().poison(), 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON + if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur -+ entity.hurtServer(world, entity.damageSources().poison(), entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur ++ entity.hurtServer(level, entity.damageSources().poison(), entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur - Config MobEffect by world } return true; -diff --git a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java -index b43e573e91d1f1b407774bb2d60ee5f099f171e7..25aa932fc03eeebc5aabca6c5eae0cbfc8ad8396 100644 ---- a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java -@@ -12,7 +12,7 @@ class RegenerationMobEffect extends MobEffect { +diff --git a/net/minecraft/world/effect/RegenerationMobEffect.java b/net/minecraft/world/effect/RegenerationMobEffect.java +index 76cffa4d4d18d6c04749d941dbdf5eb60aed4095..81481267a1577721dcc405f39a4c350bd59ac9a2 100644 +--- a/net/minecraft/world/effect/RegenerationMobEffect.java ++++ b/net/minecraft/world/effect/RegenerationMobEffect.java +@@ -11,7 +11,7 @@ class RegenerationMobEffect extends MobEffect { @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { + public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { if (entity.getHealth() < entity.getMaxHealth()) { - entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit -+ entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur ++ entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur - Config MobEffect by world } return true; -diff --git a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java -index 98b74649a667fb9b10afef0ba5383a73022d8c71..0c7c0524e487ff32e16dd9939d92bc6441602747 100644 ---- a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java -@@ -21,7 +21,8 @@ class SaturationMobEffect extends InstantenousMobEffect { - int oldFoodLevel = entityhuman.getFoodData().foodLevel; - org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel); +diff --git a/net/minecraft/world/effect/SaturationMobEffect.java b/net/minecraft/world/effect/SaturationMobEffect.java +index c192165910f6b139df6f604d0bce989061efa9cb..622c23f4570d07de8bee9623bf900aabb3331ded 100644 +--- a/net/minecraft/world/effect/SaturationMobEffect.java ++++ b/net/minecraft/world/effect/SaturationMobEffect.java +@@ -16,7 +16,8 @@ class SaturationMobEffect extends InstantenousMobEffect { + int oldFoodLevel = player.getFoodData().foodLevel; + org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, amplifier + 1 + oldFoodLevel); if (!event.isCancelled()) { -- entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F); -+ if (entityhuman.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) entityhuman.burpDelay = entityhuman.level().purpurConfig.playerBurpDelay; // Purpur -+ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur +- player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F); ++ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - Burp delay ++ player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur - Config MobEffect by world } - ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate(); -diff --git a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java -index 303cefba51e19ac43b1f6188ad64ef480715ebaf..98ec88751b3e71c2e7aad633096b7f41608c0b33 100644 ---- a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java -@@ -10,7 +10,7 @@ class WitherMobEffect extends MobEffect { + ((org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity()).sendHealthUpdate(); +diff --git a/net/minecraft/world/effect/WitherMobEffect.java b/net/minecraft/world/effect/WitherMobEffect.java +index 1fc9e1ad541c46124183a401b2a7d99aea69cecf..881271f0bc77a8a8a7d31daad9a8188bebaca67b 100644 +--- a/net/minecraft/world/effect/WitherMobEffect.java ++++ b/net/minecraft/world/effect/WitherMobEffect.java +@@ -12,7 +12,7 @@ public class WitherMobEffect extends MobEffect { @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { -- entity.hurtServer(world, entity.damageSources().wither(), 1.0F); -+ entity.hurtServer(world, entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur + public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { +- entity.hurtServer(level, entity.damageSources().wither(), 1.0F); ++ entity.hurtServer(level, entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur - Config MobEffect by world return true; } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e9438f3b2acabcb9d3ecd6484704db1a94b21a0e..68f94543395c143234957cd5e33600a0c4b1c87d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -175,7 +175,7 @@ import org.bukkit.plugin.PluginManager; - // CraftBukkit end +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 36f5c28fa2fb3ba2cb5a4b2614c6b8d934659892..3f97182bc9ef86476c25deb3106dab7152014edf 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -134,7 +134,7 @@ import net.minecraft.world.scores.Team; + import org.slf4j.Logger; public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker - -+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur ++ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur - Configurable entity base attributes // CraftBukkit start private static final int CURRENT_LEVEL = 2; public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation -@@ -299,6 +299,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -252,9 +252,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public double xOld; public double yOld; public double zOld; -+ public float maxUpStep; // Purpur ++ public float maxUpStep; // Purpur - Add option to set armorstand step height public boolean noPhysics; private boolean wasOnFire; - public final RandomSource random; -@@ -339,7 +340,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - private final Set tags; - private final double[] pistonDeltas; - private long pistonDeltasGameTime; -- private EntityDimensions dimensions; -+ protected EntityDimensions dimensions; // Purpur - private -> protected - private float eyeHeight; - public boolean isInPowderSnow; - public boolean wasInPowderSnow; -@@ -390,6 +391,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - private final int despawnTime; // Paper - entity despawn time limit - public boolean activatedPriorityReset = false; // Pufferfish - DAB - public int activatedPriority = org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.maximumActivationPrio; // Pufferfish - DAB (golf score) +- public final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random ++ public final RandomSource random; // Paper - Share random for entities to make them more random // Add toggle for RNG manipulation + public int tickCount; + private int remainingFireTicks = -this.getFireImmuneTicks(); + public boolean wasTouchingWater; +@@ -288,8 +289,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public PortalProcessor portalProcess; + public int portalCooldown; + private boolean invulnerable; +- protected UUID uuid = Mth.createInsecureUUID(this.random); +- protected String stringUUID = this.uuid.toString(); ++ protected UUID uuid; // Purpur - Add toggle for RNG manipulation ++ protected String stringUUID; // Purpur - Add toggle for RNG manipulation + private boolean hasGlowingTag; + private final Set tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl + private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0}; +@@ -343,6 +344,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public long activatedTick = Integer.MIN_VALUE; + public boolean isTemporarilyActive; + public long activatedImmunityTick = Integer.MIN_VALUE; + public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -570,6 +572,27 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } + public void inactiveTick() { + } +@@ -528,10 +530,39 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } // Paper end - optimise entity tracker -+ // Purpur start + ++ // Purpur start - Add canSaveToDisk to Entity + public boolean canSaveToDisk() { + return true; + } -+ // Purpur end ++ // Purpur end - Add canSaveToDisk to Entity + + // Purpur start - copied from Mob - API for any mob to burn daylight + public boolean isSunBurnTick() { + if (this.level().isDay() && !this.level().isClientSide) { -+ float f = this.getLightLevelDependentMagicValue(); -+ BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); ++ float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue(); ++ BlockPos blockPos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); + boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; -+ -+ if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { ++ if (lightLevelDependentMagicValue > 0.5F ++ && this.random.nextFloat() * 30.0F < (lightLevelDependentMagicValue - 0.4F) * 2.0F ++ && !flag ++ && this.level().canSeeSky(blockPos)) { + return true; + } + } @@ -2196,27 +1955,28 @@ index e9438f3b2acabcb9d3ecd6484704db1a94b21a0e..68f94543395c143234957cd5e33600a0 + return false; + } + // Purpur end - copied from Mob - API for any mob to burn daylight - - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); -@@ -579,7 +602,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.bb = Entity.INITIAL_AABB; - this.stuckSpeedMultiplier = Vec3.ZERO; - this.nextStep = 1.0F; -- this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random -+ this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper - Share random for entities to make them more random // Purpur - this.remainingFireTicks = -this.getFireImmuneTicks(); - this.fluidHeight = new Object2DoubleArrayMap(2); - this.fluidOnEyes = new HashSet(); -@@ -977,6 +1000,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess ++ + public Entity(EntityType entityType, Level level) { + this.type = entityType; + this.level = level; + this.dimensions = entityType.getDimensions(); ++ // Purpur start - Add toggle for RNG manipulation ++ this.random = level == null || level.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); ++ this.uuid = Mth.createInsecureUUID(this.random); ++ this.stringUUID = this.uuid.toString(); ++ // Purpur end - Add toggle for RNG manipulation + this.position = Vec3.ZERO; + this.blockPosition = BlockPos.ZERO; + this.chunkPosition = ChunkPos.ZERO; +@@ -912,6 +943,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { // Paper end - Configurable nether ceiling damage -+ if (this.level().purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur ++ if (this.level.purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur - Add option to teleport to spawn on nether ceiling damage this.onBelowWorld(); } - -@@ -1943,7 +1967,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } +@@ -1841,7 +1873,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public boolean fireImmune() { @@ -2224,141 +1984,146 @@ index e9438f3b2acabcb9d3ecd6484704db1a94b21a0e..68f94543395c143234957cd5e33600a0 + return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API } - public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { -@@ -2016,7 +2040,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) { +@@ -1910,7 +1942,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return this.isInWater() || flag; } -- void updateInWaterStateAndDoWaterCurrentPushing() { -+ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public - Entity entity = this.getVehicle(); - - if (entity instanceof AbstractBoat abstractboat) { -@@ -2708,6 +2732,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - nbttagcompound.putBoolean("Paper.FreezeLock", true); +- public void updateInWaterStateAndDoWaterCurrentPushing() { ++ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - Movement options for armor stands - package-private -> public - TODO: use AT file + if (this.getVehicle() instanceof AbstractBoat abstractBoat && !abstractBoat.isUnderWater()) { + this.wasTouchingWater = false; + } else if (this.updateFluidHeightAndDoFluidPushing(FluidTags.WATER, 0.014)) { +@@ -2546,6 +2578,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + compound.putBoolean("Paper.FreezeLock", true); } // Paper end -+ // Purpur start ++ ++ // Purpur start - Fire immune API + if (immuneToFire != null) { -+ nbttagcompound.putBoolean("Purpur.FireImmune", immuneToFire); ++ compound.putBoolean("Purpur.FireImmune", immuneToFire); + } -+ // Purpur end - return nbttagcompound; - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); -@@ -2856,6 +2885,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - freezeLocked = nbt.getBoolean("Paper.FreezeLock"); ++ // Purpur end - Fire immune API ++ + return compound; + } catch (Throwable var9) { + CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT"); +@@ -2695,6 +2734,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + freezeLocked = compound.getBoolean("Paper.FreezeLock"); } // Paper end -+ // Purpur start -+ if (nbt.contains("Purpur.FireImmune")) { -+ immuneToFire = nbt.getBoolean("Purpur.FireImmune"); ++ ++ // Purpur start - Fire immune API ++ if (compound.contains("Purpur.FireImmune")) { ++ immuneToFire = compound.getBoolean("Purpur.FireImmune"); + } -+ // Purpur end - - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); -@@ -3107,6 +3141,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess ++ // Purpur end - Fire immune API ++ + } catch (Throwable var17) { + CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT"); + CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded"); +@@ -2941,6 +2987,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess if (this.isAlive() && this instanceof Leashable leashable) { if (leashable.getLeashHolder() == player) { if (!this.level().isClientSide()) { -+ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur ++ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur - Allow leashing villagers // CraftBukkit start - fire PlayerUnleashEntityEvent // Paper start - Expand EntityUnleashEvent - org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); -@@ -3312,6 +3347,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + org.bukkit.event.player.PlayerUnleashEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); +@@ -3147,6 +3194,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.passengers = ImmutableList.copyOf(list); } -+ // Purpur start ++ // Purpur start - Ridables + if (isRidable() && this.passengers.get(0) == passenger && passenger instanceof Player player) { + onMount(player); + this.rider = player; + } -+ // Purpur end ++ // Purpur end - Ridables + this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); } } -@@ -3351,6 +3393,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3188,6 +3242,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return false; } // CraftBukkit end + -+ // Purpur start ++ // Purpur start - Ridables + if (this.rider != null && this.passengers.get(0) == this.rider) { + onDismount(this.rider); + this.rider = null; + } -+ // Purpur end ++ // Purpur end - Ridables + - if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { + if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) { this.passengers = ImmutableList.of(); } else { -@@ -3431,14 +3481,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3266,15 +3328,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return Vec3.directionFromRotation(this.getRotationVector()); } -+ public BlockPos portalPos = BlockPos.ZERO; // Purpur ++ public BlockPos portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals public void setAsInsidePortal(Portal portal, BlockPos pos) { if (this.isOnPortalCooldown()) { + if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(this.portalPos))) // Purpur - Fix stuck in portals this.setPortalCooldown(); - } else { + } else if (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur - Entities can use portals - if (this.portalProcess != null && this.portalProcess.isSamePortal(portal)) { - if (!this.portalProcess.isInsidePortalThisTick()) { - this.portalProcess.updateEntryPosition(pos.immutable()); - this.portalProcess.setAsInsidePortalThisTick(true); -+ this.portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals - } - } else { + if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) { this.portalProcess = new PortalProcessor(portal, pos.immutable()); -@@ -3650,7 +3703,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } else if (!this.portalProcess.isInsidePortalThisTick()) { + this.portalProcess.updateEntryPosition(pos.immutable()); + this.portalProcess.setAsInsidePortalThisTick(true); ++ this.portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals + } + } + } +@@ -3476,7 +3541,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public int getMaxAirSupply() { - return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() -+ return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur ++ return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur - Drowning Settings } public int getAirSupply() { -@@ -4139,7 +4192,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3964,7 +4029,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess // CraftBukkit end - public boolean canUsePortal(boolean allowVehicles) { -- return (allowVehicles || !this.isPassenger()) && this.isAlive(); -+ return (allowVehicles || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals + public boolean canUsePortal(boolean allowPassengers) { +- return (allowPassengers || !this.isPassenger()) && this.isAlive(); ++ return (allowPassengers || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals } - public boolean canTeleport(Level from, Level to) { -@@ -4732,6 +4785,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return Mth.lerp(delta, this.yRotO, this.yRot); + public boolean canTeleport(Level fromLevel, Level toLevel) { +@@ -4501,6 +4566,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return Mth.lerp(partialTick, this.yRotO, this.yRot); } -+ // Purpur start ++ // Purpur start - Stop squids floating on top of water + public AABB getAxisForFluidCheck() { + return this.getBoundingBox().deflate(0.001D); + } -+ // Purpur end ++ // Purpur end - Stop squids floating on top of water + // Paper start - optimise collisions public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { if (this.touchingUnloadedChunk()) { -@@ -5143,7 +5202,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4909,7 +4980,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public float maxUpStep() { - return 0.0F; -+ return maxUpStep; ++ return maxUpStep; // Purpur - Add option to set armorstand step height } - public void onExplosionHit(@Nullable Entity entity) {} -@@ -5344,4 +5403,44 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition()); + public void onExplosionHit(@Nullable Entity entity) { +@@ -5107,4 +5178,44 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return ((ServerLevel) this.level).isPositionEntityTicking(this.blockPosition()); } // Paper end - Expose entity id counter -+ // Purpur start ++ // Purpur start - Ridables + @Nullable + private Player rider = null; + @@ -2397,39 +2162,30 @@ index e9438f3b2acabcb9d3ecd6484704db1a94b21a0e..68f94543395c143234957cd5e33600a0 + public boolean processClick(InteractionHand hand) { + return false; + } -+ // Purpur end ++ // Purpur end - Ridables } -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 6bf691fcc6486bde73bae30eff09142802c29eda..59c4d3753c7084e92402608b7fb3c4adbc6c2f65 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -39,6 +39,7 @@ public final class EntitySelector { +diff --git a/net/minecraft/world/entity/EntitySelector.java b/net/minecraft/world/entity/EntitySelector.java +index 2aa54ee4ae742a68b79b906062528696beedf60d..002ec5f1ec14411ca48ae04b3379db0c70f81942 100644 +--- a/net/minecraft/world/entity/EntitySelector.java ++++ b/net/minecraft/world/entity/EntitySelector.java +@@ -28,6 +28,8 @@ public final class EntitySelector { return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; }; // Paper end - Ability to control player's insomnia and phantoms -+ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur - - private EntitySelector() {} ++ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur - AFK API ++ // Paper start - Affects Spawning API -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 24eaa9a2e6f0cf198a307058e655d5eb16a2c8c5..002795df9c9c8d27f07f855dff148dfe353bef68 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -389,7 +389,8 @@ public class EntityType implements FeatureElement, EntityTypeT - @Nullable - private Component description; - private final Optional> lootTable; -- private final EntityDimensions dimensions; -+ private EntityDimensions dimensions; // Purpur - remove final -+ public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur - private final float spawnDimensionsScale; - private final FeatureFlagSet requiredFeatures; - -@@ -405,6 +406,16 @@ public class EntityType implements FeatureElement, EntityTypeT - return EntityType.register(EntityType.vanillaEntityId(id), type); + public static final Predicate PLAYER_AFFECTS_SPAWNING = (entity) -> { + return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning; +diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java +index 303bd2d3ea5c313477c8ab48359a01f230327447..adeab8980b590e4a8b64b62cb60f623b2a842982 100644 +--- a/net/minecraft/world/entity/EntityType.java ++++ b/net/minecraft/world/entity/EntityType.java +@@ -1084,6 +1084,16 @@ public class EntityType implements FeatureElement, EntityTypeT + return register(vanillaEntityId(key), builder); } -+ // Purpur start ++ // Purpur start - PlayerSetSpawnerTypeWithEggEvent + public static EntityType getFromBukkitType(org.bukkit.entity.EntityType bukkitType) { + return getFromKey(ResourceLocation.parse(bukkitType.getKey().toString())); + } @@ -2437,16 +2193,16 @@ index 24eaa9a2e6f0cf198a307058e655d5eb16a2c8c5..002795df9c9c8d27f07f855dff148dfe + public static EntityType getFromKey(ResourceLocation location) { + return BuiltInRegistries.ENTITY_TYPE.getValue(location); + } -+ // Purpur end ++ // Purpur end - PlayerSetSpawnerTypeWithEggEvent + - public static ResourceLocation getKey(EntityType type) { - return BuiltInRegistries.ENTITY_TYPE.getKey(type); + public static ResourceLocation getKey(EntityType entityType) { + return BuiltInRegistries.ENTITY_TYPE.getKey(entityType); } -@@ -605,6 +616,16 @@ public class EntityType implements FeatureElement, EntityTypeT +@@ -1313,6 +1323,16 @@ public class EntityType implements FeatureElement, EntityTypeT return this.category; } -+ // Purpur start ++ // Purpur start - PlayerSetSpawnerTypeWithEggEvent + public String getName() { + return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath(); + } @@ -2454,55 +2210,65 @@ index 24eaa9a2e6f0cf198a307058e655d5eb16a2c8c5..002795df9c9c8d27f07f855dff148dfe + public String getTranslatedName() { + return getDescription().getString(); + } -+ // Purpur end ++ // Purpur end - PlayerSetSpawnerTypeWithEggEvent + public String getDescriptionId() { return this.descriptionId; } -@@ -662,6 +683,12 @@ public class EntityType implements FeatureElement, EntityTypeT - entity.load(nbt); - }, () -> { - EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); +@@ -1371,7 +1391,14 @@ public class EntityType implements FeatureElement, EntityTypeT + entity.load(tag); + }, + // Paper end - Don't fire sync event during generation +- () -> LOGGER.warn("Skipping Entity with id {}", tag.getString("id")) + // Purpur start - log skipped entity's position -+ try { -+ ListTag pos = nbt.getList("Pos", 6); -+ EntityType.LOGGER.warn("Location: {} {},{},{}", world.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2)); -+ } catch (Throwable ignore) {} -+ // Purpur end - }); ++ () -> {LOGGER.warn("Skipping Entity with id {}", tag.getString("id")); ++ try { ++ ListTag pos = tag.getList("Pos", 6); ++ EntityType.LOGGER.warn("Location: {} {},{},{}", level.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2)); ++ } catch (Throwable ignore) {} ++ } ++ // Purpur end - log skipped entity's position + ); } -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index bf0838f574fa3fb9654e087d602b8d380bd7fb28..32a0db7e8f974712bd8a05f16f3bd48ac66142d5 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -333,7 +333,7 @@ public class ExperienceOrb extends Entity { - public void playerTouch(Player player) { - if (player instanceof ServerPlayer entityplayer) { - if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(entityplayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent -- player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; -+ player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - player.take(this, 1); - int i = this.repairPlayerItems(entityplayer, this.value); - -@@ -351,7 +351,7 @@ public class ExperienceOrb extends Entity { +diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java +index a5fd13641d134eae9d8f1d998cfc456b8fccd140..a43e5190c0f9ae14ccecccd5b58dc0e17f18b0a1 100644 +--- a/net/minecraft/world/entity/ExperienceOrb.java ++++ b/net/minecraft/world/entity/ExperienceOrb.java +@@ -323,7 +323,7 @@ public class ExperienceOrb extends Entity { + public void playerTouch(Player entity) { + if (entity instanceof ServerPlayer serverPlayer) { + if (entity.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent +- entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; ++ entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay + entity.take(this, 1); + int i = this.repairPlayerItems(serverPlayer, this.value); + if (i > 0) { +@@ -339,7 +339,7 @@ public class ExperienceOrb extends Entity { } - private int repairPlayerItems(ServerPlayer player, int amount) { -- Optional optional = EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); -+ Optional optional = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); // Purpur - Add option to mend the most damaged equipment first - - if (optional.isPresent()) { - ItemStack itemstack = ((EnchantedItemInUse) optional.get()).itemStack(); -diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java -index b851c3ee1426bc0a259bf6c4a662af0c9883dd71..3283228d7ebf98ce98780725a0a412bea4200da5 100644 ---- a/src/main/java/net/minecraft/world/entity/GlowSquid.java -+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java -@@ -25,6 +25,39 @@ public class GlowSquid extends Squid { - super(type, world); + private int repairPlayerItems(ServerPlayer player, int value) { +- Optional randomItemWith = EnchantmentHelper.getRandomItemWith( ++ Optional randomItemWith = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith( // Purpur - Add option to mend the most damaged equipment first + EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged + ); + if (randomItemWith.isPresent()) { +diff --git a/net/minecraft/world/entity/GlowSquid.java b/net/minecraft/world/entity/GlowSquid.java +index efee812785240c1ab1fd47514cfb236a3548f9cf..b982d4b7bdf39fcaf5f22cc889467d7b953e3a8e 100644 +--- a/net/minecraft/world/entity/GlowSquid.java ++++ b/net/minecraft/world/entity/GlowSquid.java +@@ -25,6 +25,47 @@ public class GlowSquid extends Squid { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Flying squids! Oh my! ++ @Override ++ public boolean canFly() { ++ return this.level().purpurConfig.glowSquidsCanFly; ++ } ++ // Purpur end - Flying squids! Oh my! ++ ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.glowSquidRidable; @@ -2513,37 +2279,38 @@ index b851c3ee1426bc0a259bf6c4a662af0c9883dd71..3283228d7ebf98ce98780725a0a412be + public boolean isControllable() { + return level().purpurConfig.glowSquidControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.glowSquidMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + -+ @Override -+ public boolean canFly() { -+ return this.level().purpurConfig.glowSquidsCanFly; -+ } -+ ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.glowSquidTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.glowSquidAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected ParticleOptions getInkParticle() { return ParticleTypes.GLOW_SQUID_INK; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef273a6167 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -246,9 +246,9 @@ public abstract class LivingEntity extends Entity implements Attackable { - protected int deathScore; +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 10d1d56fdf8ba1c86899077db7e98b96dd7ebdb6..8728aba69ac24b52e6221ea2ddd8fc6d5a26f3f3 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -248,9 +248,9 @@ public abstract class LivingEntity extends Entity implements Attackable { + protected float rotOffs; public float lastHurt; public boolean jumping; - public float xxa; @@ -2555,7 +2322,7 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef protected int lerpSteps; protected double lerpX; protected double lerpY; -@@ -295,6 +295,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -299,6 +299,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API @@ -2563,125 +2330,136 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef @Override public float getBukkitYaw() { -@@ -323,7 +324,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.lastClimbablePos = Optional.empty(); - this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class); - this.appliedScale = 1.0F; -- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); -+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur -+ this.initAttributes(); // Purpur +@@ -308,7 +309,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + + protected LivingEntity(EntityType entityType, Level level) { + super(entityType, level); +- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType)); ++ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType), this); // Purpur - Ridables ++ this.initAttributes(); // Purpur - Configurable entity base attributes this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit - // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor - this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue()); -@@ -338,6 +340,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.brain = this.makeBrain(new Dynamic(dynamicopsnbt, (Tag) dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), (Tag) dynamicopsnbt.emptyMap())))); + // CraftBukkit - this.setHealth(this.getMaxHealth()) inlined and simplified to skip the instanceof check for Player, as getBukkitEntity() is not initialized in constructor + this.entityData.set(LivingEntity.DATA_HEALTH_ID, this.getMaxHealth()); +@@ -322,6 +324,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.brain = this.makeBrain(new Dynamic<>(nbtOps, nbtOps.createMap(ImmutableMap.of(nbtOps.createString("memories"), nbtOps.emptyMap())))); } -+ protected void initAttributes() {}// Purpur ++ protected void initAttributes() {}// Purpur - Configurable entity base attributes + public Brain getBrain() { return this.brain; } -@@ -373,6 +377,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - public static AttributeSupplier.Builder createLivingAttributes() { - return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS).add(Attributes.MAX_ABSORPTION).add(Attributes.STEP_HEIGHT).add(Attributes.SCALE).add(Attributes.GRAVITY).add(Attributes.SAFE_FALL_DISTANCE).add(Attributes.FALL_DAMAGE_MULTIPLIER).add(Attributes.JUMP_STRENGTH).add(Attributes.OXYGEN_BONUS).add(Attributes.BURNING_TIME).add(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE).add(Attributes.WATER_MOVEMENT_EFFICIENCY).add(Attributes.MOVEMENT_EFFICIENCY).add(Attributes.ATTACK_KNOCKBACK); +@@ -375,6 +379,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + .add(Attributes.MOVEMENT_EFFICIENCY) + .add(Attributes.ATTACK_KNOCKBACK); } -+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur ++ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur - Ridables @Override - protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) { -@@ -474,6 +479,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (d1 < 0.0D) { - d0 = this.level().getWorldBorder().getDamagePerBlock(); - if (d0 > 0.0D) { -+ if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur - this.hurtServer(worldserver1, this.damageSources().outOfBorder(), (float) Math.max(1, Mth.floor(-d1 * d0))); + protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) { +@@ -458,6 +463,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (d < 0.0) { + double damagePerBlock = this.level().getWorldBorder().getDamagePerBlock(); + if (damagePerBlock > 0.0) { ++ // Purpur start - Add option to teleport to spawn if outside world border ++ if (this.level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { ++ serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level(), this.level().getSharedSpawnPos())); ++ return; ++ } ++ // Purpur end - Add option to teleport to spawn if outside world border + this.hurtServer(serverLevel1, this.damageSources().outOfBorder(), Math.max(1, Mth.floor(-d * damagePerBlock))); } } -@@ -485,7 +491,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - +@@ -471,7 +482,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + && (!flag || !((Player)this).getAbilities().invulnerable); if (flag1) { this.setAirSupply(this.decreaseAirSupply(this.getAirSupply())); - if (this.getAirSupply() == -20) { -+ if (this.getAirSupply() == -this.level().purpurConfig.drowningDamageInterval) { // Purpur ++ if (this.getAirSupply() == -this.level().purpurConfig.drowningDamageInterval) { // Purpur - Drowning Settings this.setAirSupply(0); - Vec3 vec3d = this.getDeltaMovement(); + Vec3 deltaMovement = this.getDeltaMovement(); -@@ -497,7 +503,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d0, this.getY() + d2, this.getZ() + d3, vec3d.x, vec3d.y, vec3d.z); +@@ -491,7 +502,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + ); } - this.hurt(this.damageSources().drown(), 2.0F); -+ this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur ++ this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur - Drowning Settings } - } - -@@ -830,6 +836,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> { - nbt.put("Brain", nbtbase); + } else if (this.getAirSupply() < this.getMaxAirSupply()) { + this.setAirSupply(this.increaseAirSupply(this.getAirSupply())); +@@ -795,6 +806,7 @@ public abstract class LivingEntity extends Entity implements Attackable { }); -+ nbt.putBoolean("Purpur.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - API for any mob to burn daylight + DataResult dataResult = this.brain.serializeStart(NbtOps.INSTANCE); + dataResult.resultOrPartial(LOGGER::error).ifPresent(brain -> compound.put("Brain", brain)); ++ compound.putBoolean("Purpur.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - API for any mob to burn daylight } @Override -@@ -918,6 +925,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain"))); +@@ -878,6 +890,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (compound.contains("Brain", 10)) { + this.brain = this.makeBrain(new Dynamic<>(NbtOps.INSTANCE, compound.get("Brain"))); } - ++ + // Purpur start - API for any mob to burn daylight -+ if (nbt.contains("Purpur.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Purpur.ShouldBurnInDay"); ++ if (compound.contains("Purpur.ShouldBurnInDay")) { ++ this.shouldBurnInDay = compound.getBoolean("Purpur.ShouldBurnInDay"); + } + // Purpur end - API for any mob to burn daylight } // CraftBukkit start -@@ -1052,9 +1064,29 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (entity != null) { - EntityType entitytypes = entity.getType(); - -- if (entitytypes == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL) || entitytypes == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD) || entitytypes == EntityType.PIGLIN && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD) || entitytypes == EntityType.PIGLIN_BRUTE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD) || entitytypes == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { // Gale - Petal - reduce skull ItemStack lookups for reduced visibility -- d0 *= 0.5D; -+ // Purpur start -+ if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) { -+ d0 *= entity.level().purpurConfig.skeletonHeadVisibilityPercent; -+ } else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) { -+ d0 *= entity.level().purpurConfig.zombieHeadVisibilityPercent; +@@ -1005,15 +1023,30 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (lookingEntity != null) { + // Gale start - Petal - reduce skull ItemStack lookups for reduced visibility + EntityType type = lookingEntity.getType(); +- if (type == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL) +- || type == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD) +- || type == EntityType.PIGLIN && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD) +- || type == EntityType.PIGLIN_BRUTE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD) +- || type == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { +- d *= 0.5; ++ // Purpur start - Mob head visibility percent ++ if (type == EntityType.SKELETON && itemBySlot.is(Items.SKELETON_SKULL)) { ++ d *= lookingEntity.level().purpurConfig.skeletonHeadVisibilityPercent; ++ } else if (type == EntityType.ZOMBIE && itemBySlot.is(Items.ZOMBIE_HEAD)) { ++ d *= lookingEntity.level().purpurConfig.zombieHeadVisibilityPercent; ++ } else if ((type == EntityType.PIGLIN || type == EntityType.PIGLIN_BRUTE) && itemBySlot.is(Items.PIGLIN_HEAD)) { ++ d *= lookingEntity.level().purpurConfig.piglinHeadVisibilityPercent; ++ } else if (type == EntityType.CREEPER && itemBySlot.is(Items.CREEPER_HEAD)) { ++ d *= lookingEntity.level().purpurConfig.creeperHeadVisibilityPercent; + } -+ else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) { -+ d0 *= entity.level().purpurConfig.creeperHeadVisibilityPercent; -+ } else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && itemstack.is(Items.PIGLIN_HEAD)) { -+ d0 *= entity.level().purpurConfig.piglinHeadVisibilityPercent; -+ } -+ // Purpur end ++ // Purpur end - Mob head visibility percent ++ } + -+ // Purpur start -+ if (entity instanceof LivingEntity entityliving) { -+ if (entityliving.hasEffect(MobEffects.BLINDNESS)) { -+ int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier(); -+ for (int i = 0; i < amplifier; i++) { -+ d0 *= this.level().purpurConfig.mobsBlindnessMultiplier; -+ } ++ // Purpur start - Configurable mob blindness ++ if (lookingEntity instanceof LivingEntity entityliving) { ++ if (entityliving.hasEffect(MobEffects.BLINDNESS)) { ++ int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier(); ++ for (int i = 0; i < amplifier; i++) { ++ d *= this.level().purpurConfig.mobsBlindnessMultiplier; + } } -+ // Purpur end + // Gale end - Petal - reduce skull ItemStack lookups for reduced visibility } ++ // Purpur end - Configurable mob blindness - return d0; -@@ -1110,6 +1142,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - + return d; + } +@@ -1060,6 +1093,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + Iterator iterator = this.activeEffects.values().iterator(); while (iterator.hasNext()) { MobEffectInstance effect = iterator.next(); -+ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().value().isBeneficial()) continue; // Purpur ++ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().value().isBeneficial()) continue; // Purpur - Milk Keeps Beneficial Effects EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED); if (event.isCancelled()) { continue; -@@ -1454,6 +1487,24 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -1378,6 +1412,24 @@ public abstract class LivingEntity extends Entity implements Attackable { this.stopSleeping(); } -+ // Purpur start -+ if (source.getEntity() instanceof net.minecraft.world.entity.player.Player player && source.getEntity().level().purpurConfig.creativeOnePunch && !source.is(DamageTypeTags.IS_PROJECTILE)) { ++ // Purpur start - One Punch Man! ++ if (damageSource.getEntity() instanceof net.minecraft.world.entity.player.Player player && damageSource.getEntity().level().purpurConfig.creativeOnePunch && !damageSource.is(DamageTypeTags.IS_PROJECTILE)) { + if (player.isCreative()) { + org.apache.commons.lang3.mutable.MutableDouble attackDamage = new org.apache.commons.lang3.mutable.MutableDouble(); + player.getMainHandItem().forEachModifier(EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { @@ -2696,103 +2474,101 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef + } + } + } -+ // Purpur end ++ // Purpur end - One Punch Man! + this.noActionTime = 0; if (amount < 0.0F) { amount = 0.0F; -@@ -1553,13 +1604,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (entity1 instanceof net.minecraft.world.entity.player.Player) { - net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity1; - -- this.lastHurtByPlayerTime = 100; -+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - this.lastHurtByPlayer = entityhuman; - } else if (entity1 instanceof Wolf) { - Wolf entitywolf = (Wolf) entity1; - - if (entitywolf.isTame()) { -- this.lastHurtByPlayerTime = 100; -+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - LivingEntity entityliving2 = entitywolf.getOwner(); - - if (entityliving2 instanceof net.minecraft.world.entity.player.Player) { -@@ -1706,6 +1757,18 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -1542,11 +1594,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + protected Player resolvePlayerResponsibleForDamage(DamageSource damageSource) { + Entity entity = damageSource.getEntity(); + if (entity instanceof Player player) { +- this.lastHurtByPlayerTime = 100; ++ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - Config for mob last hurt by player time + this.lastHurtByPlayer = player; + return player; + } else if (entity instanceof Wolf wolf && wolf.isTame()) { +- this.lastHurtByPlayerTime = 100; ++ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - Config for mob last hurt by player time + if (wolf.getOwner() instanceof Player player1) { + this.lastHurtByPlayer = player1; + } else { +@@ -1600,6 +1652,18 @@ public abstract class LivingEntity extends Entity implements Attackable { } } -+ // Purpur start -+ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemstack == null || itemstack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) { ++ // Purpur start - Totems work in inventory ++ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemStack == null || itemStack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) { + for (ItemStack item : player.getInventory().items) { + if (item.getItem() == Items.TOTEM_OF_UNDYING) { -+ itemstack1 = item; -+ itemstack = item.copy(); ++ itemInHand = item; ++ itemStack = item.copy(); + break; + } + } + } -+ // Purpur end ++ // Purpur end - Totems work in inventory + - org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null; - EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot); - event.setCancelled(itemstack == null); -@@ -1877,7 +1940,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - boolean flag = false; - - if (this.dead && adversary instanceof WitherBoss) { // Paper -- if (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (worldserver.purpurConfig.witherBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - BlockPos blockposition = this.blockPosition(); - BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); - -@@ -1914,6 +1977,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.dropEquipment(world); // CraftBukkit - from below - if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -+ if (!(damageSource.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur - this.dropFromLootTable(world, damageSource, flag); + final org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null; + final EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot); + event.setCancelled(itemStack == null); +@@ -1765,7 +1829,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (this.level() instanceof ServerLevel serverLevel) { + boolean var6 = false; + if (this.dead && entitySource instanceof WitherBoss) { // Paper +- if (serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (serverLevel.purpurConfig.witherBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected + BlockPos blockPos = this.blockPosition(); + BlockState blockState = Blocks.WITHER_ROSE.defaultBlockState(); + if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) { +@@ -1796,6 +1860,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + boolean flag = this.lastHurtByPlayerTime > 0; + this.dropEquipment(level); // CraftBukkit - from below + if (this.shouldDropLoot() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ++ if (!(damageSource.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur - Disable loot drops on death by cramming + this.dropFromLootTable(level, damageSource, flag); // Paper start final boolean prev = this.clearEquipmentSlots; -@@ -1922,6 +1986,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -1804,6 +1869,7 @@ public abstract class LivingEntity extends Entity implements Attackable { // Paper end - this.dropCustomDeathLoot(world, damageSource, flag); + this.dropCustomDeathLoot(level, damageSource, flag); this.clearEquipmentSlots = prev; // Paper -+ } // Purpur ++ } // Purpur - Disable loot drops on death by cramming } - // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment - org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> { -@@ -3188,6 +3253,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment +@@ -3033,6 +3099,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + float f = (float)(d * 10.0 - 3.0); if (f > 0.0F) { - this.playSound(this.getFallDamageSound((int) f), 1.0F, 1.0F); -+ if (level().purpurConfig.elytraKineticDamage) // Purpur + this.playSound(this.getFallDamageSound((int)f), 1.0F, 1.0F); ++ if (level().purpurConfig.elytraKineticDamage) // Purpur - Toggle for kinetic damage this.hurt(this.damageSources().flyIntoWall(), f); } } -@@ -3717,8 +3783,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.pushEntities(); +@@ -3505,8 +3572,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.pushEntities(); // Paper start - Add EntityMoveEvent -- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { +- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) { - if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ // Purpur start ++ // Purpur start - Ridables + if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -+ // Purpur end ++ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) { ++ // Purpur end - Ridables 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()); -@@ -3728,6 +3796,21 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3516,11 +3585,52 @@ public abstract class LivingEntity extends Entity implements Attackable { this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); } } -+ // Purpur start ++ // Purpur start - Ridables + if (getRider() != null) { + getRider().resetLastActionTime(); + if (((ServerLevel) level()).hasRidableMoveEvent && this instanceof Mob) { + Location from = new Location(level().getWorld(), xo, yo, zo, this.yRotO, this.xRotO); + Location to = new Location(level().getWorld(), getX(), getY(), getZ(), this.getYRot(), this.getXRot()); -+ org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone()); ++ org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (org.bukkit.entity.Player) getRider().getBukkitEntity(), from, to.clone()); + if (!event.callEvent()) { + absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); + } else if (!to.equals(event.getTo())) { @@ -2800,27 +2576,23 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef + } + } + } -+ // Purpur end ++ // Purpur end - Ridables } // Paper end - Add EntityMoveEvent - world = this.level(); -@@ -3737,6 +3820,34 @@ public abstract class LivingEntity extends Entity implements Attackable { - } + if (this.level() instanceof ServerLevel serverLevel && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { + this.hurtServer(serverLevel, this.damageSources().drown(), 1.0F); } - ++ + // Purpur start - copied from Zombie - API for any mob to burn daylight + if (this.isAlive()) { + boolean flag = this.shouldBurnInDay() && this.isSunBurnTick(); // Paper - shouldBurnInDay API // Purpur - use shouldBurnInDay() method to handle Phantoms properly - API for any mob to burn daylight -+ + if (flag) { -+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -+ -+ if (!itemstack.isEmpty()) { -+ if (itemstack.isDamageableItem()) { -+ Item item = itemstack.getItem(); -+ -+ itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); -+ if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { ++ ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD); ++ if (!itemBySlot.isEmpty()) { ++ if (itemBySlot.isDamageableItem()) { ++ Item item = itemBySlot.getItem(); ++ itemBySlot.setDamageValue(itemBySlot.getDamageValue() + this.random.nextInt(2)); ++ if (itemBySlot.getDamageValue() >= itemBySlot.getMaxDamage()) { + this.onEquippedItemBroken(item, EquipmentSlot.HEAD); + this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); + } @@ -2831,7 +2603,7 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef + + if (flag) { + if (getRider() == null || !this.isControllable()) // Purpur - ignore mobs which are uncontrollable or without rider - API for any mob to burn daylight -+ this.igniteForSeconds(8.0F); ++ this.igniteForSeconds(8.0F); + } + } + } @@ -2839,12 +2611,13 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef } public boolean isSensitiveToWater() { -@@ -3763,7 +3874,17 @@ public abstract class LivingEntity extends Entity implements Attackable { - }).toList(); - EquipmentSlot enumitemslot = (EquipmentSlot) Util.getRandom(list, this.random); - -- this.getItemBySlot(enumitemslot).hurtAndBreak(1, this, enumitemslot); -+ // Purpur start +@@ -3542,7 +3652,18 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (i1 % 2 == 0) { + List list = EquipmentSlot.VALUES.stream().filter(slot -> canGlideUsing(this.getItemBySlot(slot), slot)).toList(); + EquipmentSlot equipmentSlot = Util.getRandom(list, this.random); +- this.getItemBySlot(equipmentSlot).hurtAndBreak(1, this, equipmentSlot); ++ ++ // Purpur start - Implement elytra settings + int damage = level().purpurConfig.elytraDamagePerSecond; + if (level().purpurConfig.elytraDamageMultiplyBySpeed > 0) { + double speed = getDeltaMovement().lengthSqr(); @@ -2853,31 +2626,13 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef + } + } + -+ this.getItemBySlot(enumitemslot).hurtAndBreak(damage, this, enumitemslot); -+ // Purpur end ++ this.getItemBySlot(equipmentSlot).hurtAndBreak(damage, this, equipmentSlot); ++ // Purpur end - Implement elytra settings } this.gameEvent(GameEvent.ELYTRA_GLIDE); -@@ -3772,7 +3893,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - } - -- protected boolean canGlide() { -+ public boolean canGlide() { // Purpur - if (!this.onGround() && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) { - Iterator iterator = EquipmentSlot.VALUES.iterator(); - -@@ -4694,7 +4815,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (equippable != null && equippable.dispensable()) { - EquipmentSlot enumitemslot = equippable.slot(); - -- return this.canUseSlot(enumitemslot) && equippable.canBeEquippedBy(this.getType()) ? this.getItemBySlot(enumitemslot).isEmpty() && this.canDispenserEquipIntoSlot(enumitemslot) : false; -+ return this.canUseSlot(enumitemslot) && equippable.canBeEquippedBy(this.getType()) && this.getItemBySlot(enumitemslot).isEmpty() && this.canDispenserEquipIntoSlot(enumitemslot); - } else { - return false; - } -@@ -4719,6 +4840,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - return equippable == null ? slot == EquipmentSlot.MAINHAND && this.canUseSlot(EquipmentSlot.MAINHAND) : slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.getType()); +@@ -4425,6 +4546,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + : slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.getType()); } + // Purpur start - Dispenser curse of binding protection @@ -2887,48 +2642,46 @@ index 167d164d07285bfff6eb8076d7abe17ca9543df9..260e9b0398ddaacacfe5de352ac686ef + // Purpur end - Dispenser curse of binding protection + private static SlotAccess createEquipmentSlotAccess(LivingEntity entity, EquipmentSlot slot) { - return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND ? SlotAccess.forEquipmentSlot(entity, slot, (itemstack) -> { - return itemstack.isEmpty() || entity.getEquipmentSlotForItem(itemstack) == slot; -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 92e38600c64fa8311a191f76aa9c7654ce10c5b4..a2ab53e70328b4ac0d019ebbd3d3cffeeb29d76b 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -146,6 +146,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - private BlockPos restrictCenter; - private float restrictRadius; - -+ public int ticksSinceLastInteraction; // Purpur + return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND + ? SlotAccess.forEquipmentSlot(entity, slot, stack -> stack.isEmpty() || entity.getEquipmentSlotForItem(stack) == slot) +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 1b74114d0833eb9ca2c854122727d4bf76a11071..7b8804844ab5636323bc8d136c775f1e9591e89c 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -143,13 +143,14 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + private BlockPos restrictCenter = BlockPos.ZERO; + private float restrictRadius = -1.0F; public boolean aware = true; // CraftBukkit ++ public int ticksSinceLastInteraction; // Purpur - Entity lifespan - protected Mob(EntityType type, Level world) { -@@ -161,8 +162,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - this.restrictRadius = -1.0F; + protected Mob(EntityType entityType, Level level) { + super(entityType, level); this.goalSelector = new GoalSelector(); this.targetSelector = new GoalSelector(); - this.lookControl = new LookControl(this); - this.moveControl = new MoveControl(this); -+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur -+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur ++ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur - Ridables ++ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur - Ridables this.jumpControl = new JumpControl(this); this.bodyRotationControl = this.createBodyControl(); - this.navigation = this.createNavigation(world); -@@ -334,6 +335,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - entityliving = null; + this.navigation = this.createNavigation(level); +@@ -294,6 +295,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + target = null; } } -+ if (entityliving instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - this.target = entityliving; ++ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan + this.target = target; return true; // CraftBukkit end -@@ -374,8 +376,29 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -333,7 +335,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.resetAmbientSoundTime(); this.playAmbientSound(); } + -+ incrementTicksSinceLastInteraction(); // Purpur - } - -+ // Purpur start ++ incrementTicksSinceLastInteraction(); // Purpur - Entity lifespan ++ } ++ ++ // Purpur start - Entity lifespan + private void incrementTicksSinceLastInteraction() { + ++this.ticksSinceLastInteraction; + if (getRider() != null) { @@ -2944,68 +2697,67 @@ index 92e38600c64fa8311a191f76aa9c7654ce10c5b4..a2ab53e70328b4ac0d019ebbd3d3cffe + if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } -+ } -+ // Purpur end -+ - @Override - protected void playHurtSound(DamageSource damageSource) { - this.resetAmbientSoundTime(); -@@ -543,6 +566,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - } + } ++ // Purpur end - Entity lifespan - nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit -+ nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur + @Override + protected void playHurtSound(DamageSource source) { +@@ -482,6 +505,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + compound.putBoolean("NoAI", this.isNoAi()); + } + compound.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit ++ compound.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur - Entity lifespan } @Override -@@ -620,6 +644,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - this.aware = nbt.getBoolean("Bukkit.Aware"); +@@ -564,6 +588,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + this.aware = compound.getBoolean("Bukkit.Aware"); } // CraftBukkit end -+ // Purpur start -+ if (nbt.contains("Purpur.ticksSinceLastInteraction")) { -+ this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction"); ++ // Purpur start - Entity lifespan ++ if (compound.contains("Purpur.ticksSinceLastInteraction")) { ++ this.ticksSinceLastInteraction = compound.getInt("Purpur.ticksSinceLastInteraction"); + } -+ // Purpur end ++ // Purpur end - Entity lifespan } @Override -@@ -670,7 +699,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - Level world = this.level(); +@@ -614,7 +643,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + && this.canPickUpLoot() + && this.isAlive() + && !this.dead +- && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ && serverLevel.purpurConfig.entitiesPickUpLootBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected + Vec3i pickupReach = this.getPickupReach(); - if (world instanceof ServerLevel worldserver) { -- if (this.canPickUpLoot() && this.isAlive() && !this.dead && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (this.canPickUpLoot() && this.isAlive() && !this.dead && (worldserver.purpurConfig.entitiesPickUpLootBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur - Vec3i baseblockposition = this.getPickupReach(); - List list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ())); - Iterator iterator = list.iterator(); -@@ -1370,7 +1399,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - attributemodifiable.addPermanentModifier(new AttributeModifier(Mob.RANDOM_SPAWN_BONUS_ID, randomsource.triangle(0.0D, 0.11485000000000001D), AttributeModifier.Operation.ADD_MULTIPLIED_BASE)); + for (ItemEntity itemEntity : this.level() +@@ -1255,7 +1284,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + ); } -- this.setLeftHanded(randomsource.nextFloat() < 0.05F); -+ this.setLeftHanded(randomsource.nextFloat() < world.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - return entityData; +- this.setLeftHanded(random.nextFloat() < 0.05F); ++ this.setLeftHanded(random.nextFloat() < level.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - Changeable Mob Left Handed Chance + return spawnGroupData; } -@@ -1472,7 +1501,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - protected void onOffspringSpawnedFromEgg(Player player, Mob child) {} +@@ -1352,7 +1381,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } protected InteractionResult mobInteract(Player player, InteractionHand hand) { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } public boolean isWithinRestriction() { -@@ -1711,6 +1740,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -1594,6 +1623,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.playAttackSound(); } -+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur ++ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan return flag; } -@@ -1722,27 +1752,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -1606,24 +1636,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab // Gale end - JettPack - optimize sun burn tick - cache eye blockpos public boolean isSunBurnTick() { @@ -3016,31 +2768,28 @@ index 92e38600c64fa8311a191f76aa9c7654ce10c5b4..a2ab53e70328b4ac0d019ebbd3d3cffe - this.cached_position = this.position; - } - -- float f = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness +- float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness - - // Check brightness first -- if (f <= 0.5F) return false; -- if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false; +- if (lightLevelDependentMagicValue <= 0.5F) return false; +- if (this.random.nextFloat() * 30.0F >= (lightLevelDependentMagicValue - 0.4F) * 2.0F) return false; - // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; -- -- if (!flag && this.level().canSeeSky(this.cached_eye_blockpos)) { // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos -- return true; -- } +- return !flag && this.level().canSeeSky(this.cached_eye_blockpos); // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - } - -- return false; +- return false; // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - diff on change + // Purpur - implemented in Entity - API for any mob to burn daylight + return super.isSunBurnTick(); } @Override -@@ -1804,4 +1815,58 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -1679,4 +1693,58 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab public float[] getArmorDropChances() { return this.armorDropChances; } + -+ // Purpur start ++ // Purpur start - Ridables + public double getMaxY() { + return level().getHeight(); + } @@ -3092,33 +2841,34 @@ index 92e38600c64fa8311a191f76aa9c7654ce10c5b4..a2ab53e70328b4ac0d019ebbd3d3cffe + return InteractionResult.PASS; + } + } -+ // Purpur end ++ // Purpur end - Ridables } -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 e45ed0ffa031f7ab5fa39b71a2678c5a282e0561..7bc3a6f4dabc6411b6ff17e6dbbd190d57076cd1 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 +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index df3724294a3297ebdc11aef3f935bf0cf36b9c95..89f4c5b2d61e27acd48063f9f24ce9ea91898b8b 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -26,15 +26,22 @@ public class AttributeMap { // Gale end - Lithium - replace AI attributes with optimized collections private final AttributeSupplier supplier; private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations -+ private final net.minecraft.world.entity.LivingEntity entity; // Purpur ++ private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables - public AttributeMap(AttributeSupplier defaultAttributes) { -+ // Purpur start -+ this(defaultAttributes, null); + public AttributeMap(AttributeSupplier supplier) { +- this.supplier = supplier; ++ // Purpur start - Ridables ++ this(supplier, null); + } + public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) { + this.entity = entity; -+ // Purpur end - this.supplier = defaultAttributes; - this.createInstance = attributex -> this.supplier.createInstance(this::onAttributeModified, attributex); // Gale - Airplane - reduce entity allocations ++ // Purpur end - Ridables ++ this.supplier = defaultAttributes; + this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations } private void onAttributeModified(AttributeInstance instance) { this.attributesToUpdate.add(instance); - if (instance.getAttribute().value().isClientSyncable()) { -+ if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur ++ if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables this.attributesToSync.add(instance); } } @@ -3126,230 +2876,230 @@ index e45ed0ffa031f7ab5fa39b71a2678c5a282e0561..7bc3a6f4dabc6411b6ff17e6dbbd190d } public Collection getSyncableAttributes() { -- return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable()).collect(Collectors.toList()); -+ return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute().value()))).collect(Collectors.toList()); // Purpur +- return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable()).collect(Collectors.toList()); ++ return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables } @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -index 386f9bca728055f7b75fb690b307ff4510068105..0bb08af954d224a2b4404615bee720ac4bdbac55 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -@@ -130,7 +130,7 @@ public class DefaultAttributes { +diff --git a/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java +index 33527a1825119f3667fb3c7ccec318f2c7328ec9..e773c426567964fc8269237d71c3434a5473985c 100644 +--- a/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java ++++ b/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java +@@ -131,7 +131,7 @@ public class DefaultAttributes { .put(EntityType.OCELOT, Ocelot.createAttributes().build()) .put(EntityType.PANDA, Panda.createAttributes().build()) .put(EntityType.PARROT, Parrot.createAttributes().build()) - .put(EntityType.PHANTOM, Monster.createMonsterAttributes().build()) -+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur ++ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur - Ridables .put(EntityType.PIG, Pig.createAttributes().build()) .put(EntityType.PIGLIN, Piglin.createAttributes().build()) .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()) -@@ -161,7 +161,7 @@ public class DefaultAttributes { +@@ -162,7 +162,7 @@ public class DefaultAttributes { .put(EntityType.VILLAGER, Villager.createAttributes().build()) .put(EntityType.VINDICATOR, Vindicator.createAttributes().build()) .put(EntityType.WARDEN, Warden.createAttributes().build()) - .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()) -+ .put(EntityType.WANDERING_TRADER, net.minecraft.world.entity.npc.WanderingTrader.createAttributes().build()) // Purpur ++ .put(EntityType.WANDERING_TRADER, net.minecraft.world.entity.npc.WanderingTrader.createAttributes().build()) // Purpur - Villagers follow emerald blocks .put(EntityType.WITCH, Witch.createAttributes().build()) .put(EntityType.WITHER, WitherBoss.createAttributes().build()) .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()) -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java -index f0703302e7dbbda88de8c648d20d87c55ed9b1e0..a913ebabaa5f443afa987b972355a8f8d1723c78 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java +diff --git a/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/net/minecraft/world/entity/ai/attributes/RangedAttribute.java +index a87accd5fe14102e7a2938f991a8ed0b9accd1bb..c7515f7f24e39d6931dcf18574cb47d754983903 100644 +--- a/net/minecraft/world/entity/ai/attributes/RangedAttribute.java ++++ b/net/minecraft/world/entity/ai/attributes/RangedAttribute.java @@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute { @Override public double sanitizeValue(double value) { -+ if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur ++ if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur - Add attribute clamping and armor limit config return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue); } } -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 59732e3b17c20143ecbdc6afa480fdbdc1afc55f..93d17d93922841354fb0bfb15ce43776fafb19d2 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 -@@ -81,7 +81,7 @@ public class AcquirePoi { - }; - // Paper start - optimise POI access - java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); -- io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); -+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), world.purpurConfig.villagerAcquirePoiSearchRadius, world.purpurConfig.villagerAcquirePoiSearchRadius*world.purpurConfig.villagerAcquirePoiSearchRadius, 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); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -index 2ade08d1466660ee1787fa97908002ef56389712..018cc6ff39641157668fca09e64bcddf7d4d3a5c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -@@ -41,17 +41,19 @@ public class HarvestFarmland extends Behavior { +diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +index 751e91a922b20c96f27885c3eb085ec4ae39091b..7f0975f8bd6d5f8ca28f503f93c8cb5c42557420 100644 +--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -94,7 +94,7 @@ public class AcquirePoi { + }; + // Paper start - optimise POI access + final java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); +- io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); ++ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), level.purpurConfig.villagerAcquirePoiSearchRadius, level.purpurConfig.villagerAcquirePoiSearchRadius*level.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); // Purpur - Configurable villager search radius + final Set, BlockPos>> set = new java.util.HashSet<>(poiposes.size()); + for (final Pair, BlockPos> poiPose : poiposes) { + if (predicate.test(level, poiPose.getSecond())) { +diff --git a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +index 4106549bd4dec1cc47d8765be8f5d119fe33bf56..56d49bc71cb0cb0a08ff771991fd77ab774b4b59 100644 +--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java ++++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +@@ -32,6 +32,7 @@ public class HarvestFarmland extends Behavior { private long nextOkStartTime; private int timeWorkedSoFar; private final List validFarmlandAroundVillager = Lists.newArrayList(); -+ private boolean clericWartFarmer = false; // Purpur ++ private boolean clericWartFarmer = false; // Purpur - Option for Villager Clerics to farm Nether Wart public HarvestFarmland() { - super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT)); - } + super( +@@ -48,11 +49,12 @@ public class HarvestFarmland extends Behavior { - protected boolean checkExtraStartConditions(ServerLevel world, Villager entity) { -- if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!world.purpurConfig.villagerBypassMobGriefing == !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur + @Override + protected boolean checkExtraStartConditions(ServerLevel level, Villager owner) { +- if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (!level.purpurConfig.villagerBypassMobGriefing == !level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return false; -- } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER) { -+ } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER && !(world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur +- } else if (owner.getVillagerData().getProfession() != VillagerProfession.FARMER) { ++ } else if (owner.getVillagerData().getProfession() != VillagerProfession.FARMER && !(level.purpurConfig.villagerClericsFarmWarts && owner.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur - Option for Villager Clerics to farm Nether Wart return false; } else { -+ if (!this.clericWartFarmer && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur - BlockPos.MutableBlockPos blockposition_mutableblockposition = entity.blockPosition().mutable(); - ++ if (!this.clericWartFarmer && owner.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur - Option for Villager Clerics to farm Nether Wart + BlockPos.MutableBlockPos mutableBlockPos = owner.blockPosition().mutable(); this.validFarmlandAroundVillager.clear(); -@@ -82,6 +84,7 @@ public class HarvestFarmland extends Behavior { - Block block = iblockdata.getBlock(); - Block block1 = world.getBlockState(pos.below()).getBlock(); -+ if (this.clericWartFarmer) return block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || iblockdata.isAir() && block1 == Blocks.SOUL_SAND; // Purpur - return block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) || iblockdata.isAir() && block1 instanceof FarmBlock; +@@ -83,6 +85,7 @@ public class HarvestFarmland extends Behavior { + BlockState blockState = serverLevel.getBlockState(pos); + Block block = blockState.getBlock(); + Block block1 = serverLevel.getBlockState(pos.below()).getBlock(); ++ if (this.clericWartFarmer) return block == net.minecraft.world.level.block.Blocks.NETHER_WART && blockState.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || blockState.isAir() && block1 == net.minecraft.world.level.block.Blocks.SOUL_SAND; // Purpur - Option for Villager Clerics to farm Nether Wart + return block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState) || blockState.isAir() && block1 instanceof FarmBlock; } -@@ -107,20 +110,20 @@ public class HarvestFarmland extends Behavior { - Block block = iblockdata.getBlock(); - Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); - -- if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { -+ if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) && !this.clericWartFarmer || this.clericWartFarmer && block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur - if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state - world.destroyBlock(this.aboveFarmlandPos, true, entity); +@@ -109,19 +112,19 @@ public class HarvestFarmland extends Behavior { + BlockState blockState = level.getBlockState(this.aboveFarmlandPos); + Block block = blockState.getBlock(); + Block block1 = level.getBlockState(this.aboveFarmlandPos.below()).getBlock(); +- if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState)) { ++ if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState) && !this.clericWartFarmer || this.clericWartFarmer && block == net.minecraft.world.level.block.Blocks.NETHER_WART && blockState.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur - Option for Villager Clerics to farm Nether Wart + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state + level.destroyBlock(this.aboveFarmlandPos, true, owner); } // CraftBukkit } -- if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) { -+ if (iblockdata.isAir() && (block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == Blocks.SOUL_SAND) && entity.hasFarmSeeds()) { // Purpur - SimpleContainer inventorysubcontainer = entity.getInventory(); +- if (blockState.isAir() && block1 instanceof FarmBlock && owner.hasFarmSeeds()) { ++ if (blockState.isAir() && block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == net.minecraft.world.level.block.Blocks.SOUL_SAND && owner.hasFarmSeeds()) { // Purpur - Option for Villager Clerics to farm Nether Wart + SimpleContainer inventory = owner.getInventory(); - for (int j = 0; j < inventorysubcontainer.getContainerSize(); ++j) { - ItemStack itemstack = inventorysubcontainer.getItem(j); + for (int i = 0; i < inventory.getContainerSize(); i++) { + ItemStack item = inventory.getItem(i); boolean flag = false; - -- if (!itemstack.isEmpty() && itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)) { -+ if (!itemstack.isEmpty() && (itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && itemstack.getItem() == net.minecraft.world.item.Items.NETHER_WART)) { - Item item = itemstack.getItem(); - - if (item instanceof BlockItem) { +- if (!item.isEmpty() && item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) && item.getItem() instanceof BlockItem blockItem) { ++ if (!item.isEmpty() && (item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && item.getItem() == net.minecraft.world.item.Items.NETHER_WART) && item.getItem() instanceof BlockItem blockItem) { // Purpur - Option for Villager Clerics to farm Nether Wart + BlockState blockState1 = blockItem.getBlock().defaultBlockState(); + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState1)) { // CraftBukkit + level.setBlockAndUpdate(this.aboveFarmlandPos, blockState1); @@ -136,7 +139,7 @@ public class HarvestFarmland extends Behavior { - } + this.aboveFarmlandPos.getX(), + this.aboveFarmlandPos.getY(), + this.aboveFarmlandPos.getZ(), +- SoundEvents.CROP_PLANTED, ++ this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, // Purpur - Option for Villager Clerics to farm Nether Wart + SoundSource.BLOCKS, + 1.0F, + 1.0F +diff --git a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java +index 296ecffbbce931b42c67ea523373a61cea23acf4..b2eec24be3635f2c19da9b147211fe6cb454c780 100644 +--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java ++++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java +@@ -55,7 +55,7 @@ public class InteractWithDoor { + Node nextNode = path.getNextNode(); + BlockPos blockPos = previousNode.asBlockPos(); + BlockState blockState = level.getBlockState(blockPos); +- if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) { ++ if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)&& !DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone + DoorBlock doorBlock = (DoorBlock)blockState.getBlock(); + if (!doorBlock.isOpen(blockState)) { + // CraftBukkit start - entities opening doors +@@ -72,7 +72,7 @@ public class InteractWithDoor { - if (flag) { -- world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - itemstack.shrink(1); - if (itemstack.isEmpty()) { - inventorysubcontainer.setItem(j, ItemStack.EMPTY); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java -index 3513b15f6622bfc134ecfcd9129f81a8acc2c601..6e70579a58a1bf906b176b81713e55318199cef6 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java -@@ -57,7 +57,7 @@ public class InteractWithDoor { - - if (iblockdata.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { - return blockbase_blockdata.getBlock() instanceof DoorBlock; -- })) { -+ }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition)) { // Purpur - DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); - - if (!blockdoor.isOpen(iblockdata)) { -@@ -79,7 +79,7 @@ public class InteractWithDoor { - - if (iblockdata1.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { - return blockbase_blockdata.getBlock() instanceof DoorBlock; -- })) { -+ }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition1)) { // Purpur - DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock(); - - if (!blockdoor1.isOpen(iblockdata1)) { -@@ -122,7 +122,7 @@ public class InteractWithDoor { - - if (!iblockdata.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { - return blockbase_blockdata.getBlock() instanceof DoorBlock; -- })) { -+ }) || DoorBlock.requiresRedstone(entity.level(), iblockdata, blockposition)) { // Purpur + BlockPos blockPos1 = nextNode.asBlockPos(); + BlockState blockState1 = level.getBlockState(blockPos1); +- if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) { ++ if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock) && !DoorBlock.requiresRedstone(entity.level(), blockState1, blockPos1)) { // Purpur - Option to make doors require redstone + DoorBlock doorBlock1 = (DoorBlock)blockState1.getBlock(); + if (!doorBlock1.isOpen(blockState1)) { + // CraftBukkit start - entities opening doors +@@ -118,7 +118,7 @@ public class InteractWithDoor { + iterator.remove(); + } else { + BlockState blockState = level.getBlockState(blockPos); +- if (!blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) { ++ if (!blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock) || DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone iterator.remove(); } else { - DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java -index 18dad0825616c4167a0a7555689ee64910a87e09..6945992491027d43eca4f1ca697ad45ce06ded55 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java + DoorBlock doorBlock = (DoorBlock)blockState.getBlock(); +diff --git a/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java +index 400e6d49144b3e5803901938dcd2ac4e52e9c131..45c45afeffcfba3558bdf46cbe39ff60004ffc01 100644 +--- a/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java ++++ b/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java @@ -46,6 +46,7 @@ public class ShowTradesToPlayer extends Behavior { @Override - public boolean canStillUse(ServerLevel world, Villager entity, long time) { -+ if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur - return this.checkExtraStartConditions(world, entity) + public boolean canStillUse(ServerLevel level, Villager entity, long gameTime) { ++ if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur - Option for villager display trade item + return this.checkExtraStartConditions(level, entity) && this.lookTime > 0 && entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java -index 8508ac7de8cda3127b73e11ff4aee62502e65ead..b1544e028d5a9b84b944e1fb5a12bb163067fb54 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java +diff --git a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java +index 243ac036f95794e7766aefb4630b635681ae5a5f..4d8523a43d60cd6b4fd5546ffb3a61417b2c475b 100644 +--- a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java ++++ b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java @@ -59,6 +59,12 @@ public class TradeWithVillager extends Behavior { - throwHalfStack(entity, ImmutableSet.of(Items.WHEAT), villager); + throwHalfStack(owner, ImmutableSet.of(Items.WHEAT), villager); } -+ // Purpur start -+ if (world.purpurConfig.villagerClericsFarmWarts && world.purpurConfig.villagerClericFarmersThrowWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC && entity.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getDefaultMaxStackSize() / 2) { -+ throwHalfStack(entity, ImmutableSet.of(Items.NETHER_WART), villager); ++ // Purpur start - Option for Villager Clerics to farm Nether Wart ++ if (level.purpurConfig.villagerClericsFarmWarts && level.purpurConfig.villagerClericFarmersThrowWarts && owner.getVillagerData().getProfession() == VillagerProfession.CLERIC && owner.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getDefaultMaxStackSize() / 2) { ++ throwHalfStack(owner, ImmutableSet.of(Items.NETHER_WART), villager); + } -+ // Purpur end ++ // Purpur end - Option for Villager Clerics to farm Nether Wart + - if (!this.trades.isEmpty() && entity.getInventory().hasAnyOf(this.trades)) { - throwHalfStack(entity, this.trades, villager); + if (!this.trades.isEmpty() && owner.getInventory().hasAnyOf(this.trades)) { + throwHalfStack(owner, this.trades, villager); } -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java -index bb65d46967cb04f611b3c9c97d5732cfb21ede9b..7f4156e4690bbd57f9e9141f008851062cae733d 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java -@@ -52,8 +52,13 @@ public class VillagerGoalPackages { +diff --git a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +index 84afd8646b05409c582f29d73f9fea4b09feb603..32779b121322688a4b14e460b1f902ef67770b32 100644 +--- a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java ++++ b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +@@ -74,8 +74,13 @@ public class VillagerGoalPackages { } - public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed) { -+ // Purpur start -+ return getWorkPackage(profession, speed, false); + public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speedModifier) { ++ // Purpur start - Option for Villager Clerics to farm Nether Wart ++ return getWorkPackage(profession, speedModifier, false); + } -+ public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed, boolean clericsFarmWarts) { -+ // Purpur end ++ public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speedModifier, boolean clericsFarmWarts) { ++ // Purpur end - Option for Villager Clerics to farm Nether Wart WorkAtPoi workAtPoi; - if (profession == VillagerProfession.FARMER) { -+ if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur ++ if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur - Option for Villager Clerics to farm Nether Wart workAtPoi = new WorkAtComposter(); } else { workAtPoi = new WorkAtPoi(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java -index 0a608418f87b71d5d71706712e1f82da0d7e4d34..03e7ca83e4c28dfaa5b52bcb100bd542db105970 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java -@@ -125,8 +125,10 @@ public class VillagerMakeLove extends Behavior { +diff --git a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java +index 4fb63e58eac5d67fcd31c3233dca1dae72b98bc4..dd8d315eba203db121e24e3402f2117fc0f3043f 100644 +--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java ++++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java +@@ -118,8 +118,10 @@ public class VillagerMakeLove extends Behavior { return Optional.empty(); } // Move age setting down - parent.setAge(6000); - partner.setAge(6000); -+ // Purpur start -+ parent.setAge(world.purpurConfig.villagerBreedingTicks); -+ partner.setAge(world.purpurConfig.villagerBreedingTicks); -+ // Purpur end - world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING); ++ // Purpur start - Make entity breeding times configurable ++ parent.setAge(level.purpurConfig.villagerBreedingTicks); ++ partner.setAge(level.purpurConfig.villagerBreedingTicks); ++ // Purpur end - Make entity breeding times configurable + level.addFreshEntityWithPassengers(breedOffspring, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit end - world.broadcastEntityEvent(entityvillager2, (byte) 12); -diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -index c8fd5696de7c3623cdb4f498190a5c2708cf843e..e403d9dfeeaa3dcf53be790d761e7e922419efb0 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -+++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java + level.broadcastEntityEvent(breedOffspring, (byte)12); +diff --git a/net/minecraft/world/entity/ai/control/MoveControl.java b/net/minecraft/world/entity/ai/control/MoveControl.java +index 0f9bf0cb0655a6ed449a86e99b17f89b4e3264df..1860b4ab2314f5da017313977c6423e735a4f96b 100644 +--- a/net/minecraft/world/entity/ai/control/MoveControl.java ++++ b/net/minecraft/world/entity/ai/control/MoveControl.java @@ -29,6 +29,20 @@ public class MoveControl implements Control { - this.mob = entity; + this.mob = mob; } -+ // Purpur start ++ // Purpur start - Ridables + public void setSpeedModifier(double speed) { + this.speedModifier = speed; + } @@ -3361,21 +3111,21 @@ index c8fd5696de7c3623cdb4f498190a5c2708cf843e..e403d9dfeeaa3dcf53be790d761e7e92 + public void setStrafe(float strafe) { + this.strafeRight = strafe; + } -+ // Purpur end ++ // Purpur end - Ridables + public boolean hasWanted() { return this.operation == MoveControl.Operation.MOVE_TO; } -diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -index fbfc2f2515ad709b2c1212aef9521e795547d66b..e77bd11af62682d5eca41f6c9e1aed30eb6879ce 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -+++ b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java +diff --git a/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java +index d7f9b3b2b1077ea10e8f64b87c8f4c4354e90858..713f62b34a91fa76f40e49a5e390145f70755e58 100644 +--- a/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java ++++ b/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java @@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control; import net.minecraft.util.Mth; import net.minecraft.world.entity.Mob; -public class SmoothSwimmingLookControl extends LookControl { -+public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur ++public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables private final int maxYRotFromCenter; private static final int HEAD_TILT_X = 10; private static final int HEAD_TILT_Y = 20; @@ -3384,152 +3134,152 @@ index fbfc2f2515ad709b2c1212aef9521e795547d66b..e77bd11af62682d5eca41f6c9e1aed30 @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (this.lookAtCooldown > 0) { this.lookAtCooldown--; - this.getYRotD().ifPresent(yaw -> this.mob.yHeadRot = this.rotateTowards(this.mob.yHeadRot, yaw + 20.0F, this.yMaxRotSpeed)); -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -index 7324da6b7dd2623ce394e3827ff77ef684a3b98b..d0ba8f74cd0d676640776c46df1913852f4a4a2e 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -@@ -33,7 +33,7 @@ public class BreakDoorGoal extends DoorInteractGoal { - + this.getYRotD().ifPresent(rotationWanted -> this.mob.yHeadRot = this.rotateTowards(this.mob.yHeadRot, rotationWanted + 20.0F, this.yMaxRotSpeed)); +diff --git a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +index e026e07ca86700c864a3dceda6817fb7b6cb11e9..f1dfe0bf047e7d331b2379a672ff7b8eae4c9c90 100644 +--- a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java ++++ b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +@@ -30,7 +30,7 @@ public class BreakDoorGoal extends DoorInteractGoal { @Override public boolean canUse() { -- return !super.canUse() ? false : (!getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); -+ return !super.canUse() ? false : (!this.mob.level().purpurConfig.zombieBypassMobGriefing == !getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); // Purpur + return super.canUse() +- && getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ++ && this.mob.level().purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) // Purpur - Add mobGriefing bypass to everything affected + && this.isValidDifficulty(this.mob.level().getDifficulty()) + && !this.isOpen(); } - - @Override -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 32bb591371fe78ba10a2bc52389ef33978cbc0eb..13f5e5c199688954c263b9e3397e02c9f77bbb92 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 -@@ -74,7 +74,7 @@ public class EatBlockGoal extends Goal { - - final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state - if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur - this.level.destroyBlock(blockposition, false); +diff --git a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index e84893780b533b1ecb3675606b5c2daf7339b861..65eb4d8001b07cb3f7cda17565eea10a88a9c47c 100644 +--- a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -67,7 +67,7 @@ public class EatBlockGoal extends Goal { + BlockPos blockPos = this.mob.blockPosition(); + final BlockState blockState = this.level.getBlockState(blockPos); // Paper - fix wrong block state + if (IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur - Add mobGriefing bypass to everything affected + this.level.destroyBlock(blockPos, false); } -@@ -83,7 +83,7 @@ public class EatBlockGoal extends Goal { - BlockPos blockposition1 = blockposition.below(); - - if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur - this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); - this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); +@@ -75,7 +75,7 @@ public class EatBlockGoal extends Goal { + } else { + BlockPos blockPos1 = blockPos.below(); + if (this.level.getBlockState(blockPos1).is(Blocks.GRASS_BLOCK)) { +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur - Add mobGriefing bypass to everything affected + this.level.levelEvent(2001, blockPos1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); + this.level.setBlock(blockPos1, Blocks.DIRT.defaultBlockState(), 2); } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index df695b444fa2a993d381e2f197182c3e91a68502..0f4f546cd0eda4bd82b47446ae23ac32da8a9556 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +index 6eaf0bd944349cd0c6084462ac385fa2caafe933..be59d0c27a83b329ec3f97c029cfb9c114e22472 100644 +--- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java ++++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canUse() { -+ if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur ++ if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur - Llama API // Purpur - Config to disable Llama caravans if (!this.llama.isLeashed() && !this.llama.inCaravan()) { - List list = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity -> { - EntityType entityType = entity.getType(); + List entities = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity1 -> { + EntityType type = entity1.getType(); @@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canContinueToUse() { -+ if (!this.llama.shouldJoinCaravan) return false; // Purpur ++ if (!this.llama.shouldJoinCaravan) return false; // Purpur - Llama API if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) { double d = this.llama.distanceToSqr(this.llama.getCaravanHead()); if (d > 676.0) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -index 9d245d08be61d7edee9138196ae3bf52023e3993..771bb96032149a8573d1de14fa2ab19012c82000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -@@ -41,7 +41,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { +diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +index 579ca031d461ed4327fe4fb45c5289565322e64e..95fa516910a3834bbd4db6d11279e13a1f0dac41 100644 +--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +@@ -35,7 +35,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { @Override public boolean canUse() { -- if (!getServerLevel((Entity) this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel((Entity) this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur +- if (!getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return false; } else if (this.nextStartTick > 0) { - --this.nextStartTick; -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java -index b0944fa1f3849dd24cd010fa0a6638f5fd7179d1..d409ae987088df3d47192128401d7491aaabc87c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java -@@ -67,7 +67,7 @@ public class RunAroundLikeCrazyGoal extends Goal { - int i = this.horse.getTemper(); - int j = this.horse.getMaxTemper(); - -- if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent -+ if ((this.horse.level().purpurConfig.alwaysTameInCreative && entityhuman.hasInfiniteMaterials()) || (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled())) { // CraftBukkit - fire EntityTameEvent // Purpur - this.horse.tameWithName(entityhuman); + this.nextStartTick--; +diff --git a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +index 878d7813b3f2f52bef336c6d855d738bc2f83491..0752a8c6308f9f74ffe177d7a1decde2227228f9 100644 +--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java ++++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +@@ -58,7 +58,7 @@ public class RunAroundLikeCrazyGoal extends Goal { + if (firstPassenger instanceof Player player) { + int temper = this.horse.getTemper(); + int maxTemper = this.horse.getMaxTemper(); +- if (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent ++ if (this.horse.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials() || maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent // Purpur - Config to always tame in Creative + this.horse.tameWithName(player); return; } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -index 137ec75ee803789deb7b1ca93dd9369c9af362b9..ca95d25af3e9a0536868b0c7fd8e7d2ff1154ee3 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -@@ -54,6 +54,14 @@ public class SwellGoal extends Goal { +diff --git a/net/minecraft/world/entity/ai/goal/SwellGoal.java b/net/minecraft/world/entity/ai/goal/SwellGoal.java +index 243a552f6f0c8c2bd25c0209c95e3bca08734711..38fd0196a0f5a90e39fa4eb8592f89bf6b88ccf5 100644 +--- a/net/minecraft/world/entity/ai/goal/SwellGoal.java ++++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java +@@ -55,6 +55,14 @@ public class SwellGoal extends Goal { this.creeper.setSwellDir(-1); } else { this.creeper.setSwellDir(1); -+ // Purpur start ++ // Purpur start - option to allow creeper to encircle target when fusing + if (this.creeper.level().purpurConfig.creeperEncircleTarget) { + net.minecraft.world.phys.Vec3 relative = this.creeper.position().subtract(this.target.position()); + relative = relative.yRot((float) Math.PI / 3).normalize().multiply(2, 2, 2); + net.minecraft.world.phys.Vec3 destination = this.target.position().add(relative); + this.creeper.getNavigation().moveTo(destination.x, destination.y, destination.z, 1); + } -+ // Purpur end ++ // Purpur end - option to allow creeper to encircle target when fusing } } } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java -index 84ab90dd1fe693da71732533ccff940c1007e1b6..179fdf7b7f68db610925a9d7b88879289b1b98b8 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -67,7 +67,7 @@ public class TemptGoal extends Goal { +diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java +index 438d6347778a94b4fe430320b268a2d67afa209a..f88f618d34fb343b31de3af1a875d6633703df71 100644 +--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java ++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java +@@ -58,7 +58,7 @@ public class TemptGoal extends Goal { } private boolean shouldFollow(LivingEntity entity) { - return this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem()); -+ return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur Fix #512 ++ return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur - Villagers follow emerald blocks } @Override -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 92731b6b593289e9f583c9b705b219e81fcd8e73..9104d7010bda6f9f73b478c11490ef9c53f76da2 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 +diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +index 066faa704338c573472381e1ebd063e0d52aaaa4..1f96fd5085bacb4c584576c7cb9f51e7898e9b03 100644 +--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java @@ -56,7 +56,7 @@ public class NearestBedSensor extends Sensor { // 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); -+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), world.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur ++ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), level.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur - Configurable villager search radius Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); // Paper end - optimise POI access if (path != null && path.canReach()) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java -index 1595568f3140a62b0f2236644ac2da11db12af05..d548d1b2686667d809f363cd0ae4444bc3918bf2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +diff --git a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +index 05c664732adfca7d18bfcbebae9bb455f001aa93..6f96551ba91da214054b89a255254ca597977cc0 100644 +--- a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java @@ -29,6 +29,13 @@ public class SecondaryPoiSensor extends Sensor { return; } // Gale end - Lithium - skip secondary POI sensor if absent -+ // Purpur start - make sure clerics don't wander to soul sand when the option is off ++ // Purpur start - Option for Villager Clerics to farm Nether Wart - make sure clerics don't wander to soul sand when the option is off + Brain brain = entity.getBrain(); -+ if (!world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { ++ if (!level.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { + brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); + return; + } -+ // Purpur end - ResourceKey resourceKey = world.dimension(); ++ // Purpur end - Option for Villager Clerics to farm Nether Wart + ResourceKey resourceKey = level.dimension(); BlockPos blockPos = entity.blockPosition(); List list = Lists.newArrayList(); @@ -45,7 +52,7 @@ public class SecondaryPoiSensor extends Sensor { @@ -3537,41 +3287,40 @@ index 1595568f3140a62b0f2236644ac2da11db12af05..d548d1b2686667d809f363cd0ae4444b } - Brain brain = entity.getBrain(); -+ //Brain brain = entity.getBrain(); // Purpur - moved up ++ //Brain brain = entity.getBrain(); // Purpur - Option for Villager Clerics to farm Nether Wart - moved up if (!list.isEmpty()) { brain.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, list); } else { -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 b51a04d3e006bc770006cff790791bc0f6bee77d..886ca1c8a22714bc299ad08659e5279281669bb3 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 +diff --git a/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +index 17a08a3af468093668a41f154c2beb69c6617efa..398a97a72dca785204f6b7b8fc4abe5cd64de51e 100644 +--- a/net/minecraft/world/entity/ai/targeting/TargetingConditions.java ++++ b/net/minecraft/world/entity/ai/targeting/TargetingConditions.java @@ -64,6 +64,10 @@ public class TargetingConditions { return false; - } else if (this.selector != null && !this.selector.test(target, world)) { + } else if (this.selector != null && !this.selector.test(target, level)) { return false; -+ // Purpur start -+ } else if (!world.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) { ++ // Purpur start - AFK API ++ } else if (!level.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) { + return false; -+ // Purpur end ++ // Purpur end - AFK API } else { - if (tester == null) { - if (this.isCombat && (!target.canBeSeenAsEnemy() || world.getDifficulty() == Difficulty.PEACEFUL)) { -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 05ab901becfc7ffe4e4483ac2c7acb2e2a72490f..75445d2fa84620f3d27235a941107db199f563d9 100644 ---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java -+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -@@ -47,12 +47,59 @@ public class Bat extends AmbientCreature { + if (entity == null) { + if (this.isCombat && (!target.canBeSeenAsEnemy() || level.getDifficulty() == Difficulty.PEACEFUL)) { +diff --git a/net/minecraft/world/entity/ambient/Bat.java b/net/minecraft/world/entity/ambient/Bat.java +index 62085eecd2bb55721208c5fd126aaae56a50ed6b..e042a4df5a9ad16751f64ce71537969932ae81fd 100644 +--- a/net/minecraft/world/entity/ambient/Bat.java ++++ b/net/minecraft/world/entity/ambient/Bat.java +@@ -42,11 +42,87 @@ public class Bat extends AmbientCreature { - public Bat(EntityType type, Level world) { - super(type, world); -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur - if (!world.isClientSide) { + public Bat(EntityType entityType, Level level) { + super(entityType, level); ++ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur - Ridables + if (!level.isClientSide) { this.setResting(true); } - } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED.value(); } // Fixes log spam on clients + @@ -3596,7 +3345,7 @@ index 05ab901becfc7ffe4e4483ac2c7acb2e2a72490f..75445d2fa84620f3d27235a941107db1 + } + + @Override -+ public void onMount(Player rider) { ++ public void onMount(net.minecraft.world.entity.player.Player rider) { + super.onMount(rider); + if (isResting()) { + setResting(false); @@ -3615,39 +3364,9 @@ index 05ab901becfc7ffe4e4483ac2c7acb2e2a72490f..75445d2fa84620f3d27235a941107db1 + setDeltaMovement(mot.scale(0.9D)); + } + } -+ // Purpur end ++ // Purpur end - Ridables + - @Override - public boolean isFlapping() { - return !this.isResting() && (float) this.tickCount % 10.0F == 0.0F; -@@ -102,7 +149,7 @@ public class Bat extends AmbientCreature { - protected void pushEntities() {} - - public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D); -+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - } - - public boolean isResting() { -@@ -135,6 +182,14 @@ public class Bat extends AmbientCreature { - - @Override - protected void customServerAiStep(ServerLevel world) { -+ // Purpur start -+ if (getRider() != null && this.isControllable()) { -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); -+ return; -+ } -+ // Purpur end -+ - super.customServerAiStep(world); - BlockPos blockposition = this.blockPosition(); - BlockPos blockposition1 = blockposition.above(); -@@ -213,6 +268,29 @@ public class Bat extends AmbientCreature { - } - } - ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth); @@ -3660,198 +3379,226 @@ index 05ab901becfc7ffe4e4483ac2c7acb2e2a72490f..75445d2fa84620f3d27235a941107db1 + this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness); + this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.batTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.batAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); -@@ -232,7 +310,7 @@ public class Bat extends AmbientCreature { - int i = world.getMaxLocalRawBrightness(pos); - byte b0 = 4; - -- if (Bat.isHalloween()) { -+ if (Bat.isHalloweenSeason(world.getMinecraftWorld())) { // Purpur - b0 = 7; - } else if (random.nextBoolean()) { - return false; -@@ -242,6 +320,8 @@ public class Bat extends AmbientCreature { - } + public boolean isFlapping() { + return !this.isResting() && this.tickCount % 10.0F == 0.0F; +@@ -98,7 +174,7 @@ public class Bat extends AmbientCreature { } -+ public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur + public static AttributeSupplier.Builder createAttributes() { +- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0); ++ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables + } + + public boolean isResting() { +@@ -129,6 +205,14 @@ public class Bat extends AmbientCreature { + + @Override + protected void customServerAiStep(ServerLevel level) { ++ // Purpur start - Ridables ++ if (getRider() != null && this.isControllable()) { ++ Vec3 mot = getDeltaMovement(); ++ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); ++ return; ++ } ++ // Purpur end - Ridables + - // Gale start - predict Halloween - /** - * The 1-indexed month of the year that Halloween starts (inclusive). -diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -index 9aedc62b1766f6a7db4da7eba55167d21d698791..9708ed3e00059fdf5d1d60e0c607d0ab153d1d3f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java + super.customServerAiStep(level); + BlockPos blockPos = this.blockPosition(); + BlockPos blockPos1 = blockPos.above(); +@@ -231,7 +315,7 @@ public class Bat extends AmbientCreature { + } else { + int maxLocalRawBrightness = level.getMaxLocalRawBrightness(pos); + int i = 4; +- if (isHalloween()) { ++ if (Bat.isHalloweenSeason(level.getMinecraftWorld())) { // Purpur - Halloween options and optimizations + i = 7; + } else if (randomSource.nextBoolean()) { + return false; +@@ -276,6 +360,8 @@ public class Bat extends AmbientCreature { + */ + private static long nextHalloweenEnd = 0; + ++ public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur - Halloween options and optimizations ++ + // The Halloween begins at 10/20 0:00, and end with 11/04 0:00 + // Only when the current Halloween period ends, the `nextHalloweenStart` + // and `nextHalloweenEnd` will adjust to the epoch ms of date of next year +diff --git a/net/minecraft/world/entity/animal/AbstractFish.java b/net/minecraft/world/entity/animal/AbstractFish.java +index c0997c8c0f8ee4474d3acdd5938b1879c4e589a2..28ae152125ed83d8917674b6068f227f87890f30 100644 +--- a/net/minecraft/world/entity/animal/AbstractFish.java ++++ b/net/minecraft/world/entity/animal/AbstractFish.java @@ -87,6 +87,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { @Override protected void registerGoals() { super.registerGoals(); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(0, new PanicGoal(this, 1.25)); this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6, 1.4, EntitySelector.NO_SPECTATORS::test)); this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this)); @@ -100,7 +101,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { @Override - public void travel(Vec3 movementInput) { + public void travel(Vec3 travelVector) { if (this.isControlledByLocalInstance() && this.isInWater()) { -- this.moveRelative(0.01F, movementInput); -+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur +- this.moveRelative(0.01F, travelVector); ++ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, travelVector); // Purpur - Ridables this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.9)); if (this.getTarget() == null) { -@@ -161,7 +162,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - protected void playStepSound(BlockPos pos, BlockState state) { +@@ -160,7 +161,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { + protected void playStepSound(BlockPos pos, BlockState block) { } - static class FishMoveControl extends MoveControl { -+ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur ++ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables private final AbstractFish fish; - FishMoveControl(AbstractFish owner) { -@@ -169,14 +170,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - this.fish = owner; + FishMoveControl(AbstractFish mob) { +@@ -168,14 +169,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { + this.fish = mob; } -+ // Purpur start ++ // Purpur start - Ridables @Override - public void tick() { + public void purpurTick(Player rider) { + super.purpurTick(rider); + fish.setDeltaMovement(fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); + } -+ // Purpur end ++ // Purpur end - Ridables + + @Override -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (this.fish.isEyeInFluid(FluidTags.WATER)) { this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0, 0.005, 0.0)); } if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) { - float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur ++ float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f)); double d = this.wantedX - this.fish.getX(); - double e = this.wantedY - this.fish.getY(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java -index 5677dc97ed83652f261100cf391883cfac7d16fe..9987d28ea145f6d0126cb4ea22001e0922fb51bd 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Animal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java -@@ -49,6 +49,7 @@ public abstract class Animal extends AgeableMob { + double d1 = this.wantedY - this.fish.getY(); +diff --git a/net/minecraft/world/entity/animal/Animal.java b/net/minecraft/world/entity/animal/Animal.java +index e34e3b949676aa28dd7c82a47f2ed3b44ad200e3..fa34e7f1c20dfd569b52a9c8e0a8d4d5e659ce20 100644 +--- a/net/minecraft/world/entity/animal/Animal.java ++++ b/net/minecraft/world/entity/animal/Animal.java +@@ -40,6 +40,7 @@ public abstract class Animal extends AgeableMob { @Nullable public UUID loveCause; public ItemStack breedItem; // CraftBukkit - Add breedItem variable -+ public abstract int getPurpurBreedTime(); // Purpur ++ public abstract int getPurpurBreedTime(); // Purpur - Make entity breeding times configurable - protected Animal(EntityType type, Level world) { - super(type, world); -@@ -157,7 +158,7 @@ public abstract class Animal extends AgeableMob { - if (this.isFood(itemstack)) { - int i = this.getAge(); - -- if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { -+ if (!this.level().isClientSide && i == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur - final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying - this.usePlayerItem(player, hand, itemstack); + protected Animal(EntityType entityType, Level level) { + super(entityType, level); +@@ -142,7 +143,7 @@ public abstract class Animal extends AgeableMob { + ItemStack itemInHand = player.getItemInHand(hand); + if (this.isFood(itemInHand)) { + int age = this.getAge(); +- if (!this.level().isClientSide && age == 0 && this.canFallInLove()) { ++ if (!this.level().isClientSide && age == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur - Add adjustable breeding cooldown to config + final ItemStack breedCopy = itemInHand.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemInHand); this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying -@@ -261,12 +262,20 @@ public abstract class Animal extends AgeableMob { - AgeableMob entityageable = this.getBreedOffspring(world, other); - - if (entityageable != null) { -- entityageable.setBaby(true); -- entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); -- // CraftBukkit start - call EntityBreedEvent -+ // Purpur start - ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> { - return Optional.ofNullable(other.getLoveCause()); - }).orElse(null); -+ if (breeder != null && world.purpurConfig.animalBreedingCooldownSeconds > 0) { -+ if (world.hasBreedingCooldown(breeder.getUUID(), this.getClass())) { +@@ -239,10 +240,20 @@ public abstract class Animal extends AgeableMob { + public void spawnChildFromBreeding(ServerLevel level, Animal mate) { + AgeableMob breedOffspring = this.getBreedOffspring(level, mate); + if (breedOffspring != null) { +- breedOffspring.setBaby(true); +- breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); ++ //breedOffspring.setBaby(true); // Purpur - Add adjustable breeding cooldown to config - moved down ++ //breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); // Purpur - Add adjustable breeding cooldown to config - moved down + // CraftBukkit start - call EntityBreedEvent + ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(mate.getLoveCause())).orElse(null); ++ // Purpur start - Add adjustable breeding cooldown to config ++ if (breeder != null && level.purpurConfig.animalBreedingCooldownSeconds > 0) { ++ if (level.hasBreedingCooldown(breeder.getUUID(), this.getClass())) { + return; + } -+ world.addBreedingCooldown(breeder.getUUID(), this.getClass()); ++ level.addBreedingCooldown(breeder.getUUID(), this.getClass()); + } -+ entityageable.setBaby(true); -+ entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); -+ // CraftBukkit start - call EntityBreedEvent -+ // Purpur end ++ breedOffspring.setBaby(true); ++ breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); ++ // Purpur end - Add adjustable breeding cooldown to config int experience = this.getRandom().nextInt(7) + 1; - EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience); + org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(breedOffspring, this, mate, breeder, this.breedItem, experience); if (entityBreedEvent.isCancelled()) { -@@ -294,8 +303,10 @@ public abstract class Animal extends AgeableMob { - entityplayer.awardStat(Stats.ANIMALS_BRED); - CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable); +@@ -269,8 +280,10 @@ public abstract class Animal extends AgeableMob { + player.awardStat(Stats.ANIMALS_BRED); + CriteriaTriggers.BRED_ANIMALS.trigger(player, this, animal, baby); } // Paper - this.setAge(6000); -- entityanimal.setAge(6000); -+ // Purpur start +- animal.setAge(6000); ++ // Purpur start - Make entity breeding times configurable + this.setAge(this.getPurpurBreedTime()); -+ entityanimal.setAge(entityanimal.getPurpurBreedTime()); -+ // Purpur end ++ animal.setAge(animal.getPurpurBreedTime()); ++ // Purpur end - Make entity breeding times configurable this.resetLove(); - entityanimal.resetLove(); - worldserver.broadcastEntityEvent(this, (byte) 18); -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 42276acfeadec6e7aa9a91d3f446f4fedb04829d..dc8df0912c1d18176e18a8f4dc43c4f60f81b659 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -150,6 +150,7 @@ 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); -+ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur + animal.resetLove(); + level.broadcastEntityEvent(this, (byte)18); +diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java +index 94244b148533ef026bf5c56abbc2bb5cfa83c938..57c50ce5724b073b1aedf4df3129285143097303 100644 +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -145,6 +145,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + public Bee(EntityType entityType, Level level) { + super(entityType, level); ++ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur - Ridables // Paper start - Fix MC-167279 class BeeFlyingMoveControl extends FlyingMoveControl { public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { -@@ -158,22 +159,69 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -153,22 +154,69 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { @Override public void tick() { -+ // Purpur start ++ // Purpur start - Ridables + if (mob.getRider() != null && mob.isControllable()) { + flyingController.purpurTick(mob.getRider()); + return; + } -+ // Purpur end ++ // Purpur end - Ridables if (this.mob.getY() <= Bee.this.level().getMinY()) { this.mob.setNoGravity(false); } super.tick(); } + -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean hasWanted() { + return mob.getRider() != null || !mob.isControllable() || super.hasWanted(); + } -+ // Purpur end ++ // Purpur end - Ridables } this.moveControl = new BeeFlyingMoveControl(this, 20, true); // Paper end - Fix MC-167279 this.lookControl = new Bee.BeeLookControl(this); this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(PathType.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur ++ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage this.setPathfindingMalus(PathType.WATER_BORDER, 16.0F); this.setPathfindingMalus(PathType.COCOA, -1.0F); this.setPathfindingMalus(PathType.FENCE, -1.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.beeRidable; @@ -3883,125 +3630,130 @@ index 42276acfeadec6e7aa9a91d3f446f4fedb04829d..dc8df0912c1d18176e18a8f4dc43c4f6 + setDeltaMovement(mot.scale(0.9D)); + } + } -+ // Purpur end ++ // Purpur end - Ridables + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -@@ -188,6 +236,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -183,6 +231,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.399999976158142D, true)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.4F, true)); this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal()); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); -@@ -207,6 +256,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +@@ -200,6 +249,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.goalSelector.addGoal(7, new Bee.BeeGrowCropGoal()); this.goalSelector.addGoal(8, new Bee.BeeWanderGoal()); this.goalSelector.addGoal(9, new FloatGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new Bee.BeeHurtByOtherGoal(this)).setAlertOthers(new Class[0])); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new Bee.BeeHurtByOtherGoal(this).setAlertOthers(new Class[0])); this.targetSelector.addGoal(2, new Bee.BeeBecomeAngryTargetGoal(this)); this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); -@@ -376,7 +426,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -365,7 +415,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { } - public static boolean isNightOrRaining(Level world) { -- return world.dimensionType().hasSkyLight() && (world.isNight() || world.isRaining()); -+ return world.dimensionType().hasSkyLight() && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)); // Purpur + public static boolean isNightOrRaining(Level level) { +- return level.dimensionType().hasSkyLight() && (level.isNight() || level.isRaining()); ++ return level.dimensionType().hasSkyLight() && (level.isNight() && !level.purpurConfig.beeCanWorkAtNight || level.isRaining() && !level.purpurConfig.beeCanWorkInRain); // Purpur - Bee can work when raining or at night } - public void setStayOutOfHiveCountdown(int cannotEnterHiveTicks) { -@@ -411,6 +461,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.hurtServer(world, this.damageSources().drown(), 1.0F); + public void setStayOutOfHiveCountdown(int stayOutOfHiveCountdown) { +@@ -398,6 +448,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + this.hurtServer(level, this.damageSources().drown(), 1.0F); } -+ if (flag && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur - if (flag) { - ++this.timeSinceSting; ++ if (hasStung && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur - Stop bees from dying after stinging + if (hasStung) { + this.timeSinceSting++; if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) { -@@ -435,6 +486,27 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - return tileentitybeehive != null && tileentitybeehive.isFireNearby(); +@@ -421,6 +472,33 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + return beehiveBlockEntity != null && beehiveBlockEntity.isFireNearby(); } ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.beeScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.beeBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable ++ ++ // Purpur start - Mobs always drop experience ++ @Override ++ protected boolean isAlwaysExperienceDropper() { ++ return this.level().purpurConfig.beeAlwaysDropExp; ++ } ++ // Purpur end - Mobs always drop experience + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.beeTakeDamageFromWater; + } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.beeAlwaysDropExp; -+ } + @Override public int getRemainingPersistentAngerTime() { - return (Integer) this.entityData.get(Bee.DATA_REMAINING_ANGER_TIME); -@@ -701,16 +773,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.hivePos = pos; + return this.entityData.get(DATA_REMAINING_ANGER_TIME); +@@ -1083,15 +1161,15 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + } } -- private class BeeLookControl extends LookControl { -+ private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - - BeeLookControl(final Mob entity) { - super(entity); +- class BeeLookControl extends LookControl { ++ class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables + BeeLookControl(final Mob mob) { + super(mob); } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (!Bee.this.isAngry()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } } -@@ -882,6 +954,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - if (optional.isPresent()) { - Bee.this.savedFlowerPos = (BlockPos) optional.get(); - Bee.this.navigation.moveTo((double) Bee.this.savedFlowerPos.getX() + 0.5D, (double) Bee.this.savedFlowerPos.getY() + 0.5D, (double) Bee.this.savedFlowerPos.getZ() + 0.5D, 1.2000000476837158D); -+ new org.purpurmc.purpur.event.entity.BeeFoundFlowerEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur +@@ -1136,6 +1214,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + Bee.this.savedFlowerPos = optional.get(); + Bee.this.navigation + .moveTo(Bee.this.savedFlowerPos.getX() + 0.5, Bee.this.savedFlowerPos.getY() + 0.5, Bee.this.savedFlowerPos.getZ() + 0.5, 1.2F); ++ new org.purpurmc.purpur.event.entity.BeeFoundFlowerEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - Bee API return true; } else { Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60); -@@ -925,6 +998,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -1182,6 +1261,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.pollinating = false; Bee.this.navigation.stop(); Bee.this.remainingCooldownBeforeLocatingNewFlower = 200; -+ new org.purpurmc.purpur.event.entity.BeeStopPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), Bee.this.savedFlowerPos == null ? null : io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos), Bee.this.hasNectar()).callEvent(); // Purpur ++ new org.purpurmc.purpur.event.entity.BeeStopPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), Bee.this.savedFlowerPos == null ? null : io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos), Bee.this.hasNectar()).callEvent(); // Purpur - Bee API } @Override -@@ -974,6 +1048,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -1228,6 +1308,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.setWantedPos(); } -+ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - ++this.successfulPollinatingTicks; ++ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - Bee API + this.successfulPollinatingTicks++; if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) { this.lastSoundPlayedTick = this.successfulPollinatingTicks; -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 471d5727b964922d8e898be9e1d5c30f9d3bac97..4aad4fdc80070f4000e929fff126714fc67050b0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -100,12 +100,59 @@ public class Cat extends TamableAnimal implements VariantHolder { - return itemstack.is(ItemTags.CAT_FOOD); - }, true); + this.temptGoal = new Cat.CatTemptGoal(this, 0.6, itemStack -> itemStack.is(ItemTags.CAT_FOOD), true); this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5D)); ++ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5)); this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this)); -@@ -118,6 +165,7 @@ public class Cat extends TamableAnimal implements VariantHolder(this, Rabbit.class, false, (TargetingConditions.Selector) null)); ++ this.targetSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, null)); this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); } -@@ -317,6 +365,14 @@ public class Cat extends TamableAnimal implements VariantHolder { - return itemstack.is(ItemTags.CHICKEN_FOOD); -@@ -65,6 +107,14 @@ public class Chicken extends Animal { - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); +- this.goalSelector.addGoal(1, new PanicGoal(this, 1.4)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables ++ //this.goalSelector.addGoal(1, new PanicGoal(this, 1.4)); // Purpur - Chickens can retaliate - moved down + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); + this.goalSelector.addGoal(3, new TemptGoal(this, 1.0, itemStack -> itemStack.is(ItemTags.CHICKEN_FOOD), false)); + this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); -+ // Purpur start ++ // Purpur start - Chickens can retaliate + if (level().purpurConfig.chickenRetaliate) { + this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false)); + this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this)); + } else { + this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); + } -+ // Purpur end ++ // Purpur end - Chickens can retaliate } @Override -@@ -73,7 +123,7 @@ public class Chicken extends Animal { +@@ -69,7 +129,7 @@ public class Chicken extends Animal { } public static AttributeSupplier.Builder createAttributes() { -- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D); -+ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur +- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0).add(Attributes.MOVEMENT_SPEED, 0.25); ++ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0).add(Attributes.MOVEMENT_SPEED, 0.25).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur - Chickens can retaliate } @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cod.java b/src/main/java/net/minecraft/world/entity/animal/Cod.java -index 824e5e4fe7619ae46061c3c978c9a044db8c84ab..f0b6118a9995bb41836685bbf94d2e7fb15761eb 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cod.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cod.java -@@ -13,6 +13,33 @@ public class Cod extends AbstractSchoolingFish { - super(type, world); +diff --git a/net/minecraft/world/entity/animal/Cod.java b/net/minecraft/world/entity/animal/Cod.java +index 708bcc39e7242292d5d5bfcaf599e3738628df9b..b259de78198e0e3df9e5901019283ad246c8e044 100644 +--- a/net/minecraft/world/entity/animal/Cod.java ++++ b/net/minecraft/world/entity/animal/Cod.java +@@ -13,6 +13,39 @@ public class Cod extends AbstractSchoolingFish { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.codRidable; @@ -4197,43 +3964,49 @@ index 824e5e4fe7619ae46061c3c978c9a044db8c84ab..f0b6118a9995bb41836685bbf94d2e7f + public boolean isControllable() { + return level().purpurConfig.codControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.codMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.codTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.codAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override public ItemStack getBucketItemStack() { return new ItemStack(Items.COD_BUCKET); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java -index 3e00bbff266fc71b07014e7e047d77b7f809239f..6b517deec01445de4205eedb1557995a92d3ae67 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java -@@ -37,6 +37,7 @@ import org.bukkit.event.player.PlayerBucketFillEvent; - // CraftBukkit end +diff --git a/net/minecraft/world/entity/animal/Cow.java b/net/minecraft/world/entity/animal/Cow.java +index befb99f0a96cb23f139061f92497737e9203a8fd..a0297ac3ba520122ed2095d6008c057d749b731e 100644 +--- a/net/minecraft/world/entity/animal/Cow.java ++++ b/net/minecraft/world/entity/animal/Cow.java +@@ -32,22 +32,82 @@ import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockState; public class Cow extends Animal { -+ private boolean isNaturallyAggressiveToPlayers; // Purpur - ++ private boolean isNaturallyAggressiveToPlayers; // Purpur - Cows naturally aggressive to players chance ++ private static final EntityDimensions BABY_DIMENSIONS = EntityType.COW.getDimensions().scale(0.5F).withEyeHeight(0.665F); -@@ -44,18 +45,66 @@ public class Cow extends Animal { - super(type, world); + public Cow(EntityType entityType, Level level) { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.cowRidable; @@ -4248,98 +4021,104 @@ index 3e00bbff266fc71b07014e7e047d77b7f809239f..6b517deec01445de4205eedb1557995a + public boolean isControllable() { + return level().purpurConfig.cowControllable; + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.cowScale); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur ++ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur - Cows naturally aggressive to players chance + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.cowBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.cowTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Cows naturally aggressive to players chance + @Override + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, net.minecraft.world.entity.SpawnGroupData entityData) { + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance; + return super.finalizeSpawn(world, difficulty, spawnReason, entityData); + } ++ // Purpur end - Cows naturally aggressive to players chance + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.cowAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D)); -+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, (itemstack) -> { -- return itemstack.is(ItemTags.COW_FOOD); -+ return level().purpurConfig.cowFeedMushrooms > 0 && (itemstack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemstack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemstack.is(ItemTags.COW_FOOD); // Purpur - }, false)); - this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 2.0)); ++ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - Cows naturally aggressive to players chance + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); +- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, itemStack -> itemStack.is(ItemTags.COW_FOOD), false)); ++ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, itemStack -> level().purpurConfig.cowFeedMushrooms > 0 && (itemStack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemStack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemStack.is(ItemTags.COW_FOOD), false)); // Purpur - Cows eat mushrooms + this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur ++ this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - Cows naturally aggressive to players chance } @Override -@@ -64,7 +113,7 @@ public class Cow extends Animal { +@@ -56,7 +116,7 @@ public class Cow extends Animal { } public static AttributeSupplier.Builder createAttributes() { -- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D); -+ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur +- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.MOVEMENT_SPEED, 0.2F); ++ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.MOVEMENT_SPEED, 0.2F).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur - Cows naturally aggressive to players chance } @Override -@@ -94,6 +143,7 @@ public class Cow extends Animal { +@@ -86,19 +146,24 @@ public class Cow extends Animal { @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { -+ if (getRider() != null) return InteractionResult.PASS; // Purpur - ItemStack itemstack = player.getItemInHand(hand); - - if (itemstack.is(Items.BUCKET) && !this.isBaby()) { -@@ -102,7 +152,7 @@ public class Cow extends Animal { - ++ if (getRider() != null) return InteractionResult.PASS; // Purpur - Ridables + ItemStack itemInHand = player.getItemInHand(hand); + if (itemInHand.is(Items.BUCKET) && !this.isBaby()) { + // CraftBukkit start - Got milk? + org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemInHand, Items.MILK_BUCKET, hand); if (event.isCancelled()) { player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } // CraftBukkit end - -@@ -111,6 +161,10 @@ public class Cow extends Animal { - - player.setItemInHand(hand, itemstack1); + player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F); + ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit + player.setItemInHand(hand, itemStack); return InteractionResult.SUCCESS; -+ // Purpur start - feed mushroom to change to mooshroom -+ } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemstack)) { -+ return this.feedMushroom(player, itemstack); -+ // Purpur end ++ // Purpur start - Cows eat mushrooms - feed mushroom to change to mooshroom ++ } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemInHand)) { ++ return this.feedMushroom(player, itemInHand); ++ // Purpur end - Cows eat mushrooms } else { return super.mobInteract(player, hand); } -@@ -126,4 +180,66 @@ public class Cow extends Animal { +@@ -114,4 +179,67 @@ public class Cow extends Animal { public EntityDimensions getDefaultDimensions(Pose pose) { - return this.isBaby() ? Cow.BABY_DIMENSIONS : super.getDefaultDimensions(pose); + return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose); } + -+ // Purpur start - feed mushroom to change to mooshroom ++ // Purpur start - Cows eat mushrooms - feed mushroom to change to mooshroom + private int redMushroomsFed = 0; + private int brownMushroomsFed = 0; + @@ -4384,7 +4163,7 @@ index 3e00bbff266fc71b07014e7e047d77b7f809239f..6b517deec01445de4205eedb1557995a + if (this.hasCustomName()) { + mooshroom.setCustomName(this.getCustomName()); + } -+ if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { + return InteractionResult.PASS; + } + this.level().addFreshEntity(mooshroom); @@ -4393,29 +4172,30 @@ index 3e00bbff266fc71b07014e7e047d77b7f809239f..6b517deec01445de4205eedb1557995a + stack.shrink(1); + } + for (int i = 0; i < 15; ++i) { -+ ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER, ++ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER, ++ false, true, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, -+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); ++ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0); + } + return InteractionResult.SUCCESS; + } -+ // Purpur end ++ // Purpur end - Cows eat mushrooms } -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 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..c1842894f96a567707992d8ff938dbf689dd0df6 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -@@ -85,14 +85,99 @@ public class Dolphin extends AgeableWaterCreature { - return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater(); - }; +diff --git a/net/minecraft/world/entity/animal/Dolphin.java b/net/minecraft/world/entity/animal/Dolphin.java +index 4141052dfd635804195a5cfa24dbd0394355a7da..7003b532182737a745491e397a967b72e6b308aa 100644 +--- a/net/minecraft/world/entity/animal/Dolphin.java ++++ b/net/minecraft/world/entity/animal/Dolphin.java +@@ -71,14 +71,105 @@ public class Dolphin extends AgeableWaterCreature { + private static final int TOTAL_MOISTNESS_LEVEL = 2400; + public static final Predicate ALLOWED_ITEMS = itemEntity -> !itemEntity.hasPickUpDelay() && itemEntity.isAlive() && itemEntity.isInWater(); public static final float BABY_SCALE = 0.65F; -+ private boolean isNaturallyAggressiveToPlayers; // Purpur -+ private int spitCooldown; // Purpur ++ private boolean isNaturallyAggressiveToPlayers; // Purpur - Dolphins naturally aggressive to players chance ++ private int spitCooldown; // Purpur - Ridables - public Dolphin(EntityType type, Level world) { - super(type, world); + public Dolphin(EntityType entityType, Level level) { + super(entityType, level); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start ++ // Purpur start - Ridables + class DolphinMoveControl extends SmoothSwimmingMoveControl { + private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD; + private final Dolphin dolphin; @@ -4446,12 +4226,12 @@ index 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..c1842894f96a567707992d8ff938dbf6 + } + }; + this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur end ++ // Purpur end - Ridables this.lookControl = new SmoothSwimmingLookControl(this, 10); this.setCanPickUpLoot(true); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.dolphinRidable; @@ -4476,7 +4256,7 @@ index 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..c1842894f96a567707992d8ff938dbf6 + loc.setPitch(loc.getPitch() - 10); + org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector()); + -+ org.purpurmc.purpur.entity.DolphinSpit spit = new org.purpurmc.purpur.entity.DolphinSpit(level(), this); ++ org.purpurmc.purpur.entity.projectile.DolphinSpit spit = new org.purpurmc.purpur.entity.projectile.DolphinSpit(level(), this); + spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F); + + level().addFreshEntity(spit); @@ -4485,59 +4265,65 @@ index 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..c1842894f96a567707992d8ff938dbf6 + } + return false; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.dolphinScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.dolphinTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.dolphinAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Nullable @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { -@@ -101,6 +186,7 @@ public class Dolphin extends AgeableWaterCreature { - SpawnGroupData groupdataentity1 = (SpawnGroupData) Objects.requireNonNullElseGet(entityData, () -> { - return new AgeableMob.AgeableMobGroupData(0.1F); - }); -+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur - - return super.finalizeSpawn(world, difficulty, spawnReason, groupdataentity1); + public SpawnGroupData finalizeSpawn( +@@ -87,6 +178,7 @@ public class Dolphin extends AgeableWaterCreature { + this.setAirSupply(this.getMaxAirSupply()); + this.setXRot(0.0F); + SpawnGroupData spawnGroupData1 = Objects.requireNonNullElseGet(spawnGroupData, () -> new AgeableMob.AgeableMobGroupData(0.1F)); ++ this.isNaturallyAggressiveToPlayers = level.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= level.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur - Dolphins naturally aggressive to players chance + return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData1); } -@@ -177,17 +263,21 @@ public class Dolphin extends AgeableWaterCreature { + +@@ -169,17 +261,21 @@ public class Dolphin extends AgeableWaterCreature { protected void registerGoals() { this.goalSelector.addGoal(0, new BreathAirGoal(this)); this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); -+ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - Dolphins naturally aggressive to players chance ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this)); - this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0D)); - this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10)); + this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0)); + this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0, 10)); this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(5, new DolphinJumpGoal(this, 10)); -- this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true)); -+ //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - moved up +- this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2F, true)); ++ //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2F, true)); // Purpur - moved up - Dolphins naturally aggressive to players chance this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal()); this.goalSelector.addGoal(8, new FollowBoatGoal(this)); - this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0D, 1.0D)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers()); -+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur + this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0, 1.0)); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Guardian.class).setAlertOthers()); ++ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - Dolphins naturally aggressive to players chance } public static AttributeSupplier.Builder createAttributes() { -@@ -231,7 +321,7 @@ public class Dolphin extends AgeableWaterCreature { +@@ -223,7 +319,7 @@ public class Dolphin extends AgeableWaterCreature { @Override protected boolean canRide(Entity entity) { @@ -4546,35 +4332,35 @@ index 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..c1842894f96a567707992d8ff938dbf6 } @Override -@@ -264,6 +354,11 @@ public class Dolphin extends AgeableWaterCreature { +@@ -252,6 +348,11 @@ public class Dolphin extends AgeableWaterCreature { @Override public void tick() { super.tick(); -+ // Purpur start ++ // Purpur start - Ridables + if (spitCooldown > 0) { + spitCooldown--; + } -+ // Purpur end ++ // Purpur end - Ridables if (this.isNoAi()) { this.setAirSupply(this.getMaxAirSupply()); } else { -@@ -412,6 +507,7 @@ public class Dolphin extends AgeableWaterCreature { +@@ -412,6 +513,7 @@ public class Dolphin extends AgeableWaterCreature { @Override public boolean canUse() { -+ if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur ++ if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur - Add option to disable dolphin treasure searching return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100; } -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 a9a8ebb2cebe668628d5bdb33fa1399e0ab1e08b..ac044be03494c3d6bad6bbc22321445e04d430cc 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -144,6 +144,65 @@ public class Fox extends Animal implements VariantHolder { +diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java +index 44bb04cc9796a16f5477d8f2ad22af62c9af1fc3..aa610af9db105081fcabc1f299e5f2dd1f4d907e 100644 +--- a/net/minecraft/world/entity/animal/Fox.java ++++ b/net/minecraft/world/entity/animal/Fox.java +@@ -129,6 +129,73 @@ public class Fox extends Animal implements VariantHolder { this.getNavigation().setRequiredPathLength(32.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.foxRidable; @@ -4610,73 +4396,81 @@ index a9a8ebb2cebe668628d5bdb33fa1399e0ab1e08b..ac044be03494c3d6bad6bbc22321445e + super.onDismount(rider); + setCanPickUpLoot(true); + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.foxScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.foxBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.foxTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.foxAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -@@ -163,6 +222,7 @@ public class Fox extends Animal implements VariantHolder { - return entityliving instanceof AbstractSchoolingFish; - }); +@@ -148,6 +215,7 @@ public class Fox extends Animal implements VariantHolder { + this, AbstractFish.class, 20, false, false, (entity, level) -> entity instanceof AbstractSchoolingFish + ); this.goalSelector.addGoal(0, new Fox.FoxFloatGoal()); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level())); this.goalSelector.addGoal(1, new Fox.FaceplantGoal()); - this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2D)); -@@ -189,6 +249,7 @@ public class Fox extends Animal implements VariantHolder { + this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2)); +@@ -175,6 +243,7 @@ public class Fox extends Animal implements VariantHolder { this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal()); this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F)); this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal()); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving, worldserver) -> { - return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID()); - })); -@@ -338,6 +399,11 @@ public class Fox extends Animal implements VariantHolder { ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector + .addGoal( + 3, +@@ -335,6 +404,11 @@ public class Fox extends Animal implements VariantHolder { } private void setTargetGoals() { -+ // Purpur start - do not add duplicate goals ++ // Purpur start - Tulips change fox type - do not add duplicate goals + this.targetSelector.removeGoal(this.landTargetGoal); + this.targetSelector.removeGoal(this.turtleEggTargetGoal); + this.targetSelector.removeGoal(this.fishTargetGoal); -+ // Purpur end ++ // Purpur end - Tulips change fox type if (this.getVariant() == Fox.Variant.RED) { this.targetSelector.addGoal(4, this.landTargetGoal); this.targetSelector.addGoal(4, this.turtleEggTargetGoal); -@@ -367,6 +433,7 @@ public class Fox extends Animal implements VariantHolder { - +@@ -364,6 +438,7 @@ public class Fox extends Animal implements VariantHolder { + @Override public void setVariant(Fox.Variant variant) { - this.entityData.set(Fox.DATA_TYPE_ID, variant.getId()); -+ this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change + this.entityData.set(DATA_TYPE_ID, variant.getId()); ++ this.setTargetGoals(); // Purpur - Tulips change fox type - fix API bug not updating pathfinders on type change } List getTrustedUUIDs() { -@@ -702,6 +769,29 @@ public class Fox extends Animal implements VariantHolder { +@@ -685,6 +760,29 @@ public class Fox extends Animal implements VariantHolder { } // Paper end -+ // Purpur start ++ // Purpur start - Tulips change fox type + @Override + public net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) { + if (level().purpurConfig.foxTypeChangesWithTulips) { @@ -4697,88 +4491,97 @@ index a9a8ebb2cebe668628d5bdb33fa1399e0ab1e08b..ac044be03494c3d6bad6bbc22321445e + } + return super.mobInteract(player, hand); + } -+ // Purpur end ++ // Purpur end - Tulips change fox type + @Override // Paper start - Cancellable death event - protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { -@@ -754,16 +844,16 @@ public class Fox extends Animal implements VariantHolder { - return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F)); + protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel level, DamageSource damageSource) { +@@ -892,8 +990,10 @@ public class Fox extends Animal implements VariantHolder { + CriteriaTriggers.BRED_ANIMALS.trigger(serverPlayer, this.animal, this.partner, fox); + } + +- this.animal.setAge(6000); +- this.partner.setAge(6000); ++ // Purpur start - Make entity breeding times configurable ++ this.animal.setAge(this.animal.getPurpurBreedTime()); ++ this.partner.setAge(this.partner.getPurpurBreedTime()); ++ // Purpur end - Make entity breeding times configurable + this.animal.resetLove(); + this.partner.resetLove(); + serverLevel.addFreshEntityWithPassengers(fox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason +@@ -952,7 +1052,7 @@ public class Fox extends Animal implements VariantHolder { + } + + protected void onReachedTarget() { +- if (getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (getServerLevel(Fox.this.level()).purpurConfig.foxBypassMobGriefing ^ getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected + BlockState blockState = Fox.this.level().getBlockState(this.blockPos); + if (blockState.is(Blocks.SWEET_BERRY_BUSH)) { + this.pickSweetBerries(blockState); +@@ -1066,15 +1166,15 @@ public class Fox extends Animal implements VariantHolder { + } } - public class FoxLookControl extends LookControl { -+ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - ++ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables public FoxLookControl() { super(Fox.this); } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (!Fox.this.isSleeping()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } - } -@@ -774,16 +864,16 @@ public class Fox extends Animal implements VariantHolder { + +@@ -1110,15 +1210,15 @@ public class Fox extends Animal implements VariantHolder { } } -- private class FoxMoveControl extends MoveControl { -+ private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - +- class FoxMoveControl extends MoveControl { ++ class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables public FoxMoveControl() { super(Fox.this); } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (Fox.this.canMove()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } - } -@@ -901,8 +991,10 @@ public class Fox extends Animal implements VariantHolder { - CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer2, this.animal, this.partner, entityfox); - } - -- this.animal.setAge(6000); -- this.partner.setAge(6000); -+ // Purpur start -+ this.animal.setAge(this.animal.getPurpurBreedTime()); -+ this.partner.setAge(this.partner.getPurpurBreedTime()); -+ // Purpur end - this.animal.resetLove(); - this.partner.resetLove(); - worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason -@@ -1288,7 +1380,7 @@ public class Fox extends Animal implements VariantHolder { - } - - protected void onReachedTarget() { -- if (getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (getServerLevel(Fox.this.level()).purpurConfig.foxBypassMobGriefing ^ getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - BlockState iblockdata = Fox.this.level().getBlockState(this.blockPos); - - if (iblockdata.is(Blocks.SWEET_BERRY_BUSH)) { -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 e07b79ef172095c1800c88342b3ac8dc7703aea2..9a6471d2f1eb1c8af006b70b6bba0b668220fb00 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -@@ -57,13 +57,59 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + } +diff --git a/net/minecraft/world/entity/animal/IronGolem.java b/net/minecraft/world/entity/animal/IronGolem.java +index 8e9ba307a0528eb1aef56bdc0f4ded0e71621253..ccadc9a151e258ff2c74c65c374b1f09d56d10ec 100644 +--- a/net/minecraft/world/entity/animal/IronGolem.java ++++ b/net/minecraft/world/entity/animal/IronGolem.java +@@ -56,13 +56,67 @@ public class IronGolem extends AbstractGolem implements NeutralMob { private int remainingPersistentAngerTime; @Nullable private UUID persistentAngerTarget; -+ @Nullable private UUID summoner; // Purpur ++ @Nullable private UUID summoner; // Purpur - Summoner API - public IronGolem(EntityType type, Level world) { - super(type, world); + public IronGolem(EntityType entityType, Level level) { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Summoner API ++ @Nullable ++ public UUID getSummoner() { ++ return summoner; ++ } ++ ++ public void setSummoner(@Nullable UUID summoner) { ++ this.summoner = summoner; ++ } ++ // Purpur end - Summoner API ++ ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.ironGolemRidable; @@ -4793,96 +4596,91 @@ index e07b79ef172095c1800c88342b3ac8dc7703aea2..9a6471d2f1eb1c8af006b70b6bba0b66 + public boolean isControllable() { + return level().purpurConfig.ironGolemControllable; + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ironGolemScale); + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ironGolemTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + -+ @Nullable -+ public UUID getSummoner() { -+ return summoner; -+ } -+ -+ public void setSummoner(@Nullable UUID summoner) { -+ this.summoner = summoner; -+ } -+ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ironGolemAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { -+ if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur -+ if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true)); - this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F)); - this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false)); -@@ -71,6 +117,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { ++ if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur - Iron golem calm anger options ++ if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0, true)); + this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9, 32.0F)); + this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6, false)); +@@ -70,6 +124,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { this.goalSelector.addGoal(5, new OfferFlowerGoal(this)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); + this.targetSelector.addGoal(2, new HurtByTargetGoal(this)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); -@@ -135,6 +182,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putBoolean("PlayerCreated", this.isPlayerCreated()); -+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur - this.addPersistentAngerSaveData(nbt); +@@ -140,6 +195,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); + compound.putBoolean("PlayerCreated", this.isPlayerCreated()); ++ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API + this.addPersistentAngerSaveData(compound); } -@@ -142,6 +190,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - this.setPlayerCreated(nbt.getBoolean("PlayerCreated")); -+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur - this.readPersistentAngerSaveData(this.level(), nbt); +@@ -147,6 +203,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + public void readAdditionalSaveData(CompoundTag compound) { + super.readAdditionalSaveData(compound); + this.setPlayerCreated(compound.getBoolean("PlayerCreated")); ++ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API + this.readPersistentAngerSaveData(this.level(), compound); } -@@ -267,18 +316,19 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - ItemStack itemstack = player.getItemInHand(hand); - - if (!itemstack.is(Items.IRON_INGOT)) { +@@ -256,16 +313,17 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + protected InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack itemInHand = player.getItemInHand(hand); + if (!itemInHand.is(Items.IRON_INGOT)) { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } else { - float f = this.getHealth(); - + float health = this.getHealth(); this.heal(25.0F); - if (this.getHealth() == f) { + if (this.getHealth() == health) { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } else { - float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; - - this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f1); - itemstack.consume(1, player); -+ if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur + float f = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; + this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f); + itemInHand.consume(1, player); ++ if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur - Iron golem calm anger options return InteractionResult.SUCCESS; } } -diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -index 0047c9f3c6dc901944187784e42cd09a3c6b460c..24205e62b95833779a7dff598930c4672a33fa36 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -@@ -65,6 +65,43 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(serverLevel, itemInHand); + org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops); if (event != null) { - if (event.isCancelled()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } +- if (event.isCancelled()) return InteractionResult.PASS; ++ if (event.isCancelled()) return tryRide(player, hand); // Purpur - Ridables drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - // Paper end - custom shear drops -@@ -155,7 +192,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder optional = this.getEffectsFromItemStack(itemstack); - - if (optional.isEmpty()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur + // Paper end - custom shear drops } - - itemstack.consume(1, player); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -index 0554ee499c452db6c1e6852f5022b1f197adb024..dee59cb4b87845c940ee089aa932aa69dd2539d6 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -@@ -65,6 +65,44 @@ public class Ocelot extends Animal { +diff --git a/net/minecraft/world/entity/animal/Ocelot.java b/net/minecraft/world/entity/animal/Ocelot.java +index 5b59f68141c2ceeaf7907bbf5e7b9e08cbe2239e..681f1256d8bbedc7731fd2906a7f439da4333552 100644 +--- a/net/minecraft/world/entity/animal/Ocelot.java ++++ b/net/minecraft/world/entity/animal/Ocelot.java +@@ -62,6 +62,52 @@ public class Ocelot extends Animal { this.reassessTrustingGoals(); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.ocelotRidable; @@ -4963,65 +4760,73 @@ index 0554ee499c452db6c1e6852f5022b1f197adb024..dee59cb4b87845c940ee089aa932aa69 + public boolean isControllable() { + return level().purpurConfig.ocelotControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ocelotMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ocelotScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.ocelotBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ocelotTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ocelotAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + public boolean isTrusting() { - return (Boolean) this.entityData.get(Ocelot.DATA_TRUSTING); + return this.entityData.get(DATA_TRUSTING); } -@@ -98,12 +136,14 @@ public class Ocelot extends Animal { - return itemstack.is(ItemTags.OCELOT_FOOD); - }, true); +@@ -93,12 +139,14 @@ public class Ocelot extends Animal { + protected void registerGoals() { + this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6, itemStack -> itemStack.is(ItemTags.OCELOT_FOOD), true); this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(3, this.temptGoal); this.goalSelector.addGoal(7, new LeapAtTargetGoal(this, 0.3F)); this.goalSelector.addGoal(8, new OcelotAttackGoal(this)); - this.goalSelector.addGoal(9, new BreedGoal(this, 0.8D)); - this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F)); + this.goalSelector.addGoal(9, new BreedGoal(this, 0.8)); + this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8, 1.0000001E-5F)); this.goalSelector.addGoal(11, new LookAtPlayerGoal(this, Player.class, 10.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Chicken.class, false)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR)); } -@@ -244,7 +284,7 @@ public class Ocelot extends Animal { - if (world.isUnobstructed(this) && !world.containsAnyLiquid(this.getBoundingBox())) { - BlockPos blockposition = this.blockPosition(); - -- if (blockposition.getY() < world.getSeaLevel()) { -+ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockposition.getY() < world.getSeaLevel()) { +@@ -232,7 +280,7 @@ public class Ocelot extends Animal { + public boolean checkSpawnObstruction(LevelReader level) { + if (level.isUnobstructed(this) && !level.containsAnyLiquid(this.getBoundingBox())) { + BlockPos blockPos = this.blockPosition(); +- if (blockPos.getY() < level.getSeaLevel()) { ++ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockPos.getY() < level.getSeaLevel()) { // Purpur - Option Ocelot Spawn Under Sea Level return false; } -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 be753557d7ebd6f1e82b1bdb6d60ecc450f72eec..83372c86bd54eedd9b136ddfcbfc67d303058c0a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -112,6 +112,54 @@ public class Panda extends Animal { - +diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java +index 283ddf7d13a17c0a6df5a52b7fd26ed7b7a4826b..0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688 100644 +--- a/net/minecraft/world/entity/animal/Panda.java ++++ b/net/minecraft/world/entity/animal/Panda.java +@@ -105,6 +105,62 @@ public class Panda extends Animal { + } } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.pandaRidable; @@ -5045,116 +4850,119 @@ index be753557d7ebd6f1e82b1bdb6d60ecc450f72eec..83372c86bd54eedd9b136ddfcbfc67d3 + eat(false); + setOnBack(false); + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pandaMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pandaScale); + setAttributes(); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.pandaBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pandaTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pandaAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) { return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot(); -@@ -271,6 +319,7 @@ public class Panda extends Animal { +@@ -258,6 +314,7 @@ public class Panda extends Animal { @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0D)); - this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2000000476837158D, true)); -@@ -288,6 +337,7 @@ public class Panda extends Animal { ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0)); + this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0)); + this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2F, true)); +@@ -273,6 +330,7 @@ public class Panda extends Animal { this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this)); - this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25D)); - this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0D)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0])); + this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25)); + this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0)); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new Panda.PandaHurtByTargetGoal(this).setAlertOthers(new Class[0])); } -@@ -617,7 +667,10 @@ public class Panda extends Animal { +@@ -596,7 +654,11 @@ public class Panda extends Animal { public void setAttributes() { if (this.isWeak()) { -- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0D); -+ // Purpur start +- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0); ++ // Purpur start - Configurable entity base attributes + net.minecraft.world.entity.ai.attributes.AttributeInstance maxHealth = this.getAttribute(Attributes.MAX_HEALTH); + maxHealth.setBaseValue(maxHealth.getValue() / 2); -+ // Purpur end ++ // Purpur end - Configurable entity base attributes ++ } if (this.isLazy()) { -@@ -640,7 +693,7 @@ public class Panda extends Animal { - ItemStack itemstack = player.getItemInHand(hand); - +@@ -616,7 +678,7 @@ public class Panda extends Animal { + public InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack itemInHand = player.getItemInHand(hand); if (this.isScared()) { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } else if (this.isOnBack()) { this.setOnBack(false); return InteractionResult.SUCCESS; -@@ -679,12 +732,12 @@ public class Panda extends Animal { - } - } - -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - } +@@ -652,7 +714,7 @@ public class Panda extends Animal { return InteractionResult.SUCCESS_SERVER; } else { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } } -@@ -729,7 +782,7 @@ public class Panda extends Animal { - return itemEntity.getItem().is(ItemTags.PANDA_EATS_FROM_GROUND) && itemEntity.isAlive() && !itemEntity.hasPickUpDelay(); +@@ -964,7 +1026,7 @@ public class Panda extends Animal { + } } -- private static class PandaMoveControl extends MoveControl { -+ private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - +- static class PandaMoveControl extends MoveControl { ++ static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables private final Panda panda; -@@ -739,9 +792,9 @@ public class Panda extends Animal { + public PandaMoveControl(Panda mob) { +@@ -973,9 +1035,9 @@ public class Panda extends Animal { } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (this.panda.canPerformAction()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } } } -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 8883894da73c7d5975a8826d23ee7f542db98b0b..28a9d267099f6c24f71dc5a11179d59c27265a88 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -@@ -126,12 +126,89 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) { - super(type, world); + public Parrot(EntityType entityType, Level level) { + super(entityType, level); - this.moveControl = new FlyingMoveControl(this, 10, false); -+ // Purpur start ++ // Purpur start - Ridables + final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); + class ParrotMoveControl extends FlyingMoveControl { + public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) { @@ -5176,13 +4984,13 @@ index 8883894da73c7d5975a8826d23ee7f542db98b0b..28a9d267099f6c24f71dc5a11179d59c + } + } + this.moveControl = new ParrotMoveControl(this, 10, false); -+ // Purpur end ++ // Purpur end - Ridables this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F); this.setPathfindingMalus(PathType.COCOA, -1.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.parrotRidable; @@ -5214,96 +5022,104 @@ index 8883894da73c7d5975a8826d23ee7f542db98b0b..28a9d267099f6c24f71dc5a11179d59c + setDeltaMovement(mot.scale(0.9D)); + } + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.parrotMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.parrotScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return 6000; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.parrotTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.parrotAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Nullable @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { -@@ -150,8 +227,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { -@@ -294,13 +375,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder { -@@ -156,6 +195,17 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 1.25)); + this.goalSelector.addGoal(3, new BreedGoal(this, 1.0)); + this.goalSelector.addGoal(4, new TemptGoal(this, 1.2, itemStack -> itemStack.is(Items.CARROT_ON_A_STICK), false)); +@@ -132,6 +179,19 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { + @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { - boolean flag = this.isFood(player.getItemInHand(hand)); - -+ if (level().purpurConfig.pigGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) { + boolean isFood = this.isFood(player.getItemInHand(hand)); ++ // Purpur start - Pigs give saddle back ++ if (level().purpurConfig.pigGiveSaddleBack && player.isSecondaryUseActive() && !isFood && isSaddled() && !isVehicle()) { + this.steering.setSaddle(false); + if (!player.getAbilities().instabuild) { + ItemStack saddle = new ItemStack(Items.SADDLE); @@ -5362,25 +5187,49 @@ index c39b2580a67c9b0bf8631f108e0628fa9732ada1..7895fca01c20da24a10ac6a642ebba87 + } + return InteractionResult.SUCCESS; + } ++ // Purpur end - Pigs give saddle back + - if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { + if (!isFood && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { if (!this.level().isClientSide) { player.startRiding(this); -diff --git a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -index cd72d8f766069796ce1fe4a83b8646692005ff8c..86c44912a363401bdd716c22d24dfd7e92cfd0be 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -+++ b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -@@ -59,11 +59,82 @@ public class PolarBear extends Animal implements NeutralMob { +diff --git a/net/minecraft/world/entity/animal/PolarBear.java b/net/minecraft/world/entity/animal/PolarBear.java +index f568c385e1427e183aefb5819013838aca95407b..026e64bf743aa79ba6409fa5cd4374b368f98caa 100644 +--- a/net/minecraft/world/entity/animal/PolarBear.java ++++ b/net/minecraft/world/entity/animal/PolarBear.java +@@ -59,11 +59,92 @@ public class PolarBear extends Animal implements NeutralMob { private int remainingPersistentAngerTime; @Nullable private UUID persistentAngerTarget; -+ private int standTimer = 0; // Purpur ++ private int standTimer = 0; // Purpur - Ridables - public PolarBear(EntityType type, Level world) { - super(type, world); + public PolarBear(EntityType entityType, Level level) { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Breedable Polar Bears ++ public boolean canMate(Animal other) { ++ if (other == this) { ++ return false; ++ } else if (this.isStanding()) { ++ return false; ++ } else if (this.getTarget() != null) { ++ return false; ++ } else if (!(other instanceof PolarBear)) { ++ return false; ++ } else { ++ PolarBear bear = (PolarBear) other; ++ if (bear.isStanding()) { ++ return false; ++ } ++ if (bear.getTarget() != null) { ++ return false; ++ } ++ return this.isInLove() && bear.isInLove(); ++ } ++ } ++ // Purpur end - Breedable Polar Bears ++ ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.polarBearRidable; @@ -5406,113 +5255,99 @@ index cd72d8f766069796ce1fe4a83b8646692005ff8c..86c44912a363401bdd716c22d24dfd7e + } + return false; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.polarBearMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.polarBearScale); + } ++ // Purpur end - Configurable entity base attributes + -+ public boolean canMate(Animal other) { -+ if (other == this) { -+ return false; -+ } else if (this.isStanding()) { -+ return false; -+ } else if (this.getTarget() != null) { -+ return false; -+ } else if (!(other instanceof PolarBear)) { -+ return false; -+ } else { -+ PolarBear bear = (PolarBear) other; -+ if (bear.isStanding()) { -+ return false; -+ } -+ if (bear.getTarget() != null) { -+ return false; -+ } -+ return this.isInLove() && bear.isInLove(); -+ } -+ } -+ ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.polarBearBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.polarBearTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.polarBearAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Nullable @Override - public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) { -@@ -72,20 +143,28 @@ public class PolarBear extends Animal implements NeutralMob { + public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) { +@@ -72,19 +153,27 @@ public class PolarBear extends Animal implements NeutralMob { @Override public boolean isFood(ItemStack stack) { - return false; -+ return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur ++ return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur - Breedable Polar Bears } @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal()); - this.goalSelector - .addGoal(1, new PanicGoal(this, 2.0, polarBear -> polarBear.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)); -+ // Purpur start + this.goalSelector.addGoal(1, new PanicGoal(this, 2.0, mob -> mob.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)); ++ // Purpur start - Breedable Polar Bears + if (level().purpurConfig.polarBearBreedableItem != null) { + this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D)); + this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level().purpurConfig.polarBearBreedableItem), false)); + } -+ // Purpur end ++ // Purpur end - Breedable Polar Bears this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25)); this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal()); this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); -@@ -204,6 +283,12 @@ public class PolarBear extends Animal implements NeutralMob { +@@ -203,6 +292,12 @@ public class PolarBear extends Animal implements NeutralMob { if (!this.level().isClientSide) { this.updatePersistentAnger((ServerLevel)this.level(), true); } + -+ // Purpur start ++ // Purpur start - Ridables + if (isStanding() && --standTimer <= 0) { + setStanding(false); + } -+ // Purpur end ++ // Purpur end - Ridables } @Override -@@ -223,6 +308,7 @@ public class PolarBear extends Animal implements NeutralMob { +@@ -222,6 +317,7 @@ public class PolarBear extends Animal implements NeutralMob { - public void setStanding(boolean warning) { - this.entityData.set(DATA_STANDING_ID, warning); -+ standTimer = warning ? 20 : -1; // Purpur + public void setStanding(boolean standing) { + this.entityData.set(DATA_STANDING_ID, standing); ++ standTimer = standing ? 20 : -1; // Purpur - Ridables } - public float getStandingAnimationScale(float tickDelta) { -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 cdb74f86ee92ee143af29962a85d45ca585cee44..c5a39ea2ad0e5e5ac434d79c1a57e0068b8bc809 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -@@ -52,6 +52,33 @@ public class Pufferfish extends AbstractFish { + public float getStandingAnimationScale(float partialTick) { +diff --git a/net/minecraft/world/entity/animal/Pufferfish.java b/net/minecraft/world/entity/animal/Pufferfish.java +index d94a7cfcd0f7a15ce97d3b12daa8b2c71acf997a..5889a9ceb54a354a79f33c9e4fc338cbf912ddda 100644 +--- a/net/minecraft/world/entity/animal/Pufferfish.java ++++ b/net/minecraft/world/entity/animal/Pufferfish.java +@@ -45,6 +45,39 @@ public class Pufferfish extends AbstractFish { this.refreshDimensions(); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.pufferfishRidable; @@ -5522,43 +5357,49 @@ index cdb74f86ee92ee143af29962a85d45ca585cee44..c5a39ea2ad0e5e5ac434d79c1a57e006 + public boolean isControllable() { + return level().purpurConfig.pufferfishControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pufferfishMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pufferfishTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pufferfishAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -index 8cc6022507c97af62fb2b4455198bc35744137c9..b3a0146ccfcda9fa33b91d33458086b510bb4d7b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -@@ -88,6 +88,7 @@ public class Rabbit extends Animal implements VariantHolder { +diff --git a/net/minecraft/world/entity/animal/Rabbit.java b/net/minecraft/world/entity/animal/Rabbit.java +index c5346b6e4ffe09ca1ec4b85e612c9ee52ae77329..da5b32a17283e540615373097acc511d928aeff5 100644 +--- a/net/minecraft/world/entity/animal/Rabbit.java ++++ b/net/minecraft/world/entity/animal/Rabbit.java +@@ -83,6 +83,7 @@ public class Rabbit extends Animal implements VariantHolder { private boolean wasOnGround; private int jumpDelayTicks; public int moreCarrotTicks; -+ private boolean actualJump; // Purpur ++ private boolean actualJump; // Purpur - Ridables - public Rabbit(EntityType type, Level world) { - super(type, world); -@@ -95,9 +96,76 @@ public class Rabbit extends Animal implements VariantHolder { - this.moveControl = new Rabbit.RabbitMoveControl(this); + public Rabbit(EntityType entityType, Level level) { + super(entityType, level); +@@ -91,9 +92,84 @@ public class Rabbit extends Animal implements VariantHolder { + //this.setSpeedModifier(0.0); // CraftBukkit } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.rabbitRidable; @@ -5601,132 +5442,140 @@ index 8cc6022507c97af62fb2b4455198bc35744137c9..b3a0146ccfcda9fa33b91d33458086b5 + } + wasOnGround = onGround; + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.rabbitScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.rabbitBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.rabbitTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.rabbitAlwaysDropExp; + } -+ // Purpur end ++ // Purpur end - Mobs always drop experience + @Override public void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); - this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D)); -@@ -114,6 +182,14 @@ public class Rabbit extends Animal implements VariantHolder { + this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2)); + this.goalSelector.addGoal(2, new BreedGoal(this, 0.8)); +@@ -108,6 +184,14 @@ public class Rabbit extends Animal implements VariantHolder { @Override protected float getJumpPower() { -+ // Purpur start ++ // Purpur start - Ridables + if (getRider() != null && this.isControllable()) { + if (getForwardMot() < 0) { + setSpeed(getForwardMot() * 2F); + } + return actualJump ? 0.5F : 0.3F; + } -+ // Purpur end ++ // Purpur end - Ridables float f = 0.3F; - - if (this.horizontalCollision || this.moveControl.hasWanted() && this.moveControl.getWantedY() > this.getY() + 0.5D) { -@@ -188,6 +264,12 @@ public class Rabbit extends Animal implements VariantHolder { + if (this.moveControl.getSpeedModifier() <= 0.6) { + f = 0.2F; +@@ -175,6 +259,12 @@ public class Rabbit extends Animal implements VariantHolder { @Override - public void customServerAiStep(ServerLevel world) { -+ // Purpur start + public void customServerAiStep(ServerLevel level) { ++ // Purpur start - Ridables + if (getRider() != null && this.isControllable()) { + handleJumping(); + return; + } -+ // Purpur end ++ // Purpur end - Ridables if (this.jumpDelayTicks > 0) { - --this.jumpDelayTicks; + this.jumpDelayTicks--; } -@@ -402,10 +484,23 @@ public class Rabbit extends Animal implements VariantHolder { +@@ -376,10 +466,23 @@ public class Rabbit extends Animal implements VariantHolder { } - this.setVariant(entityrabbit_variant); + this.setVariant(randomRabbitVariant); + -+ // Purpur start -+ if (entityrabbit_variant != Variant.EVIL && world.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.getLevel().purpurConfig.rabbitNaturalToast) { ++ // Purpur start - Special mobs naturally spawn ++ if (randomRabbitVariant != Variant.EVIL && level.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= level.getLevel().purpurConfig.rabbitNaturalToast) { + setCustomName(Component.translatable("Toast")); + } -+ // Purpur end ++ // Purpur end - Special mobs naturally spawn + - return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); + return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } - private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor world, BlockPos pos) { -+ // Purpur start -+ Level level = world.getMinecraftWorld(); -+ if (level.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= level.purpurConfig.rabbitNaturalKiller) { + private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor level, BlockPos pos) { ++ // Purpur start - Special mobs naturally spawn ++ Level world = level.getMinecraftWorld(); ++ if (world.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= world.purpurConfig.rabbitNaturalKiller) { + return Rabbit.Variant.EVIL; + } -+ // Purpur end - Holder holder = world.getBiome(pos); - int i = world.getRandom().nextInt(100); - -@@ -469,7 +564,7 @@ public class Rabbit extends Animal implements VariantHolder { ++ // Purpur end - Special mobs naturally spawn + Holder biome = level.getBiome(pos); + int randomInt = level.getRandom().nextInt(100); + if (biome.is(BiomeTags.SPAWNS_WHITE_RABBITS)) { +@@ -470,7 +573,7 @@ public class Rabbit extends Animal implements VariantHolder { } } -- private static class RabbitMoveControl extends MoveControl { -+ private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - +- static class RabbitMoveControl extends MoveControl { ++ static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables private final Rabbit rabbit; private double nextJumpSpeed; -@@ -480,14 +575,14 @@ public class Rabbit extends Animal implements VariantHolder { + +@@ -480,14 +583,14 @@ public class Rabbit extends Animal implements VariantHolder { } @Override - public void tick() { -+ public void vanillaTick() { // Purpur - if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) { - this.rabbit.setSpeedModifier(0.0D); - } else if (this.hasWanted()) { ++ public void vanillaTick() { // Purpur - Ridables + if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl)this.rabbit.jumpControl).wantJump()) { + this.rabbit.setSpeedModifier(0.0); + } else if (this.hasWanted() || this.operation == MoveControl.Operation.JUMPING) { this.rabbit.setSpeedModifier(this.nextJumpSpeed); } - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } @Override -@@ -549,7 +644,7 @@ public class Rabbit extends Animal implements VariantHolder { +@@ -531,7 +634,7 @@ public class Rabbit extends Animal implements VariantHolder { @Override public boolean canUse() { if (this.nextStartTick <= 0) { -- if (!getServerLevel((Entity) this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!getServerLevel((Entity) this.rabbit).purpurConfig.rabbitBypassMobGriefing == !getServerLevel((Entity) this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur +- if (!getServerLevel(this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (!getServerLevel(this.rabbit).purpurConfig.rabbitBypassMobGriefing == !getServerLevel(this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return false; } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Salmon.java b/src/main/java/net/minecraft/world/entity/animal/Salmon.java -index 661997c39df777b6e332f0a5710e7f63a116a499..1a0c71ed6f3bd3d961f9b30ab29a62d683195355 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java -@@ -32,6 +32,33 @@ public class Salmon extends AbstractSchoolingFish implements VariantHolder { -diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -index fd9f6c17448a4d87f940eb8f544ecb9669068582..13eecc676e33623e776d32495969f111bf6c0e44 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -@@ -50,17 +50,57 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 1.25)); + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); + this.goalSelector.addGoal(3, new TemptGoal(this, 1.1, stack -> stack.is(ItemTags.SHEEP_FOOD), false)); +diff --git a/net/minecraft/world/entity/animal/SnowGolem.java b/net/minecraft/world/entity/animal/SnowGolem.java +index 8871964fd735178804b95182db1fd6bc1088f69d..1b9d4562b73ecdf783ecdaf4f4eff9037a4387e6 100644 +--- a/net/minecraft/world/entity/animal/SnowGolem.java ++++ b/net/minecraft/world/entity/animal/SnowGolem.java +@@ -44,17 +44,63 @@ import net.minecraft.world.phys.Vec3; + public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackMob { private static final EntityDataAccessor DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE); private static final byte PUMPKIN_FLAG = 16; -+ @Nullable private java.util.UUID summoner; // Purpur ++ @Nullable private java.util.UUID summoner; // Purpur - Summoner API - public SnowGolem(EntityType type, Level world) { - super(type, world); + public SnowGolem(EntityType entityType, Level level) { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Summoner API ++ @Nullable ++ public java.util.UUID getSummoner() { ++ return summoner; ++ } ++ ++ public void setSummoner(@Nullable java.util.UUID summoner) { ++ this.summoner = summoner; ++ } ++ // Purpur end - Summoner API ++ ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.snowGolemRidable; @@ -5839,117 +5713,131 @@ index fd9f6c17448a4d87f940eb8f544ecb9669068582..13eecc676e33623e776d32495969f111 + public boolean isControllable() { + return level().purpurConfig.snowGolemControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snowGolemMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.snowGolemScale); + } ++ // Purpur end - Configurable entity base attributes + -+ @Nullable -+ public java.util.UUID getSummoner() { -+ return summoner; -+ } -+ -+ public void setSummoner(@Nullable java.util.UUID summoner) { -+ this.summoner = summoner; -+ } -+ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.snowGolemAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { -- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur - this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F)); +- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25, 20, 10.0F)); +- this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0, 1.0000001E-5F)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables ++ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur - Snow Golem rate of fire config ++ this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F)); this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving, worldserver) -> { - return entityliving instanceof Enemy; - })); -@@ -80,6 +120,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putBoolean("Pumpkin", this.hasPumpkin()); -+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entity, level) -> entity instanceof Enemy)); + } + +@@ -72,6 +118,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); + compound.putBoolean("Pumpkin", this.hasPumpkin()); ++ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API } @Override -@@ -88,12 +129,13 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - if (nbt.contains("Pumpkin")) { - this.setPumpkin(nbt.getBoolean("Pumpkin")); +@@ -80,11 +127,12 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + if (compound.contains("Pumpkin")) { + this.setPumpkin(compound.getBoolean("Pumpkin")); } -+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur - ++ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API } @Override public boolean isSensitiveToWater() { - return true; -+ return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur ++ return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage } @Override -@@ -106,10 +148,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - this.hurtServer(worldserver, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING +@@ -95,10 +143,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + this.hurtServer(serverLevel, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING } -- if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!worldserver.purpurConfig.snowGolemBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur +- if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (!serverLevel.purpurConfig.snowGolemBypassMobGriefing == !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return; } + if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden - BlockState iblockdata = Blocks.SNOW.defaultBlockState(); + BlockState blockState = Blocks.SNOW.defaultBlockState(); - for (int i = 0; i < 4; ++i) { -@@ -166,7 +209,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); + for (int i = 0; i < 4; i++) { +@@ -141,7 +190,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops); if (event != null) { if (event.isCancelled()) { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - // Paper end - custom shear drops -@@ -178,8 +221,16 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + // Paper end - custom shear drops +@@ -153,8 +202,16 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM } return InteractionResult.SUCCESS; -+ // Purpur start -+ } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemstack.getItem() == Blocks.CARVED_PUMPKIN.asItem()) { ++ // Purpur start - Snowman drop and put back pumpkin ++ } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemInHand.getItem() == Blocks.CARVED_PUMPKIN.asItem()) { + setPumpkin(true); + if (!player.getAbilities().instabuild) { -+ itemstack.shrink(1); ++ itemInHand.shrink(1); + } + return InteractionResult.SUCCESS; -+ // Purpur end ++ // Purpur end - Snowman drop and put back pumpkin } else { - return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur ++ return tryRide(player, hand); // Purpur - Ridables } } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java -index f9fdc600dc680c55219fcbf9bc8f151a733a093c..36a56553702fa6e4a2ac92b3639c210c94faee73 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Squid.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java -@@ -46,13 +46,67 @@ public class Squid extends AgeableWaterCreature { +diff --git a/net/minecraft/world/entity/animal/Squid.java b/net/minecraft/world/entity/animal/Squid.java +index 687ac3f50ed517a0b4df70c0c0a01215691ac718..0a103e6f3f6b0ec05c5d22b1258eb6e2f776e7dd 100644 +--- a/net/minecraft/world/entity/animal/Squid.java ++++ b/net/minecraft/world/entity/animal/Squid.java +@@ -46,13 +46,77 @@ public class Squid extends AgeableWaterCreature { - public Squid(EntityType type, Level world) { - super(type, world); -- //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random -+ if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random // Purpur + public Squid(EntityType entityType, Level level) { + super(entityType, level); +- //this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random ++ if (!level.purpurConfig.entitySharedRandom) this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random // Purpur - Add toggle for RNG manipulation this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; } -+ // Purpur start ++ // Purpur start - Stop squids floating on top of water ++ @Override ++ public net.minecraft.world.phys.AABB getAxisForFluidCheck() { ++ // Stops squids from floating just over the water ++ return super.getAxisForFluidCheck().offsetY(level().purpurConfig.squidOffsetWaterCheck); ++ } ++ // Purpur end - Stop squids floating on top of water ++ ++ // Purpur start - Flying squids! Oh my! ++ public boolean canFly() { ++ return this.level().purpurConfig.squidsCanFly; ++ } ++ ++ @Override ++ public boolean isInWater() { ++ return this.wasTouchingWater || canFly(); ++ } ++ // Purpur end - Flying squids! Oh my! ++ ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.squidRidable; @@ -5960,7 +5848,7 @@ index f9fdc600dc680c55219fcbf9bc8f151a733a093c..36a56553702fa6e4a2ac92b3639c210c + return level().purpurConfig.squidControllable; + } + -+ protected void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) { ++ protected static void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) { + double rad = Math.toRadians(degrees); + double cos = Math.cos(rad); + double sine = Math.sin(rad); @@ -5969,59 +5857,50 @@ index f9fdc600dc680c55219fcbf9bc8f151a733a093c..36a56553702fa6e4a2ac92b3639c210c + vector.setX(cos * x - sine * z); + vector.setZ(sine * x + cos * z); + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.squidMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.squidScale); + } ++ // Purpur end - Configurable entity base attributes + -+ @Override -+ public net.minecraft.world.phys.AABB getAxisForFluidCheck() { -+ // Stops squids from floating just over the water -+ return super.getAxisForFluidCheck().offsetY(level().purpurConfig.squidOffsetWaterCheck); -+ } -+ -+ public boolean canFly() { -+ return this.level().purpurConfig.squidsCanFly; -+ } -+ -+ @Override -+ public boolean isInWater() { -+ return this.wasTouchingWater || canFly(); -+ } -+ ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.squidTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.squidAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new Squid.SquidFleeGoal()); } -@@ -127,6 +181,7 @@ public class Squid extends AgeableWaterCreature { +@@ -127,6 +191,7 @@ public class Squid extends AgeableWaterCreature { } if (this.isInWaterOrBubble()) { -+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur ++ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur - Flying squids! Oh my! if (this.tentacleMovement < (float) Math.PI) { float f = this.tentacleMovement / (float) Math.PI; this.tentacleAngle = Mth.sin(f * f * (float) Math.PI) * (float) Math.PI * 0.25F; -@@ -305,10 +360,41 @@ public class Squid extends AgeableWaterCreature { +@@ -307,10 +372,41 @@ public class Squid extends AgeableWaterCreature { @Override public void tick() { -+ // Purpur start ++ // Purpur start - Ridables + net.minecraft.world.entity.player.Player rider = squid.getRider(); + if (rider != null && squid.isControllable()) { + if (rider.jumping) { @@ -6051,24 +5930,24 @@ index f9fdc600dc680c55219fcbf9bc8f151a733a093c..36a56553702fa6e4a2ac92b3639c210c + } + return; + } -+ // Purpur end - int i = this.squid.getNoActionTime(); - if (i > 100) { ++ // Purpur end - Ridables + int noActionTime = this.squid.getNoActionTime(); + if (noActionTime > 100) { this.squid.movementVector = Vec3.ZERO; - } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) { -+ } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur ++ } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur - Flying squids! Oh my! float f = this.squid.getRandom().nextFloat() * (float) (Math.PI * 2); - this.squid.movementVector = new Vec3( - (double)(Mth.cos(f) * 0.2F), (double)(-0.1F + this.squid.getRandom().nextFloat() * 0.2F), (double)(Mth.sin(f) * 0.2F) -diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -index 8d59d606bdaaea7c64389572b2810b65414a1533..7f9d3177285f6496c4313da63f0fd0cc78266586 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -@@ -67,6 +67,33 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder - super(type, world); + this.squid.movementVector = new Vec3(Mth.cos(f) * 0.2F, -0.1F + this.squid.getRandom().nextFloat() * 0.2F, Mth.sin(f) * 0.2F); + } +diff --git a/net/minecraft/world/entity/animal/TropicalFish.java b/net/minecraft/world/entity/animal/TropicalFish.java +index fa5f7f7d54083f9ea2095dd44362069d00e0b9a5..41074e7847583583331ef8d685a9f9b85ddf0243 100644 +--- a/net/minecraft/world/entity/animal/TropicalFish.java ++++ b/net/minecraft/world/entity/animal/TropicalFish.java +@@ -67,6 +67,39 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.tropicalFishRidable; @@ -6078,35 +5957,41 @@ index 8d59d606bdaaea7c64389572b2810b65414a1533..7f9d3177285f6496c4313da63f0fd0cc + public boolean isControllable() { + return level().purpurConfig.tropicalFishControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.tropicalFishMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.tropicalFishTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.tropicalFishAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + - public static String getPredefinedName(int variant) { - return "entity.minecraft.tropical_fish.predefined." + variant; + public static String getPredefinedName(int variantId) { + return "entity.minecraft.tropical_fish.predefined." + variantId; } -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 d6605c15111dbdb6ee61a24822bc0a9aed7198d6..c9e307452a097329c26893673055cfb72a43e4c7 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -86,6 +86,44 @@ public class Turtle extends Animal { +diff --git a/net/minecraft/world/entity/animal/Turtle.java b/net/minecraft/world/entity/animal/Turtle.java +index 354ec2b987882d8f40ef4ac5257183d2fda73bb8..bc6acbc801e5b371859e731b8419736324e81bf0 100644 +--- a/net/minecraft/world/entity/animal/Turtle.java ++++ b/net/minecraft/world/entity/animal/Turtle.java +@@ -84,6 +84,52 @@ public class Turtle extends Animal { this.moveControl = new Turtle.TurtleMoveControl(this); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.turtleRidable; @@ -6121,84 +6006,91 @@ index d6605c15111dbdb6ee61a24822bc0a9aed7198d6..c9e307452a097329c26893673055cfb7 + public boolean isControllable() { + return level().purpurConfig.turtleControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.turtleMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.turtleScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.turtleBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.turtleTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.turtleAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + - public void setHomePos(BlockPos pos) { - this.entityData.set(Turtle.HOME_POS, pos); + public void setHomePos(BlockPos homePos) { + this.entityData.set(HOME_POS, homePos); } -@@ -188,6 +226,7 @@ public class Turtle extends Animal { +@@ -188,6 +234,7 @@ public class Turtle extends Animal { @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2D)); - this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0D)); - this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0D)); -@@ -349,13 +388,15 @@ public class Turtle extends Animal { - return this.isBaby() ? Turtle.BABY_DIMENSIONS : super.getDefaultDimensions(pose); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2)); + this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0)); + this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0)); +@@ -539,12 +586,14 @@ public class Turtle extends Animal { + } } -- private static class TurtleMoveControl extends MoveControl { -+ private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - +- static class TurtleMoveControl extends MoveControl { ++ static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables private final Turtle turtle; -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur ++ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables - TurtleMoveControl(Turtle turtle) { - super(turtle); - this.turtle = turtle; -+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur + TurtleMoveControl(Turtle mob) { + super(mob); + this.turtle = mob; ++ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur - Ridables } private void updateSpeed() { -@@ -375,7 +416,7 @@ public class Turtle extends Animal { +@@ -563,7 +612,7 @@ public class Turtle extends Animal { } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables this.updateSpeed(); if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) { - double d0 = this.wantedX - this.turtle.getX(); -@@ -391,7 +432,7 @@ public class Turtle extends Animal { - + double d = this.wantedX - this.turtle.getX(); +@@ -577,7 +626,7 @@ public class Turtle extends Animal { + float f = (float)(Mth.atan2(d2, d) * 180.0F / (float)Math.PI) - 90.0F; this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F)); this.turtle.yBodyRot = this.turtle.getYRot(); -- float f1 = (float) (this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float f1 = (float) (this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); - +- float f1 = (float)(this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); ++ float f1 = (float)(this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1)); - this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D)); -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 fb84ee1225cd762ef306d1fc3e1baed42c034a3c..420345f130a40c4f59a021a4bdce3e218dc87cde 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -@@ -103,6 +103,37 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder type = entity.getType(); + return type == EntityType.SHEEP || type == EntityType.RABBIT || type == EntityType.FOX; }; -+ // Purpur start - rabid wolf spawn chance ++ // Purpur start - Configurable chance for wolves to spawn rabid + private boolean isRabid = false; + private static final TargetingConditions.Selector RABID_PREDICATE = (entity, ignored) -> entity instanceof net.minecraft.server.level.ServerPlayer || entity instanceof net.minecraft.world.entity.Mob; + private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR); @@ -6228,47 +6120,15 @@ index fb84ee1225cd762ef306d1fc3e1baed42c034a3c..420345f130a40c4f59a021a4bdce3e21 + super.tick(); + } + } -+ // Purpur end ++ // Purpur end - Configurable chance for wolves to spawn rabid private static final float START_HEALTH = 8.0F; private static final float TAME_HEALTH = 40.0F; private static final float ARMOR_REPAIR_UNIT = 0.125F; -@@ -124,12 +155,87 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(this, Llama.class, 24.0F, 1.5D, 1.5D)); -+ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur + this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5, 1.5)); ++ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur - Configurable chance for wolves to spawn rabid this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F)); - this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0D, true)); - this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0D, 10.0F, 2.0F)); -@@ -138,11 +244,12 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(this, Player.class, 10, true, false, this::isAngryAt)); -- this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); -+ // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); // Purpur - moved to updatePathfinders() +- this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR)); ++ // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR)); // Purpur - Configurable chance for wolves to spawn rabid - moved to updatePathfinders() this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false)); this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true)); -@@ -191,6 +298,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder { - nbt.putString("variant", resourcekey.location().toString()); - }); -@@ -208,6 +316,10 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder compound.putString("variant", resourceKey.location().toString())); + this.addPersistentAngerSaveData(compound); } -@@ -226,6 +338,12 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.wolfNaturalRabid; + this.setVariant(holder); ++ // Purpur start - Configurable chance for wolves to spawn rabid ++ this.isRabid = level.getLevel().purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= level.getLevel().purpurConfig.wolfNaturalRabid; + this.updatePathfinders(false); -+ // Purpur end -+ - return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); ++ // Purpur end - Configurable chance for wolves to spawn rabid + return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } -@@ -269,6 +387,11 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder type, Level world) { - super(type, world); + public Allay(EntityType entityType, Level level) { + super(entityType, level); - this.moveControl = new FlyingMoveControl(this, 20, true); -+ // Purpur start ++ // Purpur start - Ridables + this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F); + this.moveControl = new FlyingMoveControl(this, 20, true) { + @Override @@ -6432,15 +6334,15 @@ index 5470b000760728b6703fccab5448a3f62d4335c8..b9f39d873e243da34aafa9f285978d2d + } + } + }; -+ // Purpur end ++ // Purpur end - Ridables this.setCanPickUpLoot(this.canPickUpLoot()); this.vibrationUser = new Allay.VibrationUser(); this.vibrationData = new VibrationSystem.Data(); -@@ -119,6 +132,34 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS +@@ -136,6 +149,36 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS } // CraftBukkit end -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.allayRidable; @@ -6458,37 +6360,38 @@ index 5470b000760728b6703fccab5448a3f62d4335c8..b9f39d873e243da34aafa9f285978d2d + + @Override + protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.allayMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.allayScale); + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + @Override protected Brain.Provider brainProvider() { - return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); -@@ -220,7 +261,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS + return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); +@@ -244,6 +287,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS private int behaviorTick = 0; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); AllayAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -index 844a5a61428234adcc12e7e8af8f2781a4ad4be8..35e89ecad020a2a5320a757b154fbf409bb2b19b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -@@ -80,6 +80,34 @@ public class Armadillo extends Animal { - return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 12.0D).add(Attributes.MOVEMENT_SPEED, 0.14D); +diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +index 86e78ce740b27f9714145a690e8b182a2ccb3fb9..a24ed1747fb8836927ac41b822dc666862701516 100644 +--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java ++++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +@@ -78,6 +78,38 @@ public class Armadillo extends Animal { + return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 12.0).add(Attributes.MOVEMENT_SPEED, 0.14); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.armadilloRidable; @@ -6503,31 +6406,35 @@ index 844a5a61428234adcc12e7e8af8f2781a4ad4be8..35e89ecad020a2a5320a757b154fbf40 + public boolean isControllable() { + return level().purpurConfig.armadilloControllable; + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.armadilloMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.armadilloScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.armadilloBreedingTicks; + } -+ // Purpur end ++ // Purpur end - Make entity breeding times configurable + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index dab55d73069018b17769b82a79acb5b8fe62772d..7e0b7485e06289478976c7a289a8fa5fe4974d99 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -@@ -98,6 +98,44 @@ public class Axolotl extends Animal implements VariantHolder, B +diff --git a/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/net/minecraft/world/entity/animal/axolotl/Axolotl.java +index 4fb36e2a6d71b79219e10f5089eb0daebf830ee7..1323cedcacd3072cdf5f1eac644688cd098b53db 100644 +--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java ++++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java +@@ -113,6 +113,52 @@ public class Axolotl extends Animal implements VariantHolder, B this.lookControl = new Axolotl.AxolotlLookControl(this, 20); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.axolotlRidable; @@ -6540,105 +6447,114 @@ index dab55d73069018b17769b82a79acb5b8fe62772d..7e0b7485e06289478976c7a289a8fa5f + + @Override + protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.axolotlMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.axolotlScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.axolotlBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.axolotlTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.axolotlAlwaysDropExp; + } -+ // Purpur end ++ // Purpur end - Mobs always drop experience + @Override - public float getWalkTargetValue(BlockPos pos, LevelReader world) { + public float getWalkTargetValue(BlockPos pos, LevelReader level) { return 0.0F; -@@ -293,7 +331,7 @@ public class Axolotl extends Animal implements VariantHolder, B +@@ -301,6 +347,7 @@ public class Axolotl extends Animal implements VariantHolder, B private int behaviorTick = 0; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); AxolotlAi.updateActivity(this); - if (!this.isNoAi()) { -@@ -514,14 +552,22 @@ public class Axolotl extends Animal implements VariantHolder, B - private static class AxolotlMoveControl extends SmoothSwimmingMoveControl { - - private final Axolotl axolotl; -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - - public AxolotlMoveControl(Axolotl axolotl) { - super(axolotl, 85, 10, 0.1F, 0.5F, false); - this.axolotl = axolotl; -+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(axolotl, 0.5D); // Purpur - } - - @Override - public void tick() { -+ // Purpur start -+ if (axolotl.getRider() != null && axolotl.isControllable()) { -+ waterController.purpurTick(axolotl.getRider()); -+ return; -+ } -+ // Purpur end - if (!this.axolotl.isPlayingDead()) { - super.tick(); - } -@@ -536,9 +582,9 @@ public class Axolotl extends Animal implements VariantHolder, B +@@ -550,23 +597,31 @@ public class Axolotl extends Animal implements VariantHolder, B } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (!Axolotl.this.isPlayingDead()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index 002e8b5a7037e48a7d9bd84c321b152f40fe1fce..542b972b266eae642b220025c6173c738fcff80f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -85,6 +85,17 @@ public class Camel extends AbstractHorse { - navigation.setCanWalkOverFences(true); } -+ // Purpur start + static class AxolotlMoveControl extends SmoothSwimmingMoveControl { + private final Axolotl axolotl; ++ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables + + public AxolotlMoveControl(Axolotl axolotl) { + super(axolotl, 85, 10, 0.1F, 0.5F, false); + this.axolotl = axolotl; ++ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(axolotl, 0.5D); // Purpur - Ridables + } + + @Override + public void tick() { ++ // Purpur start - Ridables ++ if (axolotl.getRider() != null && axolotl.isControllable()) { ++ waterController.purpurTick(axolotl.getRider()); ++ return; ++ } ++ // Purpur end - Ridables + if (!this.axolotl.isPlayingDead()) { + super.tick(); + } +diff --git a/net/minecraft/world/entity/animal/camel/Camel.java b/net/minecraft/world/entity/animal/camel/Camel.java +index 1ac4b13554d2699c3e04d41946e1adfd5e854a17..64ff0d2923f16a567aa753cad028a1b21c20101b 100644 +--- a/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/net/minecraft/world/entity/animal/camel/Camel.java +@@ -81,6 +81,20 @@ public class Camel extends AbstractHorse { + groundPathNavigation.setCanWalkOverFences(true); + } + ++ // Purpur start - Ridables + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.camelRidableInWater; + } ++ // Purpur end - Ridables + ++ // Purpur start - Make entity breeding times configurable ++ @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.camelBreedingTicks; + } -+ // Purpur end ++ // Purpur end - Make entity breeding times configurable + @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -306,6 +317,23 @@ public class Camel extends AbstractHorse { + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); +@@ -308,6 +322,23 @@ public class Camel extends AbstractHorse { return this.dashCooldown; } -+ // Purpur start ++ // Purpur start - Configurable entity base attributes + @Override + public float generateMaxHealth(net.minecraft.util.RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.camelMaxHealthMin, this.level().purpurConfig.camelMaxHealthMax); @@ -6653,29 +6569,29 @@ index 002e8b5a7037e48a7d9bd84c321b152f40fe1fce..542b972b266eae642b220025c6173c73 + public double generateSpeed(net.minecraft.util.RandomSource random) { + return generateSpeed(this.level().purpurConfig.camelMovementSpeedMin, this.level().purpurConfig.camelMovementSpeedMax); + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + @Override protected SoundEvent getAmbientSound() { return SoundEvents.CAMEL_AMBIENT; -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index e6e12142c24e7fff99423678bc47c2e1d38e8afd..0f077f9be003a17b77e9b29fa2f398a4de1fa3c5 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java +index 10a0779bf8611ade19e64031bb00beb277e98598..4ecc6b6247a6ab14a5d46f9a05d5df8412ae2a5f 100644 +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java @@ -104,6 +104,8 @@ public class Frog extends Animal implements VariantHolder> { public final AnimationState croakAnimationState = new AnimationState(); public final AnimationState tongueAnimationState = new AnimationState(); public final AnimationState swimIdleAnimationState = new AnimationState(); -+ private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur -+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur ++ private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur - Ridables ++ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur - Ridables - public Frog(EntityType type, Level world) { - super(type, world); -@@ -111,6 +113,58 @@ public class Frog extends Animal implements VariantHolder> { + public Frog(EntityType entityType, Level level) { + super(entityType, level); +@@ -111,8 +113,62 @@ public class Frog extends Animal implements VariantHolder> { this.setPathfindingMalus(PathType.WATER, 4.0F); this.setPathfindingMalus(PathType.TRAPDOOR, -1.0F); this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start ++ // Purpur start - Ridables + this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F); + this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); + this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { @@ -6694,10 +6610,10 @@ index e6e12142c24e7fff99423678bc47c2e1d38e8afd..0f077f9be003a17b77e9b29fa2f398a4 + } + } + }; -+ // Purpur end ++ // Purpur end - Ridables + } + -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.frogRidable; @@ -6711,57 +6627,60 @@ index e6e12142c24e7fff99423678bc47c2e1d38e8afd..0f077f9be003a17b77e9b29fa2f398a4 + @Override + public boolean isControllable() { + return level().purpurConfig.frogControllable; -+ } -+ + } + + @Override + protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + } + + @Override + public float getJumpPower() { + return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower(); + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Make entity breeding times configurable ++ @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.frogBreedingTicks; - } - ++ } ++ // Purpur end - Make entity breeding times configurable @Override -@@ -185,7 +239,7 @@ public class Frog extends Animal implements VariantHolder> { + protected Brain.Provider brainProvider() { + return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); +@@ -185,6 +241,7 @@ public class Frog extends Animal implements VariantHolder> { private int behaviorTick = 0; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); FrogAi.updateActivity(this); - super.customServerAiStep(world); -@@ -379,7 +433,7 @@ public class Frog extends Animal implements VariantHolder> { - return world.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); +@@ -375,7 +432,7 @@ public class Frog extends Animal implements VariantHolder> { + return level.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(level, pos); } - class FrogLookControl extends LookControl { -+ class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - FrogLookControl(final Mob entity) { - super(entity); ++ class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables + FrogLookControl(final Mob mob) { + super(mob); } -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index db617182d75f3418fef4b34cd8eddbfc90fc5a9e..2b3e722a5c802fbbebb339df4398905e144fd174 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -49,13 +49,50 @@ public class Tadpole extends AbstractFish { - protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); - protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); +diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java +index 77691e10f7c511eca4384f2974e538d78d55c2ca..0ca35514a920dddf230d749bc1a5fe15f1c7940a 100644 +--- a/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -61,13 +61,50 @@ public class Tadpole extends AbstractFish { + MemoryModuleType.IS_PANICKING + ); public boolean ageLocked; // Paper -+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur ++ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur - Ridables - public Tadpole(EntityType type, Level world) { - super(type, world); + public Tadpole(EntityType entityType, Level level) { + super(entityType, level); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start ++ // Purpur start - Ridables + this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); + this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { + @Override @@ -6775,11 +6694,11 @@ index db617182d75f3418fef4b34cd8eddbfc90fc5a9e..2b3e722a5c802fbbebb339df4398905e + } + } + }; -+ // Purpur end ++ // Purpur end - Ridables this.lookControl = new SmoothSwimmingLookControl(this, 10); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.tadpoleRidable; @@ -6797,31 +6716,30 @@ index db617182d75f3418fef4b34cd8eddbfc90fc5a9e..2b3e722a5c802fbbebb339df4398905e + + @Override + protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + } -+ // Purpur end ++ // Purpur end - Ridables + @Override - protected PathNavigation createNavigation(Level world) { - return new WaterBoundPathNavigation(this, world); -@@ -84,7 +121,7 @@ public class Tadpole extends AbstractFish { + protected PathNavigation createNavigation(Level level) { + return new WaterBoundPathNavigation(this, level); +@@ -96,6 +133,7 @@ public class Tadpole extends AbstractFish { private int behaviorTick = 0; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); TadpoleAi.updateActivity(this); - super.customServerAiStep(world); -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 509d1d1497c3f015686dbc485b82649a98356a02..e9a42f8a73e17c6a7fe5603686c67424e1f8ed9b 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 -@@ -91,6 +91,38 @@ public class Goat extends Animal { - }); +diff --git a/net/minecraft/world/entity/animal/goat/Goat.java b/net/minecraft/world/entity/animal/goat/Goat.java +index 35d492106506c28412fea5c59c7b67c809ce231c..7107cc2462e7d33bca413a1b051822cc1bd61bfa 100644 +--- a/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/net/minecraft/world/entity/animal/goat/Goat.java +@@ -109,6 +109,44 @@ public class Goat extends Animal { + .orElseGet(() -> new ItemStack(Items.GOAT_HORN)); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.goatRidable; @@ -6836,63 +6754,69 @@ index 509d1d1497c3f015686dbc485b82649a98356a02..e9a42f8a73e17c6a7fe5603686c67424 + public boolean isControllable() { + return level().purpurConfig.goatControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.goatBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.goatTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.goatAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected Brain.Provider brainProvider() { - return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES); -@@ -193,7 +225,7 @@ public class Goat extends Animal { + return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); +@@ -185,6 +223,7 @@ public class Goat extends Animal { private int behaviorTick = 0; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); GoatAi.updateActivity(this); - super.customServerAiStep(world); -@@ -393,6 +425,7 @@ public class Goat extends Animal { +@@ -388,6 +427,7 @@ public class Goat extends Animal { // Paper start - Goat ram API public void ram(net.minecraft.world.entity.LivingEntity entity) { -+ if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), (org.bukkit.entity.LivingEntity) entity.getBukkitLivingEntity()).callEvent()) return; // Purpur ++ if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), entity.getBukkitLivingEntity()).callEvent()) return; // Purpur - Added goat ram event Brain brain = this.getBrain(); brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); -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 8aed30cdbbfdd42c20dcd4c8773c8a0ee21a980d..b258be16f32ffd58ac8406deac9423cb01ca9a5c 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 -@@ -228,11 +228,59 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, +diff --git a/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index d52a8315f1e6876c26c732f4c4caa47bc6bebf6e..56dc7011ed07f0bd5870fbadde2b5c0c630c5edd 100644 +--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -206,11 +206,61 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - protected AbstractHorse(EntityType type, Level world) { - super(type, world); + protected AbstractHorse(EntityType entityType, Level level) { + super(entityType, level); + this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller + this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller this.createInventory(); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return false; // vanilla handles + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.generateMaxHealth(random)); @@ -6924,51 +6848,53 @@ index 8aed30cdbbfdd42c20dcd4c8773c8a0ee21a980d..b258be16f32ffd58ac8406deac9423cb + } + + protected double generateJumpStrength(RandomSource random) { -+ return 0.4000000059604645D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D; ++ return 0.4F + random.nextDouble() * 0.2 + random.nextDouble() * 0.2 + random.nextDouble() * 0.2; + } + + protected double generateSpeed(RandomSource random) { -+ return (0.44999998807907104D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D) * 0.25D; ++ return (0.45F + random.nextDouble() * 0.3 + random.nextDouble() * 0.3 + random.nextDouble() * 0.3) * 0.25; + } ++ // Purpur end - Configurable entity base attributes + @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D)); - this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class)); -@@ -243,6 +291,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new PanicGoal(this, 1.2)); + this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2)); + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0, AbstractHorse.class)); +@@ -221,6 +271,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, if (this.canPerformRearing()) { this.goalSelector.addGoal(9, new RandomStandGoal(this)); } -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables this.addBehaviourGoals(); } -@@ -1269,7 +1318,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - entityData = new AgeableMob.AgeableMobGroupData(0.2F); +@@ -1207,7 +1258,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + spawnGroupData = new AgeableMob.AgeableMobGroupData(0.2F); } -- this.randomizeAttributes(world.getRandom()); -+ // this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes() - return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); +- this.randomizeAttributes(level.getRandom()); ++ //this.randomizeAttributes(level.getRandom()); // Purpur - replaced by initAttributes() + return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -index 5cafdde956d7a5b00cd5aec5c44849639307363d..dbf9fa551023cc9bf634fd5f5d504c4d689264cd 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -@@ -16,6 +16,43 @@ public class Donkey extends AbstractChestedHorse { - super(type, world); +diff --git a/net/minecraft/world/entity/animal/horse/Donkey.java b/net/minecraft/world/entity/animal/horse/Donkey.java +index 9b97f3d3675f5051b18a68ff7fa056d859a283e9..3e0181578a6f2d22d1da3776abf30bf97d124620 100644 +--- a/net/minecraft/world/entity/animal/horse/Donkey.java ++++ b/net/minecraft/world/entity/animal/horse/Donkey.java +@@ -16,6 +16,51 @@ public class Donkey extends AbstractChestedHorse { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public float generateMaxHealth(net.minecraft.util.RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax); @@ -6983,40 +6909,48 @@ index 5cafdde956d7a5b00cd5aec5c44849639307363d..dbf9fa551023cc9bf634fd5f5d504c4d + public double generateSpeed(net.minecraft.util.RandomSource random) { + return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.donkeyBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.donkeyTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.donkeyAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected SoundEvent getAmbientSound() { return SoundEvents.DONKEY_AMBIENT; -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -index b5ec7c8ad0e482930d1a54b590b26093f4e477ea..780cad91fff78bda6264cfd78ff7a408a79e8a2f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -@@ -43,6 +43,43 @@ public class Horse extends AbstractHorse implements VariantHolder { - super(type, world); +diff --git a/net/minecraft/world/entity/animal/horse/Horse.java b/net/minecraft/world/entity/animal/horse/Horse.java +index c6d0700f29d6c8123e96efe225faf2d99202ac81..be0d636ca894c5995f28f59c196cd8e56dd228c4 100644 +--- a/net/minecraft/world/entity/animal/horse/Horse.java ++++ b/net/minecraft/world/entity/animal/horse/Horse.java +@@ -43,6 +43,51 @@ public class Horse extends AbstractHorse implements VariantHolder { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public float generateMaxHealth(RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax); @@ -7031,40 +6965,47 @@ index b5ec7c8ad0e482930d1a54b590b26093f4e477ea..780cad91fff78bda6264cfd78ff7a408 + public double generateSpeed(RandomSource random) { + return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.horseBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.horseTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.horseAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void randomizeAttributes(RandomSource random) { - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)generateMaxHealth(random::nextInt)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index 18bd483fe46de3d9dc129bffbccfba9d4cab9550..8401c7ae749f6300281cbd6b2bfc77f03d5eb9ea 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -72,11 +72,86 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { - super(type, world); + public Llama(EntityType entityType, Level level) { + super(entityType, level); this.getNavigation().setRequiredPathLength(40.0F); this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value -+ // Purpur start ++ // Purpur start - Ridables + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this) { + @Override + public void tick() { @@ -7085,10 +7026,10 @@ index 18bd483fe46de3d9dc129bffbccfba9d4cab9550..8401c7ae749f6300281cbd6b2bfc77f0 + } + } + }; -+ // Purpur end ++ // Purpur end - Ridables + } + -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.llamaRidable; @@ -7108,8 +7049,9 @@ index 18bd483fe46de3d9dc129bffbccfba9d4cab9550..8401c7ae749f6300281cbd6b2bfc77f0 + public boolean isSaddled() { + return super.isSaddled() || (isTamed()); + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public float generateMaxHealth(RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.llamaMaxHealthMin, this.level().purpurConfig.llamaMaxHealthMax); @@ -7124,84 +7066,93 @@ index 18bd483fe46de3d9dc129bffbccfba9d4cab9550..8401c7ae749f6300281cbd6b2bfc77f0 + public double generateSpeed(RandomSource random) { + return generateSpeed(this.level().purpurConfig.llamaMovementSpeedMin, this.level().purpurConfig.llamaMovementSpeedMax); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.llamaBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.llamaTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.llamaAlwaysDropExp; } ++ // Purpur end - Mobs always drop experience public boolean isTraderLlama() { -@@ -107,6 +182,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); public int time; public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals -+ // Purpur start ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms + private net.minecraft.world.entity.monster.Phantom targetPhantom; + private int phantomBeamTicks = 0; + private int phantomDamageCooldown = 0; + private int idleCooldown = 0; -+ // Purpur end ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms - public EndCrystal(EntityType type, Level world) { - super(type, world); -@@ -43,6 +49,22 @@ public class EndCrystal extends Entity { + public EndCrystal(EntityType entityType, Level level) { + super(entityType, level); +@@ -38,6 +44,24 @@ public class EndCrystal extends Entity { this.setPos(x, y, z); } ++ // Purpur start - End crystal explosion options + public boolean shouldExplode() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplode : level().purpurConfig.baselessEndCrystalExplode; + } @@ -7511,18 +7495,18 @@ index 7cb3d69a69e0e3ef4b7f9f9c8b1eb67edb5d116d..b1db1e92de3a88a0f0e0fdb42b0bf973 + public Level.ExplosionInteraction getExplosionEffect() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionEffect : level().purpurConfig.baselessEndCrystalExplosionEffect; + } ++ // Purpur end - End crystal explosion options + @Override protected Entity.MovementEmission getMovementEmission() { return Entity.MovementEmission.NONE; -@@ -80,8 +102,52 @@ public class EndCrystal extends Entity { - } +@@ -74,6 +98,51 @@ public class EndCrystal extends Entity { } - // Paper end - Fix invulnerable end crystals -+ if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur -+ } + } + // Paper end - Fix invulnerable end crystals ++ if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur - End Crystal Cramming + -+ // Purpur start ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms + if (level().purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) { + return; // on cooldown + } @@ -7549,7 +7533,7 @@ index 7cb3d69a69e0e3ef4b7f9f9c8b1eb67edb5d116d..b1db1e92de3a88a0f0e0fdb42b0bf973 + } else { + forgetPhantom(); // attacked long enough + } - } ++ } + } + + private void attackPhantom(net.minecraft.world.entity.monster.Phantom phantom) { @@ -7557,56 +7541,55 @@ index 7cb3d69a69e0e3ef4b7f9f9c8b1eb67edb5d116d..b1db1e92de3a88a0f0e0fdb42b0bf973 + phantomBeamTicks = 60; + targetPhantom = phantom; + } - ++ + private void forgetPhantom() { + targetPhantom = null; + setBeamTarget(null); + phantomBeamTicks = 0; + phantomDamageCooldown = 0; + idleCooldown = 60; -+ // Purpur end ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms } @Override -@@ -128,16 +194,18 @@ public class EndCrystal extends Entity { +@@ -119,15 +188,17 @@ public class EndCrystal extends Entity { } // CraftBukkit end - if (!source.is(DamageTypeTags.IS_EXPLOSION)) { -+ if (shouldExplode()) {// Purpur - DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null; - + if (!damageSource.is(DamageTypeTags.IS_EXPLOSION)) { ++ if (shouldExplode()) {// Purpur - End crystal explosion options + DamageSource damageSource1 = damageSource.getEntity() != null ? this.damageSources().explosion(this, damageSource.getEntity()) : null; // CraftBukkit start -- ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); -+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur +- org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); ++ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur - End crystal explosion options if (event.isCancelled()) { return false; } - this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause -- world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); -+ world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur -+ } else this.unsetRemoved(); // Purpur + this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // Paper - add Bukkit remove cause +- level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); ++ level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur - End crystal explosion options ++ } else this.unsetRemoved(); // Purpur - End crystal explosion options } else { - this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - } -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 2df8bf818345246cc1f88b93e4a3b62e61772efb..c9e3bb91ff506a35551a58f469ad2849e6058668 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 -@@ -108,6 +108,7 @@ public class EnderDragon extends Mob implements Enemy { - @Nullable - private BlockPos podium; - // Paper end - Allow changing the EnderDragon podium -+ private boolean hadRider; // Purpur + this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause + // CraftBukkit end +diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 403bcd056bf9c385599383983bf7a0cc117a1881..b2a0ba6faa117ad781aaa3e6932482d4d9c8a789 100644 +--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -90,6 +90,7 @@ public class EnderDragon extends Mob implements Enemy { + private final net.minecraft.world.level.Explosion explosionSource; // Paper - reusable source for CraftTNTPrimed.getSource() + @Nullable private BlockPos podium; + // Paper end ++ private boolean hadRider; // Purpur - Ridables - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -129,6 +130,37 @@ public class EnderDragon extends Mob implements Enemy { + public EnderDragon(EntityType entityType, Level level) { + super(EntityType.ENDER_DRAGON, level); +@@ -106,6 +107,37 @@ public class EnderDragon extends Mob implements Enemy { this.noPhysics = true; this.phaseManager = new EnderDragonPhaseManager(this); - this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit + this.explosionSource = new net.minecraft.world.level.ServerExplosion(level.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, net.minecraft.world.level.Explosion.BlockInteraction.DESTROY); // Paper + -+ // Purpur start ++ // Purpur start - Ridables + this.moveControl = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this) { + @Override + public void vanillaTick() { @@ -7624,10 +7607,10 @@ index 2df8bf818345246cc1f88b93e4a3b62e61772efb..c9e3bb91ff506a35551a58f469ad2849 + setYawPitch(rider.getYRot() - 180F, rider.xRotO * 0.5F); + } + }; -+ // Purpur end ++ // Purpur end - Ridables + } + -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.enderDragonRidable; @@ -7638,8 +7621,8 @@ index 2df8bf818345246cc1f88b93e4a3b62e61772efb..c9e3bb91ff506a35551a58f469ad2849 + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.enderDragonRidableInWater; } - public void setDragonFight(EndDragonFight fight) { -@@ -143,6 +175,27 @@ public class EnderDragon extends Mob implements Enemy { + public void setDragonFight(EndDragonFight dragonFight) { +@@ -120,6 +152,31 @@ public class EnderDragon extends Mob implements Enemy { return this.fightOrigin; } @@ -7652,26 +7635,30 @@ index 2df8bf818345246cc1f88b93e4a3b62e61772efb..c9e3bb91ff506a35551a58f469ad2849 + public double getMaxY() { + return level().purpurConfig.enderDragonMaxY; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.enderDragonMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.enderDragonTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0); } -@@ -184,6 +237,37 @@ public class EnderDragon extends Mob implements Enemy { +@@ -169,6 +226,37 @@ public class EnderDragon extends Mob implements Enemy { @Override public void aiStep() { -+ // Purpur start ++ // Purpur start - Ridables + boolean hasRider = getRider() != null && this.isControllable(); + if (hasRider) { + if (!hadRider) { @@ -7700,115 +7687,119 @@ index 2df8bf818345246cc1f88b93e4a3b62e61772efb..c9e3bb91ff506a35551a58f469ad2849 + this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(16.0F, 8.0F); + phaseManager.setPhase(EnderDragonPhase.HOLDING_PATTERN); // HoldingPattern + } -+ // Purpur end ++ // Purpur end - Ridables + this.processFlappingMovement(); if (this.level().isClientSide) { this.setHealth(this.getHealth()); -@@ -210,6 +294,8 @@ public class EnderDragon extends Mob implements Enemy { - float f; +@@ -197,6 +285,8 @@ public class EnderDragon extends Mob implements Enemy { + this.oFlapTime = this.flapTime; if (this.isDeadOrDying()) { -+ if (hasRider) ejectPassengers(); // Purpur ++ if (hasRider) ejectPassengers(); // Purpur - Ridables + - float f1 = (this.random.nextFloat() - 0.5F) * 8.0F; - - f = (this.random.nextFloat() - 0.5F) * 4.0F; -@@ -222,9 +308,9 @@ public class EnderDragon extends Mob implements Enemy { - - f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F); - f *= (float) Math.pow(2.0D, vec3d.y); + float f = (this.random.nextFloat() - 0.5F) * 8.0F; + float f1 = (this.random.nextFloat() - 0.5F) * 4.0F; + float f2 = (this.random.nextFloat() - 0.5F) * 8.0F; +@@ -206,9 +296,9 @@ public class EnderDragon extends Mob implements Enemy { + Vec3 deltaMovement = this.getDeltaMovement(); + float f1 = 0.2F / ((float)deltaMovement.horizontalDistance() * 10.0F + 1.0F); + f1 *= (float)Math.pow(2.0, deltaMovement.y); - if (this.phaseManager.getCurrentPhase().isSitting()) { -+ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur ++ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur - Ridables this.flapTime += 0.1F; - } else if (this.inWall) { -+ } else if (!hasRider && this.inWall) { // Purpur - this.flapTime += f * 0.5F; ++ } else if (!hasRider && this.inWall) { // Purpur - Ridables + this.flapTime += f1 * 0.5F; } else { - this.flapTime += f; -@@ -240,7 +326,7 @@ public class EnderDragon extends Mob implements Enemy { - float f4; - float f5; + this.flapTime += f1; +@@ -219,7 +309,7 @@ public class EnderDragon extends Mob implements Enemy { + this.flapTime = 0.5F; + } else { + this.flightHistory.record(this.getY(), this.getYRot()); +- if (this.level() instanceof ServerLevel serverLevel1) { ++ if (this.level() instanceof ServerLevel serverLevel1 && !hasRider) { // Purpur - Ridables + DragonPhaseInstance currentPhase = this.phaseManager.getCurrentPhase(); + currentPhase.doServerTick(serverLevel1); + if (this.phaseManager.getCurrentPhase() != currentPhase) { +@@ -298,7 +388,7 @@ public class EnderDragon extends Mob implements Enemy { + this.tickPart(this.body, sin1 * 0.5F, 0.0, -cos1 * 0.5F); + this.tickPart(this.wing1, cos1 * 4.5F, 2.0, sin1 * 4.5F); + this.tickPart(this.wing2, cos1 * -4.5F, 2.0, sin1 * -4.5F); +- if (this.level() instanceof ServerLevel serverLevel2 && this.hurtTime == 0) { ++ if (this.level() instanceof ServerLevel serverLevel2 && this.hurtTime == 0 && !hasRider) { // Purpur - Ridables + this.knockBack( + serverLevel2, + serverLevel2.getEntities( +@@ -348,9 +438,9 @@ public class EnderDragon extends Mob implements Enemy { + } -- if (world1 instanceof ServerLevel) { -+ if (world1 instanceof ServerLevel && !hasRider) { // Purpur - ServerLevel worldserver1 = (ServerLevel) world1; - DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase(); - -@@ -326,7 +412,7 @@ public class EnderDragon extends Mob implements Enemy { - if (world2 instanceof ServerLevel) { - ServerLevel worldserver2 = (ServerLevel) world2; - -- if (this.hurtTime == 0) { -+ if (!hasRider && this.hurtTime == 0) { // Purpur - this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); - this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); - this.hurt(worldserver2, worldserver2.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); -@@ -374,7 +460,7 @@ public class EnderDragon extends Mob implements Enemy { - if (world3 instanceof ServerLevel) { - ServerLevel worldserver3 = (ServerLevel) world3; - -- this.inWall = this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); -+ this.inWall = !hasRider && this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); // Purpur + if (this.level() instanceof ServerLevel serverLevel3) { +- this.inWall = this.checkWalls(serverLevel3, this.head.getBoundingBox()) ++ this.inWall = !hasRider && this.checkWalls(serverLevel3, this.head.getBoundingBox()) + | this.checkWalls(serverLevel3, this.neck.getBoundingBox()) +- | this.checkWalls(serverLevel3, this.body.getBoundingBox()); ++ | this.checkWalls(serverLevel3, this.body.getBoundingBox()); // Purpur - Ridables if (this.dragonFight != null) { this.dragonFight.updateDragon(this); } -@@ -510,7 +596,7 @@ public class EnderDragon extends Mob implements Enemy { - BlockState iblockdata = world.getBlockState(blockposition); - - if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) { -- if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { -+ if ((world.purpurConfig.enderDragonBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { // Purpur +@@ -464,7 +554,7 @@ public class EnderDragon extends Mob implements Enemy { + BlockPos blockPos = new BlockPos(i, i1, i2); + BlockState blockState = level.getBlockState(blockPos); + if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) { +- if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) { ++ if (level.purpurConfig.enderDragonBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) { // Purpur - Add mobGriefing bypass to everything affected // CraftBukkit start - Add blocks to list rather than destroying them - // flag1 = worldserver.removeBlock(blockposition, false) || flag1; + //flag1 = level.removeBlock(blockPos, false) || flag1; flag1 = true; -@@ -651,7 +737,7 @@ public class EnderDragon extends Mob implements Enemy { - boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); - short short0 = 500; - -- if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { -+ if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) { - short0 = 12000; - } - -@@ -1069,6 +1155,7 @@ public class EnderDragon extends Mob implements Enemy { +@@ -974,6 +1064,7 @@ public class EnderDragon extends Mob implements Enemy { @Override protected boolean canRide(Entity entity) { -+ if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur ++ if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles return false; } -@@ -1095,6 +1182,6 @@ public class EnderDragon extends Mob implements Enemy { +@@ -999,7 +1090,7 @@ public class EnderDragon extends Mob implements Enemy { @Override protected float sanitizeScale(float scale) { - return 1.0F; -+ return 1.0F; // Purpur - diff on change ++ return 1.0F; // Purpur - Configurable entity base attributes } - } -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 244e38db508efa3eebebb6392c4ebb0805367baf..ac8f05e20e810e61e4c77aa7a6e41008779563dc 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 -@@ -86,20 +86,60 @@ public class WitherBoss extends Monster implements RangedAttackMob { - return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable(); - }; - private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); -+ @Nullable private java.util.UUID summoner; // Purpur -+ private int shootCooldown = 0; // Purpur - // Paper start - private boolean canPortal = false; - public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } - // Paper end -+ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur + // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time. +@@ -1009,7 +1100,7 @@ public class EnderDragon extends Mob implements Enemy { + boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); + int i = 500; - public WitherBoss(EntityType type, Level world) { - super(type, world); - this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true); -- this.moveControl = new FlyingMoveControl(this, 10, false); -+ // Purpur start +- if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { ++ if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) { // Purpur - Ender dragon always drop full exp + i = 12000; + } + +diff --git a/net/minecraft/world/entity/boss/wither/WitherBoss.java b/net/minecraft/world/entity/boss/wither/WitherBoss.java +index afe43600c4976e01e61d716034a2823d50fb55cb..2d9f64cddbec782de1775ec6da67b16203e3fd90 100644 +--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -69,6 +69,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + private final int[] nextHeadUpdate = new int[2]; + private final int[] idleHeadUpdates = new int[2]; + private int destroyBlocksTick; ++ private int shootCooldown = 0; // Purpur - Ridables + private boolean canPortal = false; // Paper + public final ServerBossEvent bossEvent = (ServerBossEvent)new ServerBossEvent( + this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS +@@ -77,14 +78,161 @@ public class WitherBoss extends Monster implements RangedAttackMob { + private static final TargetingConditions.Selector LIVING_ENTITY_SELECTOR = (entity, level) -> !entity.getType().is(EntityTypeTags.WITHER_FRIENDS) + && entity.attackable(); + private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0).selector(LIVING_ENTITY_SELECTOR); ++ @Nullable private java.util.UUID summoner; // Purpur - Summoner API ++ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur - Ridables + + public WitherBoss(EntityType entityType, Level level) { + super(entityType, level); ++ // Purpur start - Ridables + this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F); + this.moveControl = new FlyingMoveControl(this, 10, false) { + @Override @@ -7820,22 +7811,13 @@ index 244e38db508efa3eebebb6392c4ebb0805367baf..ac8f05e20e810e61e4c77aa7a6e41008 + } + } + }; -+ // Purpur end ++ // Purpur end - Ridables + this.moveControl = new FlyingMoveControl(this, 10, false); this.setHealth(this.getMaxHealth()); this.xpReward = 50; } -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherScale); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.witherTakeDamageFromWater; -+ } -+ ++ // Purpur start - Summoner API + @Nullable + public java.util.UUID getSummoner() { + return summoner; @@ -7844,20 +7826,9 @@ index 244e38db508efa3eebebb6392c4ebb0805367baf..ac8f05e20e810e61e4c77aa7a6e41008 + public void setSummoner(@Nullable java.util.UUID summoner) { + this.summoner = summoner; + } ++ // Purpur end - Summoner API + -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.witherAlwaysDropExp; -+ } -+ - @Override - protected PathNavigation createNavigation(Level world) { - FlyingPathNavigation navigationflying = new FlyingPathNavigation(this, world); -@@ -110,13 +150,114 @@ public class WitherBoss extends Monster implements RangedAttackMob { - return navigationflying; - } - -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.witherRidable; @@ -7954,41 +7925,68 @@ index 244e38db508efa3eebebb6392c4ebb0805367baf..ac8f05e20e810e61e4c77aa7a6e41008 + skull.setPosRaw(headX, headY, headZ); + level().addFreshEntity(skull); + } -+ // Purpur end ++ // Purpur end - Ridables ++ ++ // Purpur start - Configurable entity base attributes ++ @Override ++ public void initAttributes() { ++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth); ++ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherScale); ++ } ++ // Purpur end - Configurable entity base attributes ++ ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.witherTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience ++ @Override ++ protected boolean isAlwaysExperienceDropper() { ++ return this.level().purpurConfig.witherAlwaysDropExp; ++ } ++ // Purpur end - Mobs always drop experience + + @Override + protected PathNavigation createNavigation(Level level) { + FlyingPathNavigation flyingPathNavigation = new FlyingPathNavigation(this, level); +@@ -95,11 +243,13 @@ public class WitherBoss extends Monster implements RangedAttackMob { + @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal()); - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 40, 20.0F)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0D)); + this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0, 40, 20.0F)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR)); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, LIVING_ENTITY_SELECTOR)); } -@@ -134,6 +275,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putInt("Invul", this.getInvulnerableTicks()); -+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur +@@ -117,6 +267,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); + compound.putInt("Invul", this.getInvulnerableTicks()); ++ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API } @Override -@@ -143,6 +285,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { +@@ -126,6 +277,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { if (this.hasCustomName()) { this.bossEvent.setName(this.getDisplayName()); } -+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur - ++ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API } -@@ -261,6 +404,15 @@ public class WitherBoss extends Monster implements RangedAttackMob { + @Override +@@ -257,6 +409,15 @@ public class WitherBoss extends Monster implements RangedAttackMob { @Override - protected void customServerAiStep(ServerLevel world) { -+ // Purpur start + protected void customServerAiStep(ServerLevel level) { ++ // Purpur start - Ridables + if (getRider() != null && this.isControllable()) { + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); @@ -7996,119 +7994,112 @@ index 244e38db508efa3eebebb6392c4ebb0805367baf..ac8f05e20e810e61e4c77aa7a6e41008 + if (shootCooldown > 0) { + shootCooldown--; + } -+ // Purpur end - int i; - ++ // Purpur end - Ridables if (this.getInvulnerableTicks() > 0) { -@@ -277,7 +429,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + int i = this.getInvulnerableTicks() - 1; + this.bossEvent.setProgress(1.0F - i / 220.0F); +@@ -269,7 +430,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + level.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); } // CraftBukkit end - - if (!this.isSilent()) { -+ if (!this.isSilent() && level().purpurConfig.witherPlaySpawnSound) { ++ if (!this.isSilent() && level.purpurConfig.witherPlaySpawnSound) { // Purpur - Toggle for Wither's spawn sound // CraftBukkit start - Use relative location for far away sounds - // worldserver.globalLevelEvent(1023, new BlockPosition(this), 0); - int viewDistance = world.getCraftServer().getViewDistance() * 16; -@@ -302,7 +454,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + // level.globalLevelEvent(1023, this.blockPosition(), 0); + int viewDistance = level.getCraftServer().getViewDistance() * 16; +@@ -294,7 +455,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { this.setInvulnerableTicks(i); if (this.tickCount % 10 == 0) { -- this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit -+ this.heal(this.getMaxHealth() / 33, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur +- this.heal(10.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit ++ this.heal(this.getMaxHealth() / 30, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur - Configurable entity base attributes } - } else { -@@ -362,7 +514,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + super.customServerAiStep(level); +@@ -346,7 +507,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { if (this.destroyBlocksTick > 0) { - --this.destroyBlocksTick; -- if (this.destroyBlocksTick == 0 && world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (this.destroyBlocksTick == 0 && (world.purpurConfig.witherBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur + this.destroyBlocksTick--; +- if (this.destroyBlocksTick == 0 && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (this.destroyBlocksTick == 0 && level.purpurConfig.witherBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected boolean flag = false; - - j = Mth.floor(this.getBbWidth() / 2.0F + 1.0F); -@@ -389,8 +541,10 @@ public class WitherBoss extends Monster implements RangedAttackMob { + int alternativeTarget = Mth.floor(this.getBbWidth() / 2.0F + 1.0F); + int floor = Mth.floor(this.getBbHeight()); +@@ -376,8 +537,10 @@ public class WitherBoss extends Monster implements RangedAttackMob { } } - if (this.tickCount % 20 == 0) { -- this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit -+ // Purpur start - customizable heal rate and amount +- this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit ++ // Purpur start - Customizable wither health and healing - customizable heal rate and amount + if (this.tickCount % level().purpurConfig.witherHealthRegenDelay == 0) { -+ this.heal(level().purpurConfig.witherHealthRegenAmount, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit -+ // Purpur end ++ this.heal(level().purpurConfig.witherHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit ++ // Purpur end - Customizable wither health and healing } this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); -@@ -579,11 +733,11 @@ public class WitherBoss extends Monster implements RangedAttackMob { +@@ -561,11 +724,11 @@ public class WitherBoss extends Monster implements RangedAttackMob { } - public int getAlternativeTarget(int headIndex) { -- return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex)); -+ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur + public int getAlternativeTarget(int head) { +- return this.entityData.get(DATA_TARGETS.get(head)); ++ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(DATA_TARGETS.get(head)); // Purpur - Ridables } - public void setAlternativeTarget(int headIndex, int id) { -- this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id); -+ if (getRider() == null || !this.isControllable()) this.entityData.set(WitherBoss.DATA_TARGETS.get(headIndex), id); // Purpur + public void setAlternativeTarget(int targetOffset, int newId) { +- this.entityData.set(DATA_TARGETS.get(targetOffset), newId); ++ if (getRider() == null || !this.isControllable()) this.entityData.set(DATA_TARGETS.get(targetOffset), newId); // Purpur - Ridables } public boolean isPowered() { -@@ -592,6 +746,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { +@@ -574,6 +737,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { @Override protected boolean canRide(Entity entity) { -+ if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur ++ if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles return false; } -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index 70b8023c3badc745f342d5b0ab54699e3923826a..037586c0fdb42a02660aba89dd741a647c67e52b 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -114,10 +114,12 @@ public class ArmorStand extends LivingEntity { +diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java +index a3cc0001a949597e345d7919c3f6109fa4a949ad..8c0ab32487c56e2caf42404184f86c9bcf5f8b41 100644 +--- a/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -93,10 +93,13 @@ public class ArmorStand extends LivingEntity { private boolean noTickPoseDirty = false; private boolean noTickEquipmentDirty = false; // Paper end - Allow ArmorStands not to tick -+ public boolean canMovementTick = true; // Purpur ++ public boolean canMovementTick = true; // Purpur - Movement options for armor stands - public ArmorStand(EntityType type, Level world) { - super(type, world); - if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick -+ if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur - this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); - this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); - this.headPose = ArmorStand.DEFAULT_HEAD_POSE; -@@ -126,6 +128,7 @@ public class ArmorStand extends LivingEntity { - this.rightArmPose = ArmorStand.DEFAULT_RIGHT_ARM_POSE; - this.leftLegPose = ArmorStand.DEFAULT_LEFT_LEG_POSE; - this.rightLegPose = ArmorStand.DEFAULT_RIGHT_LEG_POSE; -+ this.setShowArms(world != null && world.purpurConfig.armorstandPlaceWithArms); // Purpur + public ArmorStand(EntityType entityType, Level level) { + super(entityType, level); + if (level != null) this.canTick = level.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick ++ if (level != null) this.canMovementTick = level.purpurConfig.armorstandMovement; // Purpur - Movement options for armor stands ++ this.setShowArms(level != null && level.purpurConfig.armorstandPlaceWithArms); // Purpur - Config to show Armor Stand arms on spawn } - public ArmorStand(Level world, double x, double y, double z) { -@@ -618,6 +621,7 @@ public class ArmorStand extends LivingEntity { - private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper - ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); + public ArmorStand(Level level, double x, double y, double z) { +@@ -561,6 +564,7 @@ public class ArmorStand extends LivingEntity { -+ if (this.level().purpurConfig.persistentDroppableEntityDisplayNames) - itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); - this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior - return this.brokenByAnything(world, damageSource); // Paper -@@ -681,6 +685,7 @@ public class ArmorStand extends LivingEntity { + private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel level, DamageSource damageSource) { // Paper + ItemStack itemStack = new ItemStack(Items.ARMOR_STAND); ++ if (level.purpurConfig.persistentDroppableEntityDisplayNames) // Purpur - Apply display names from item forms of entities to entities and vice versa + itemStack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); + this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior + return this.brokenByAnything(level, damageSource); // Paper +@@ -620,6 +624,7 @@ public class ArmorStand extends LivingEntity { @Override public void tick() { -+ maxUpStep = level().purpurConfig.armorstandStepHeight; ++ maxUpStep = level().purpurConfig.armorstandStepHeight; // Purpur - Add option to set armorstand step height // Paper start - Allow ArmorStands not to tick if (!this.canTick) { if (this.noTickPoseDirty) { -@@ -1013,4 +1018,18 @@ public class ArmorStand extends LivingEntity { +@@ -949,4 +954,18 @@ public class ArmorStand extends LivingEntity { } } // Paper end + -+ // Purpur start ++ // Purpur start - Movement options for armor stands + @Override + public void updateInWaterStateAndDoWaterCurrentPushing() { + if (this.level().purpurConfig.armorstandWaterMovement && @@ -8120,102 +8111,99 @@ index 70b8023c3badc745f342d5b0ab54699e3923826a..037586c0fdb42a02660aba89dd741a64 + public void aiStep() { + if (this.canMovementTick && this.canMove) super.aiStep(); + } -+ // Purpur end ++ // Purpur end - Movement options for armor stands } -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 bbdaaa1cc0b4aed28bc39385508d221055b99d4d..bd5e034ce58ebe53d2121209d76ae60134ce72fe 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -240,7 +240,13 @@ public class ItemFrame extends HangingEntity { - } - - if (dropSelf) { -- this.spawnAtLocation(world, this.getFrameItemStack()); -+ // Purpur start +diff --git a/net/minecraft/world/entity/decoration/ItemFrame.java b/net/minecraft/world/entity/decoration/ItemFrame.java +index 65e1d7c5ac94b1cfb921fa009be59d3e5872f0b5..3ee1d8798db666ee8d83556047e40ff217cda732 100644 +--- a/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -223,7 +223,11 @@ public class ItemFrame extends HangingEntity { + this.removeFramedMap(item); + } else { + if (dropItem) { +- this.spawnAtLocation(level, this.getFrameItemStack()); ++ // Purpur start - Apply display names from item forms of entities to entities and vice versa + final ItemStack itemFrame = this.getFrameItemStack(); -+ if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) { -+ itemFrame.set(DataComponents.CUSTOM_NAME, null); -+ } -+ this.spawnAtLocation(world, itemFrame); -+ // Purpur end ++ if (!level.purpurConfig.persistentDroppableEntityDisplayNames) itemFrame.set(DataComponents.CUSTOM_NAME, null); ++ this.spawnAtLocation(level, itemFrame); ++ // Purpur end - Apply display names from item forms of entities to entities and vice versa } - if (!itemstack.isEmpty()) { -diff --git a/src/main/java/net/minecraft/world/entity/decoration/Painting.java b/src/main/java/net/minecraft/world/entity/decoration/Painting.java -index fd0e78a2318e3950d011c17358245e107b38154a..0fcab828e81176323cbdf16c0ec714d9a2846ae5 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java -@@ -179,7 +179,13 @@ public class Painting extends HangingEntity implements VariantHolder type, Level world) { - super(type, world); -@@ -410,7 +416,16 @@ public class ItemEntity extends Entity implements TraceableEntity { + public ItemEntity(EntityType entityType, Level level) { + super(entityType, level); +@@ -363,7 +369,16 @@ public class ItemEntity extends Entity implements TraceableEntity { @Override - public final boolean hurtServer(ServerLevel world, DamageSource source, float amount) { -- if (this.isInvulnerableToBase(source)) { -+ // Purpur start + public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) { +- if (this.isInvulnerableToBase(damageSource)) { ++ // Purpur start - Item entity immunities + if ( -+ (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) || -+ (immuneToFire && (source.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) || -+ (immuneToLightning && source.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) || -+ (immuneToExplosion && source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION)) ++ (immuneToCactus && damageSource.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) || ++ (immuneToFire && (damageSource.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || damageSource.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || damageSource.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) || ++ (immuneToLightning && damageSource.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) || ++ (immuneToExplosion && damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION)) + ) { + return false; -+ } else if (this.isInvulnerableToBase(source)) { -+ // Purpur end ++ } else if (this.isInvulnerableToBase(damageSource)) { ++ // Purpur end - Item entity immunities return false; - } else if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && source.getEntity() instanceof Mob) { + } else if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && damageSource.getEntity() instanceof Mob) { return false; -@@ -621,6 +636,12 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -565,6 +580,12 @@ public class ItemEntity extends Entity implements TraceableEntity { public void setItem(ItemStack stack) { - this.getEntityData().set(ItemEntity.DATA_ITEM, stack); + this.getEntityData().set(DATA_ITEM, stack); this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate -+ // Purpur start ++ // Purpur start - Item entity immunities + if (level().purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true; + if (level().purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true; + if (level().purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true; + if (level().purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true; -+ // level end ++ // level end - Item entity immunities } @Override -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 de87483600e55d88176fe25db621bbd3e464729f..287ba483614e79e78022e703ef891f59f41ac455 100644 ---- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -249,4 +249,31 @@ public class PrimedTnt extends Entity implements TraceableEntity { - return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index 40da052e7fea1306a007b3cb5c9daa33e0ef523e..40f5534b425ef57c435b365f156d3b988b74f911 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -251,4 +251,32 @@ public class PrimedTnt extends Entity implements TraceableEntity { + return !this.level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); } // Paper end - Option to prevent TNT from moving in water ++ + // Purpur start - Shears can defuse TNT + @Override + public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) { @@ -8244,62 +8232,57 @@ index de87483600e55d88176fe25db621bbd3e464729f..287ba483614e79e78022e703ef891f59 + } + // Purpur end - Shears can defuse TNT } -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 32670a3cb4b54b66d655197e3fde834d2b2b6d34..39d02cf0e31832e30c4f034b0b5385e3e0057e60 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -68,16 +68,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - protected AbstractSkeleton(EntityType type, Level world) { - super(type, world); +diff --git a/net/minecraft/world/entity/monster/AbstractSkeleton.java b/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 37abc7769573e3cdda380166dd086551d5e7bd88..223739818e9ac6c9fe396b82bce53a3ab029610a 100644 +--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -64,21 +64,24 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + AbstractSkeleton.this.setAggressive(true); + } + }; +- private boolean shouldBurnInDay = true; // Paper - shouldBurnInDay API ++ //private boolean shouldBurnInDay = true; // Paper - shouldBurnInDay API // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight + + protected AbstractSkeleton(EntityType entityType, Level level) { + super(entityType, level); this.reassessWeaponGoal(); + this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight } @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(2, new RestrictSunGoal(this)); - this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0)); + this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0, 1.2)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); -@@ -96,37 +99,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - abstract SoundEvent getStepSound(); - - // Paper start - shouldBurnInDay API -- private boolean shouldBurnInDay = true; -+ //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight - public boolean shouldBurnInDay() { return shouldBurnInDay; } - public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } - // Paper end - shouldBurnInDay API +@@ -108,27 +111,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo @Override public void aiStep() { -- boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API -- -- if (flag) { -- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -- -- if (!itemstack.isEmpty()) { -- if (itemstack.isDamageableItem()) { -- Item item = itemstack.getItem(); -- -- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); -- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { +- boolean isSunBurnTick = this.shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API +- if (isSunBurnTick) { +- ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD); +- if (!itemBySlot.isEmpty()) { +- if (itemBySlot.isDamageableItem()) { +- Item item = itemBySlot.getItem(); +- itemBySlot.setDamageValue(itemBySlot.getDamageValue() + this.random.nextInt(2)); +- if (itemBySlot.getDamageValue() >= itemBySlot.getMaxDamage()) { - this.onEquippedItemBroken(item, EquipmentSlot.HEAD); - this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); - } - } - -- flag = false; +- isSunBurnTick = false; - } - -- if (flag) { +- if (isSunBurnTick) { - this.igniteForSeconds(8.0F); - } - } @@ -8308,64 +8291,63 @@ index 32670a3cb4b54b66d655197e3fde834d2b2b6d34..39d02cf0e31832e30c4f034b0b5385e3 super.aiStep(); } -@@ -158,11 +138,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo +@@ -158,10 +141,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo this.reassessWeaponGoal(); - this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot + this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { -- LocalDate localdate = LocalDate.now(); -- int i = localdate.get(ChronoField.DAY_OF_MONTH); -- int j = localdate.get(ChronoField.MONTH_OF_YEAR); -- -- if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { -+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); +- LocalDate localDate = LocalDate.now(); +- int i = localDate.get(ChronoField.DAY_OF_MONTH); +- int i1 = localDate.get(ChronoField.MONTH_OF_YEAR); +- if (i1 == 10 && i == 31 && random.nextFloat() < 0.25F) { ++ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(level.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - Halloween options and optimizations + this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(random.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F; } -@@ -221,7 +197,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - } - - if (event.getProjectile() == entityarrow.getBukkitEntity()) { -- Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); -+ Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, this.level().purpurConfig.skeletonBowAccuracyMap.getOrDefault(this.level().getDifficulty().getId(), (float) (14 - this.level().getDifficulty().getId() * 4))); // Purpur - } +@@ -217,7 +197,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + if (event.getProjectile() == arrow.getBukkitEntity()) { // CraftBukkit end + Projectile.spawnProjectileUsingShoot( +- arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4 ++ arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, serverLevel.purpurConfig.skeletonBowAccuracyMap.getOrDefault(serverLevel.getDifficulty().getId(), (float) (14 - serverLevel.getDifficulty().getId() * 4)) // Purpur - skeleton bow accuracy option + ); + } // CraftBukkit } -@@ -243,7 +219,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - super.readAdditionalSaveData(nbt); +@@ -244,7 +224,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + super.readAdditionalSaveData(compound); this.reassessWeaponGoal(); // Paper start - shouldBurnInDay API -- if (nbt.contains("Paper.ShouldBurnInDay")) { -+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); +- if (compound.contains("Paper.ShouldBurnInDay")) { ++ if (false && compound.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight + this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay"); } // Paper end - shouldBurnInDay API -@@ -253,7 +229,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo +@@ -253,7 +233,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo @Override - public void addAdditionalSaveData(CompoundTag nbt) { + public void addAdditionalSaveData(final net.minecraft.nbt.CompoundTag nbt) { super.addAdditionalSaveData(nbt); - nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); + //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight } // Paper end - shouldBurnInDay API -diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -index e33fa82ca1332b95bb067fd621212d3026eee1b7..07db4557ab0d7a4a0f5432257bd18195d2de7255 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -@@ -33,26 +33,74 @@ public class Blaze extends Monster { +diff --git a/net/minecraft/world/entity/monster/Blaze.java b/net/minecraft/world/entity/monster/Blaze.java +index 419c729502ee708bba9e750f1b951450eca82695..79f5f0dbf95ed72dd031495c75675d7b6c7dfc34 100644 +--- a/net/minecraft/world/entity/monster/Blaze.java ++++ b/net/minecraft/world/entity/monster/Blaze.java +@@ -33,26 +33,78 @@ public class Blaze extends Monster { - public Blaze(EntityType type, Level world) { - super(type, world); + public Blaze(EntityType entityType, Level level) { + super(entityType, level); - this.setPathfindingMalus(PathType.WATER, -1.0F); -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur ++ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur - Ridables ++ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage this.setPathfindingMalus(PathType.LAVA, 8.0F); this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); this.xpReward = 10; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.blazeRidable; @@ -8397,67 +8379,71 @@ index e33fa82ca1332b95bb067fd621212d3026eee1b7..07db4557ab0d7a4a0f5432257bd18195 + setDeltaMovement(mot.scale(0.9D)); + } + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.blazeMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.blazeScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.blazeAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this)); this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0); -+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur ++ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables } @Override -@@ -112,11 +160,18 @@ public class Blaze extends Monster { +@@ -112,11 +164,18 @@ public class Blaze extends Monster { @Override public boolean isSensitiveToWater() { - return true; -+ return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur ++ return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage } @Override - protected void customServerAiStep(ServerLevel world) { -+ // Purpur start + protected void customServerAiStep(ServerLevel level) { ++ // Purpur start - Ridables + if (getRider() != null && this.isControllable()) { + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z()); + return; + } -+ // Purpur end ++ // Purpur end - Ridables this.nextHeightOffsetChangeTick--; if (this.nextHeightOffsetChangeTick <= 0) { this.nextHeightOffsetChangeTick = 100; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java -index 975477663b6d76a69c006a89e440e21471b39b89..ca63ab37bc6b5b4cb5abf2848dae476b5d937f2a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java -@@ -44,6 +44,29 @@ public class Bogged extends AbstractSkeleton implements Shearable { - super(type, world); +diff --git a/net/minecraft/world/entity/monster/Bogged.java b/net/minecraft/world/entity/monster/Bogged.java +index f01670f7106a39957c9b37839fcca0d9f29208f0..2774602455b92745e789d83f17480c141eb89abf 100644 +--- a/net/minecraft/world/entity/monster/Bogged.java ++++ b/net/minecraft/world/entity/monster/Bogged.java +@@ -41,6 +41,31 @@ public class Bogged extends AbstractSkeleton implements Shearable { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.boggedRidable; @@ -8472,26 +8458,28 @@ index 975477663b6d76a69c006a89e440e21471b39b89..ca63ab37bc6b5b4cb5abf2848dae476b + public boolean isControllable() { + return level().purpurConfig.boggedControllable; + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.boggedMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.boggedScale); + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -index 4e621a7f36b3d718695434a2a4e3060283667bb2..9a274b83a3a7726cac421856dbc7be01de45d29b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -@@ -27,6 +27,39 @@ public class CaveSpider extends Spider { - return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D); +diff --git a/net/minecraft/world/entity/monster/CaveSpider.java b/net/minecraft/world/entity/monster/CaveSpider.java +index 2e32567fca7a2a4cd87bc078a6eeb30e3ffabfce..7eca4b751d900c6d6ee34993c3e2368127d19e03 100644 +--- a/net/minecraft/world/entity/monster/CaveSpider.java ++++ b/net/minecraft/world/entity/monster/CaveSpider.java +@@ -26,6 +26,45 @@ public class CaveSpider extends Spider { + return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.caveSpiderRidable; @@ -8506,47 +8494,53 @@ index 4e621a7f36b3d718695434a2a4e3060283667bb2..9a274b83a3a7726cac421856dbc7be01 + public boolean isControllable() { + return level().purpurConfig.caveSpiderControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.caveSpiderMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.caveSpiderScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.caveSpiderTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.caveSpiderAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override - public boolean doHurtTarget(ServerLevel world, Entity target) { - if (super.doHurtTarget(world, target)) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index 1906dfc22af208d6e801ad4a8f2f9e9702432691..38cbe2fce9c36195aa9bea2af26d14364b216825 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -60,21 +60,99 @@ public class Creeper extends Monster { + public boolean doHurtTarget(ServerLevel level, Entity source) { + if (super.doHurtTarget(level, source)) { +diff --git a/net/minecraft/world/entity/monster/Creeper.java b/net/minecraft/world/entity/monster/Creeper.java +index e832612a7ed3f25d087c4f8f85f5d737ade5d645..a85b64799faa860367cded67a264b436cd46f049 100644 +--- a/net/minecraft/world/entity/monster/Creeper.java ++++ b/net/minecraft/world/entity/monster/Creeper.java +@@ -50,21 +50,107 @@ public class Creeper extends Monster { public int explosionRadius = 3; private int droppedSkulls; public Entity entityIgniter; // CraftBukkit -+ // Purpur start ++ private boolean exploding = false; // Purpur - Config to make Creepers explode on death ++ // Purpur start - Ridables + private int spacebarCharge = 0; + private int prevSpacebarCharge = 0; + private int powerToggleDelay = 0; -+ // Purpur end -+ private boolean exploding = false; // Purpur - Config to make Creepers explode on death ++ // Purpur end - Ridables - public Creeper(EntityType type, Level world) { - super(type, world); + public Creeper(EntityType entityType, Level level) { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.creeperRidable; @@ -8614,33 +8608,36 @@ index 1906dfc22af208d6e801ad4a8f2f9e9702432691..38cbe2fce9c36195aa9bea2af26d1436 + } + return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still + } -+ // Purpur end ++ // Purpur end - Ridables + - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); - this.goalSelector.addGoal(2, new SwellGoal(this)); -+ this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - } -@@ -174,6 +252,40 @@ public class Creeper extends Monster { - } - } - ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creeperMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creeperScale); + } ++ // Purpur end - Configurable entity base attributes + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(2, new SwellGoal(this)); ++ this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0, 1.2)); + this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0, 1.2)); + this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, false)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8)); + this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); + this.targetSelector.addGoal(2, new HurtByTargetGoal(this)); + } +@@ -161,6 +247,40 @@ public class Creeper extends Monster { + } + } + ++ // Purpur start - Special mobs naturally spawn + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData) { + double chance = world.getLevel().purpurConfig.creeperChargedChance; + if (chance > 0D && random.nextDouble() <= chance) { @@ -8648,11 +8645,7 @@ index 1906dfc22af208d6e801ad4a8f2f9e9702432691..38cbe2fce9c36195aa9bea2af26d1436 + } + return super.finalizeSpawn(world, difficulty, spawnReason, entityData); + } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.creeperTakeDamageFromWater; -+ } ++ // Purpur end - Special mobs naturally spawn + + // Purpur start - Config to make Creepers explode on death + @Override @@ -8664,60 +8657,67 @@ index 1906dfc22af208d6e801ad4a8f2f9e9702432691..38cbe2fce9c36195aa9bea2af26d1436 + } + // Purpur end - Config to make Creepers explode on death + ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.creeperTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.creeperAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override - protected SoundEvent getHurtSound(DamageSource source) { + protected SoundEvent getHurtSound(DamageSource damageSource) { return SoundEvents.CREEPER_HURT; -@@ -262,16 +374,18 @@ public class Creeper extends Monster { +@@ -243,14 +363,16 @@ public class Creeper extends Monster { + } public void explodeCreeper() { - Level world = this.level(); + this.exploding = true; // Purpur - Config to make Creepers explode on death - - if (world instanceof ServerLevel worldserver) { + if (this.level() instanceof ServerLevel serverLevel) { float f = this.isPowered() ? 2.0F : 1.0F; -+ float multiplier = worldserver.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur - ++ float multiplier = serverLevel.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur - Config for health to impact Creeper explosion radius // CraftBukkit start -- ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false); -+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur +- org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false); ++ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur - Config for health to impact Creeper explosion radius if (!event.isCancelled()) { // CraftBukkit end this.dead = true; -- worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) -+ worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), worldserver.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) // Purpur +- serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) ++ serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) // Purpur - Add enderman and creeper griefing controls this.spawnLingeringCloud(); - this.triggerOnDeathMobEffects(worldserver, Entity.RemovalReason.KILLED); - this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause -@@ -283,6 +397,7 @@ public class Creeper extends Monster { + this.triggerOnDeathMobEffects(serverLevel, Entity.RemovalReason.KILLED); + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause +@@ -261,6 +383,7 @@ public class Creeper extends Monster { + } // CraftBukkit end } - + this.exploding = false; // Purpur - Config to make Creepers explode on death } private void spawnLingeringCloud() { -@@ -324,6 +439,7 @@ public class Creeper extends Monster { +@@ -288,6 +411,7 @@ public class Creeper extends Monster { com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); if (event.callEvent()) { - this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); -+ if (!event.isIgnited()) setSwellDir(-1); // Purpur + this.entityData.set(DATA_IS_IGNITED, event.isIgnited()); ++ if (!event.isIgnited()) setSwellDir(-1); // Purpur - Ridables } } - // Paper end - CreeperIgniteEvent -diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -index 2e73917ce9270de7483bb1d4e9bf312a31ec9b1e..949207eda199c874f2f14074b5a4fff5462b86b9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -@@ -72,6 +72,59 @@ public class Drowned extends Zombie implements RangedAttackMob { + } +diff --git a/net/minecraft/world/entity/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java +index c23c4e44ece85fb746a497cbb8a7cd14b2f9768a..6c73245b8d04f194e72165aa0000ca79a95db59d 100644 +--- a/net/minecraft/world/entity/monster/Drowned.java ++++ b/net/minecraft/world/entity/monster/Drowned.java +@@ -75,6 +75,67 @@ public class Drowned extends Zombie implements RangedAttackMob { return Zombie.createAttributes().add(Attributes.STEP_HEIGHT, 1.0); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.drownedRidable; @@ -8732,8 +8732,9 @@ index 2e73917ce9270de7483bb1d4e9bf312a31ec9b1e..949207eda199c874f2f14074b5a4fff5 + public boolean isControllable() { + return level().purpurConfig.drownedControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.drownedMaxHealth); @@ -8744,12 +8745,9 @@ index 2e73917ce9270de7483bb1d4e9bf312a31ec9b1e..949207eda199c874f2f14074b5a4fff5 + protected void randomizeReinforcementsChance() { + this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.drownedSpawnReinforcements); + } ++ // Purpur end - Configurable entity base attributes + -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.drownedTakeDamageFromWater; -+ } -+ ++ // Purpur start - Configurable jockey options + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.drownedJockeyOnlyBaby; @@ -8764,25 +8762,35 @@ index 2e73917ce9270de7483bb1d4e9bf312a31ec9b1e..949207eda199c874f2f14074b5a4fff5 + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.drownedJockeyTryExistingChickens; + } ++ // Purpur end - Configurable jockey options + ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.drownedTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.drownedAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void addBehaviourGoals() { this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0)); -@@ -79,10 +132,23 @@ public class Drowned extends Zombie implements RangedAttackMob { +@@ -82,10 +143,23 @@ public class Drowned extends Zombie implements RangedAttackMob { this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0, false)); this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0)); this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0, this.level().getSeaLevel())); -+ if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); ++ if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); // Purpur - Option to make drowned break doors this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class)); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, world) -> this.okTarget(target))); -- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config -+ // Purpur start + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> this.okTarget(entity))); +- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config ++ // Purpur start - Add option to disable zombie aggressiveness towards villagers + if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Paper - Check drowned for villager aggression config + @Override + public boolean canUse() { @@ -8794,55 +8802,55 @@ index 2e73917ce9270de7483bb1d4e9bf312a31ec9b1e..949207eda199c874f2f14074b5a4fff5 + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse(); + } + }); -+ // Purpur end ++ // Purpur end - Add option to disable zombie aggressiveness towards villagers this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); -@@ -396,7 +462,7 @@ public class Drowned extends Zombie implements RangedAttackMob { +@@ -395,7 +469,7 @@ public class Drowned extends Zombie implements RangedAttackMob { } } - static class DrownedMoveControl extends MoveControl { -+ static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur ++ static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables private final Drowned drowned; - public DrownedMoveControl(Drowned drowned) { -@@ -405,7 +471,7 @@ public class Drowned extends Zombie implements RangedAttackMob { + public DrownedMoveControl(Drowned mob) { +@@ -404,7 +478,7 @@ public class Drowned extends Zombie implements RangedAttackMob { } @Override - public void tick() { -+ public void vanillaTick() { // Purpur - LivingEntity livingEntity = this.drowned.getTarget(); ++ public void vanillaTick() { // Purpur - Ridables + LivingEntity target = this.drowned.getTarget(); if (this.drowned.wantsToSwim() && this.drowned.isInWater()) { - if (livingEntity != null && livingEntity.getY() > this.drowned.getY() || this.drowned.searchingForLand) { -@@ -425,7 +491,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - float h = (float)(Mth.atan2(f, d) * 180.0F / (float)Math.PI) - 90.0F; - this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), h, 90.0F)); + if (target != null && target.getY() > this.drowned.getY() || this.drowned.searchingForLand) { +@@ -424,7 +498,7 @@ public class Drowned extends Zombie implements RangedAttackMob { + float f = (float)(Mth.atan2(d2, d) * 180.0F / (float)Math.PI) - 90.0F; + this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), f, 90.0F)); this.drowned.yBodyRot = this.drowned.getYRot(); -- float i = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float i = (float)(this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - float j = Mth.lerp(0.125F, this.drowned.getSpeed(), i); - this.drowned.setSpeed(j); - this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add((double)j * d * 0.005, (double)j * e * 0.1, (double)j * f * 0.005)); -@@ -434,7 +500,7 @@ public class Drowned extends Zombie implements RangedAttackMob { +- float f1 = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); ++ float f1 = (float)(this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables + float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1); + this.drowned.setSpeed(f2); + this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(f2 * d * 0.005, f2 * d1 * 0.1, f2 * d2 * 0.005)); +@@ -433,7 +507,7 @@ public class Drowned extends Zombie implements RangedAttackMob { this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, -0.008, 0.0)); } - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } } } -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 378694a38115c012978e1fea59d049d1ebd04110..a000304e3ac4c34b020f7457aa2589c87f140d8c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -@@ -33,6 +33,34 @@ public class ElderGuardian extends Guardian { - +diff --git a/net/minecraft/world/entity/monster/ElderGuardian.java b/net/minecraft/world/entity/monster/ElderGuardian.java +index 4585b7c867685f8419c4d2b5b90af5946d337f90..358021a6eb936ac43e29c7c2c92c71e9cae6ab93 100644 +--- a/net/minecraft/world/entity/monster/ElderGuardian.java ++++ b/net/minecraft/world/entity/monster/ElderGuardian.java +@@ -31,6 +31,40 @@ public class ElderGuardian extends Guardian { + } } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.elderGuardianRidable; @@ -8852,40 +8860,46 @@ index 378694a38115c012978e1fea59d049d1ebd04110..a000304e3ac4c34b020f7457aa2589c8 + public boolean isControllable() { + return level().purpurConfig.elderGuardianControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.elderGuardianMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.elderGuardianScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.elderGuardianTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.elderGuardianAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + public static AttributeSupplier.Builder createAttributes() { - return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.30000001192092896D).add(Attributes.ATTACK_DAMAGE, 8.0D).add(Attributes.MAX_HEALTH, 80.0D); + return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.3F).add(Attributes.ATTACK_DAMAGE, 8.0).add(Attributes.MAX_HEALTH, 80.0); } -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 35ffcd82df6af65a9c1b9a1a6acdd8b7567b8645..ebc2a461a9b41a28388362c777f53a0ee7f839f3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -92,12 +92,41 @@ public class EnderMan extends Monster implements NeutralMob { +diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java +index c7a77b16ba032eb24b070fd6a5f0f824b3cbcc1a..56ec8cbde61c337672972d6ade3dafbb9aed8473 100644 +--- a/net/minecraft/world/entity/monster/EnderMan.java ++++ b/net/minecraft/world/entity/monster/EnderMan.java +@@ -87,12 +87,45 @@ public class EnderMan extends Monster implements NeutralMob { - public EnderMan(EntityType type, Level world) { - super(type, world); + public EnderMan(EntityType entityType, Level level) { + super(entityType, level); - this.setPathfindingMalus(PathType.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur -+ } -+ -+ // Purpur start ++ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage + } + ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.endermanRidable; @@ -8900,140 +8914,140 @@ index 35ffcd82df6af65a9c1b9a1a6acdd8b7567b8645..ebc2a461a9b41a28388362c777f53a0e + public boolean isControllable() { + return level().purpurConfig.endermanControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermanMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermanScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.endermanAlwaysDropExp; - } - ++ } ++ // Purpur end - Mobs always drop experience ++ @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this)); - this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); -@@ -105,9 +134,10 @@ public class EnderMan extends Monster implements NeutralMob { + this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F)); +@@ -100,9 +133,10 @@ public class EnderMan extends Monster implements NeutralMob { this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(10, new EnderMan.EndermanLeaveBlockGoal(this)); this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); + this.targetSelector.addGoal(2, new HurtByTargetGoal(this)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving, ignored) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false)); } -@@ -235,7 +265,7 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -230,7 +264,7 @@ public class EnderMan extends Monster implements NeutralMob { boolean isBeingStaredBy(Player player) { - // Paper start - EndermanAttackPlayerEvent -- boolean shouldAttack = isBeingStaredBy0(player); -+ boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && isBeingStaredBy0(player); // Purpur - com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); + // Paper start - EndermanAttackPlayerEvent +- final boolean shouldAttack = isBeingStaredBy0(player); ++ final boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && isBeingStaredBy0(player); // Purpur - Config to ignore Dragon Head wearers and stare aggro + final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); event.setCancelled(!shouldAttack); return event.callEvent(); -@@ -263,12 +293,12 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -267,12 +301,12 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean isSensitiveToWater() { - return true; -+ return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur ++ return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage } @Override - protected void customServerAiStep(ServerLevel world) { -- if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) { -+ if ((getRider() == null || !this.isControllable()) && world.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting - float f = this.getLightLevelDependentMagicValue(); - - if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent -@@ -390,6 +420,8 @@ public class EnderMan extends Monster implements NeutralMob { - public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { - if (this.isInvulnerableTo(world, source)) { + protected void customServerAiStep(ServerLevel level) { +- if (level.isDay() && this.tickCount >= this.targetChangeTime + 600) { ++ if ((getRider() == null || !this.isControllable()) && level.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - Ridables - no random teleporting + float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue(); + if (lightLevelDependentMagicValue > 0.5F + && level.canSeeSky(this.blockPosition()) +@@ -392,6 +426,8 @@ public class EnderMan extends Monster implements NeutralMob { + public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) { + if (this.isInvulnerableTo(level, damageSource)) { return false; -+ } else if (getRider() != null && this.isControllable()) { return super.hurtServer(world, source, amount); // Purpur - no teleporting on damage -+ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height ++ } else if (getRider() != null && this.isControllable()) { return super.hurtServer(level, damageSource, amount); // Purpur - no teleporting on damage ++ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && damageSource.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height - Short enderman height } else { - boolean flag = source.getDirectEntity() instanceof ThrownPotion; - boolean flag1; -@@ -404,6 +436,7 @@ public class EnderMan extends Monster implements NeutralMob { + boolean flag = damageSource.getDirectEntity() instanceof ThrownPotion; + if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && !flag) { +@@ -404,6 +440,7 @@ public class EnderMan extends Monster implements NeutralMob { } else { - flag1 = flag && this.hurtWithCleanWater(world, source, (ThrownPotion) source.getDirectEntity(), amount); + boolean flag1 = flag && this.hurtWithCleanWater(level, damageSource, (ThrownPotion)damageSource.getDirectEntity(), amount); -+ if (!flag1 && world.purpurConfig.endermanIgnoreProjectiles) return super.hurtServer(world, source, amount); // Purpur ++ if (!flag1 && level.purpurConfig.endermanIgnoreProjectiles) return super.hurtServer(level, damageSource, amount); // Purpur - Config to disable Enderman teleport on projectile hit if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent - for (int i = 0; i < 64; ++i) { + for (int i = 0; i < 64; i++) { if (this.teleport()) { -@@ -448,7 +481,7 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -447,7 +484,7 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean requiresCustomPersistence() { - return super.requiresCustomPersistence() || this.getCarriedBlock() != null; -+ return super.requiresCustomPersistence() || (!this.level().purpurConfig.endermanDespawnEvenWithBlock && this.getCarriedBlock() != null); // Purpur ++ return super.requiresCustomPersistence() || (!this.level().purpurConfig.endermanDespawnEvenWithBlock && this.getCarriedBlock() != null); // Purpur - Add config for allowing Endermen to despawn even while holding a block } - private static class EndermanFreezeWhenLookedAt extends Goal { -@@ -495,7 +528,16 @@ public class EnderMan extends Monster implements NeutralMob { + static class EndermanFreezeWhenLookedAt extends Goal { +@@ -491,8 +528,9 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { -- return this.enderman.getCarriedBlock() == null ? false : (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0); -+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur -+ // Purpur start -+ if (this.enderman.getCarriedBlock() == null) { -+ return false; -+ } -+ if (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { -+ return false; -+ } -+ return this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0; -+ // Purpur end ++ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls + return this.enderman.getCarriedBlock() != null +- && getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ++ && getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing // Purpur - Add mobGriefing bypass to everything affected + && this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0; } - @Override -@@ -540,7 +582,16 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -640,8 +678,9 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { -- return this.enderman.getCarriedBlock() != null ? false : (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0); -+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur -+ // Purpur start -+ if (this.enderman.getCarriedBlock() != null) { -+ return false; -+ } -+ if (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { -+ return false; -+ } -+ return this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0; -+ // Purpur end ++ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls + return this.enderman.getCarriedBlock() == null +- && getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ++ && getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing // Purpur - Add mobGriefing bypass to everything affected + && this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0; } - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Endermite.java b/src/main/java/net/minecraft/world/entity/monster/Endermite.java -index 534e98dd7291e09dee1d0f77cbf221b15708590f..f8373fc9839fccb31e3dd090de70e2cd7c9e6cfc 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java -@@ -32,20 +32,64 @@ public class Endermite extends Monster { - +diff --git a/net/minecraft/world/entity/monster/Endermite.java b/net/minecraft/world/entity/monster/Endermite.java +index 2a219c9ae39d7cbee8484b2a93bd278d913afe95..441f7d3cfa1e9896a065dae51d871901ac4330e3 100644 +--- a/net/minecraft/world/entity/monster/Endermite.java ++++ b/net/minecraft/world/entity/monster/Endermite.java +@@ -28,20 +28,72 @@ import net.minecraft.world.level.block.state.BlockState; + public class Endermite extends Monster { private static final int MAX_LIFE = 2400; public int life; -+ private boolean isPlayerSpawned; // Purpur ++ private boolean isPlayerSpawned; // Purpur - Add back player spawned endermite API - public Endermite(EntityType type, Level world) { - super(type, world); + public Endermite(EntityType entityType, Level level) { + super(entityType, level); this.xpReward = 3; } -+ // Purpur start ++ // Purpur start - Add back player spawned endermite API ++ public boolean isPlayerSpawned() { ++ return this.isPlayerSpawned; ++ } ++ ++ public void setPlayerSpawned(boolean playerSpawned) { ++ this.isPlayerSpawned = playerSpawned; ++ } ++ // Purpur end - Add back player spawned endermite API ++ ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.endermiteRidable; @@ -9048,69 +9062,67 @@ index 534e98dd7291e09dee1d0f77cbf221b15708590f..f8373fc9839fccb31e3dd090de70e2cd + public boolean isControllable() { + return level().purpurConfig.endermiteControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermiteMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermiteScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.endermiteTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + -+ public boolean isPlayerSpawned() { -+ return this.isPlayerSpawned; -+ } -+ -+ public void setPlayerSpawned(boolean playerSpawned) { -+ this.isPlayerSpawned = playerSpawned; -+ } -+ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.endermiteAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); - this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false)); + this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } -@@ -83,12 +127,14 @@ public class Endermite extends Monster { - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - this.life = nbt.getInt("Lifetime"); -+ this.isPlayerSpawned = nbt.getBoolean("PlayerSpawned"); // Purpur +@@ -79,12 +131,14 @@ public class Endermite extends Monster { + public void readAdditionalSaveData(CompoundTag compound) { + super.readAdditionalSaveData(compound); + this.life = compound.getInt("Lifetime"); ++ this.isPlayerSpawned = compound.getBoolean("PlayerSpawned"); // Purpur - Add back player spawned endermite API } @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putInt("Lifetime", this.life); -+ nbt.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); + compound.putInt("Lifetime", this.life); ++ compound.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur - Add back player spawned endermite API } @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Evoker.java b/src/main/java/net/minecraft/world/entity/monster/Evoker.java -index 6592baa53ecb4e364d1c1b6f64178fc86c59a982..e231bb33b7e6a00d7c1a6c3540b4b48cfd4c15e3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java -@@ -53,10 +53,44 @@ public class Evoker extends SpellcasterIllager { +diff --git a/net/minecraft/world/entity/monster/Evoker.java b/net/minecraft/world/entity/monster/Evoker.java +index b70ea1af39cada6bb17001c6b65502510e34c4b2..5c32406d00ec20516649d7a219a5b3c27c8945aa 100644 +--- a/net/minecraft/world/entity/monster/Evoker.java ++++ b/net/minecraft/world/entity/monster/Evoker.java +@@ -50,10 +50,50 @@ public class Evoker extends SpellcasterIllager { this.xpReward = 10; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.evokerRidable; @@ -9125,58 +9137,64 @@ index 6592baa53ecb4e364d1c1b6f64178fc86c59a982..e231bb33b7e6a00d7c1a6c3540b4b48c + public boolean isControllable() { + return level().purpurConfig.evokerControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.evokerMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.evokerScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.evokerTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.evokerAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal()); - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6D, 1.0D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); -@@ -66,6 +100,7 @@ public class Evoker extends SpellcasterIllager { - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); + this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6, 1.0)); + this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 0.6, 1.0)); +@@ -63,6 +103,7 @@ public class Evoker extends SpellcasterIllager { + this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); - this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); -@@ -342,7 +377,7 @@ public class Evoker extends SpellcasterIllager { ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true).setUnseenMemoryTicks(300)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false).setUnseenMemoryTicks(300)); +@@ -296,7 +337,7 @@ public class Evoker extends SpellcasterIllager { + return false; } else { - ServerLevel worldserver = getServerLevel(Evoker.this.level()); - -- if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!worldserver.purpurConfig.evokerBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur + ServerLevel serverLevel = getServerLevel(Evoker.this.level()); +- if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ if (!serverLevel.purpurConfig.evokerBypassMobGriefing == !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return false; } else { - List list = worldserver.getNearbyEntities(Sheep.class, this.wololoTargeting, Evoker.this, Evoker.this.getBoundingBox().inflate(16.0D, 4.0D, 16.0D)); -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 a8c8c03e972aa6352843cf4c3e4aebfb8f493125..173b10fa553db30c321bfd9eabe13915b63cf920 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -@@ -44,11 +44,47 @@ public class Ghast extends FlyingMob implements Enemy { + List nearbyEntities = serverLevel.getNearbyEntities( +diff --git a/net/minecraft/world/entity/monster/Ghast.java b/net/minecraft/world/entity/monster/Ghast.java +index b97bbfbbc8c1a4f38b4b858ef4915b637cc8a627..a9d92e857c5d05da6f2a5b6264590efe235061a7 100644 +--- a/net/minecraft/world/entity/monster/Ghast.java ++++ b/net/minecraft/world/entity/monster/Ghast.java +@@ -42,11 +42,69 @@ public class Ghast extends FlyingMob implements Enemy { this.moveControl = new Ghast.GhastMoveControl(this); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.ghastRidable; @@ -9208,77 +9226,76 @@ index a8c8c03e972aa6352843cf4c3e4aebfb8f493125..173b10fa553db30c321bfd9eabe13915 + setDeltaMovement(mot.scale(0.9D)); + } + } -+ // Purpur end ++ // Purpur end - Ridables + - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this)); - this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this)); - this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving, worldserver) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -96,6 +132,22 @@ public class Ghast extends FlyingMob implements Enemy { - } - } - ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ghastMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ghastScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ghastTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ghastAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -@@ -103,7 +155,7 @@ public class Ghast extends FlyingMob implements Enemy { + protected void registerGoals() { ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this)); + this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this)); + this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this)); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector + .addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> Math.abs(entity.getY() - this.getY()) <= 4.0)); + } +@@ -101,7 +159,7 @@ public class Ghast extends FlyingMob implements Enemy { } public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D); -+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur +- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.FOLLOW_RANGE, 100.0); ++ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.FOLLOW_RANGE, 100.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables } @Override -@@ -155,7 +207,7 @@ public class Ghast extends FlyingMob implements Enemy { - +@@ -191,7 +249,7 @@ public class Ghast extends FlyingMob implements Enemy { + } } -- private static class GhastMoveControl extends MoveControl { -+ private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - +- static class GhastMoveControl extends MoveControl { ++ static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables private final Ghast ghast; private int floatDuration; -@@ -166,7 +218,7 @@ public class Ghast extends FlyingMob implements Enemy { + +@@ -201,7 +259,7 @@ public class Ghast extends FlyingMob implements Enemy { } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (this.operation == MoveControl.Operation.MOVE_TO) { if (this.floatDuration-- <= 0) { - this.floatDuration += this.ghast.getRandom().nextInt(5) + 2; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Giant.java b/src/main/java/net/minecraft/world/entity/monster/Giant.java -index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..859d316825658c11f58dd92912edbee75eaeabb9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Giant.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java -@@ -12,12 +12,95 @@ public class Giant extends Monster { - super(type, world); + this.floatDuration = this.floatDuration + this.ghast.getRandom().nextInt(5) + 2; +diff --git a/net/minecraft/world/entity/monster/Giant.java b/net/minecraft/world/entity/monster/Giant.java +index 969eb604851d1cce50f0f99ed479189061d5de0c..3f575abee4c8933d1642400d134b0fc915215a1a 100644 +--- a/net/minecraft/world/entity/monster/Giant.java ++++ b/net/minecraft/world/entity/monster/Giant.java +@@ -12,12 +12,104 @@ public class Giant extends Monster { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.giantRidable; @@ -9296,6 +9313,7 @@ index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..859d316825658c11f58dd92912edbee7 + + @Override + protected void registerGoals() { ++ // Purpur start - Giants AI settings + if (level().purpurConfig.giantHaveAI) { + this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); @@ -9313,18 +9331,11 @@ index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..859d316825658c11f58dd92912edbee7 + this.targetSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.Turtle.class, true)); + } + } ++ // Purpur end - Giants AI settings + } ++ // Purpur end - Ridables + -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.giantTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.giantAlwaysDropExp; -+ } -+ ++ // Purpur start - Configurable entity base attributes + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth); @@ -9332,13 +9343,27 @@ index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..859d316825658c11f58dd92912edbee7 + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage); + } ++ // Purpur end - Configurable entity base attributes + -+ // Purpur end ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.giantTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience ++ @Override ++ protected boolean isAlwaysExperienceDropper() { ++ return this.level().purpurConfig.giantAlwaysDropExp; ++ } ++ // Purpur end - Mobs always drop experience + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 100.0).add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.ATTACK_DAMAGE, 50.0); } ++ // Purpur - Giants AI settings + @Override + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @org.jetbrains.annotations.Nullable net.minecraft.world.entity.SpawnGroupData entityData) { + net.minecraft.world.entity.SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData); @@ -9364,34 +9389,35 @@ index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..859d316825658c11f58dd92912edbee7 + // 1.0 makes bottom of feet about as high as their waist when they jump + return level().purpurConfig.giantJumpHeight; + } ++ // Purpur end - Giants AI settings + @Override - public float getWalkTargetValue(BlockPos pos, LevelReader world) { -- return world.getPathfindingCostFromLightLevels(pos); -+ return super.getWalkTargetValue(pos, world); // Purpur - fix light requirements for natural spawns + public float getWalkTargetValue(BlockPos pos, LevelReader level) { +- return level.getPathfindingCostFromLightLevels(pos); ++ return super.getWalkTargetValue(pos, level); // Purpur - Giants AI settings - fix light requirements for natural spawns } } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java -index 951f46684623582980901c1ebc1870aa5bcf25a1..da833bf35342f771ecccd5dcac4fe87f72d047b0 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java -@@ -67,15 +67,52 @@ public class Guardian extends Monster { +diff --git a/net/minecraft/world/entity/monster/Guardian.java b/net/minecraft/world/entity/monster/Guardian.java +index c8e249b8f7ee8e9c075169ec988f5a0d459a3767..60278b2491f644b70ef96b2e14e1f07bb341a0f1 100644 +--- a/net/minecraft/world/entity/monster/Guardian.java ++++ b/net/minecraft/world/entity/monster/Guardian.java +@@ -66,14 +66,57 @@ public class Guardian extends Monster { this.xpReward = 10; this.setPathfindingMalus(PathType.WATER, 0.0F); this.moveControl = new Guardian.GuardianMoveControl(this); -+ // Purpur start ++ // Purpur start - Ridables + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { + @Override + public void setYawPitch(float yaw, float pitch) { + super.setYawPitch(yaw, pitch * 0.35F); + } + }; -+ // Purpur end ++ // Purpur end - Ridables this.clientSideTailAnimation = this.random.nextFloat(); this.clientSideTailAnimationO = this.clientSideTailAnimation; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.guardianRidable; @@ -9401,64 +9427,69 @@ index 951f46684623582980901c1ebc1870aa5bcf25a1..da833bf35342f771ecccd5dcac4fe87f + public boolean isControllable() { + return level().purpurConfig.guardianControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.guardianMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.guardianScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.guardianTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.guardianAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { - MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D); - - this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + MoveTowardsRestrictionGoal moveTowardsRestrictionGoal = new MoveTowardsRestrictionGoal(this, 1.0); + this.randomStrollGoal = new RandomStrollGoal(this, 1.0, 80); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field - this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction); + this.goalSelector.addGoal(5, moveTowardsRestrictionGoal); this.goalSelector.addGoal(7, this.randomStrollGoal); -@@ -84,6 +121,7 @@ public class Guardian extends Monster { +@@ -82,6 +125,7 @@ public class Guardian extends Monster { this.goalSelector.addGoal(9, new RandomLookAroundGoal(this)); this.randomStrollGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); - pathfindergoalmovetowardsrestriction.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + moveTowardsRestrictionGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this))); } -@@ -330,7 +368,7 @@ public class Guardian extends Monster { +@@ -344,7 +388,7 @@ public class Guardian extends Monster { @Override - public void travel(Vec3 movementInput) { + public void travel(Vec3 travelVector) { if (this.isControlledByLocalInstance() && this.isInWater()) { -- this.moveRelative(0.1F, movementInput); -+ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, movementInput); // Purpur +- this.moveRelative(0.1F, travelVector); ++ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, travelVector); // Purpur - Ridables this.move(MoverType.SELF, this.getDeltaMovement()); - this.setDeltaMovement(this.getDeltaMovement().scale(0.9D)); + this.setDeltaMovement(this.getDeltaMovement().scale(0.9)); if (!this.isMoving() && this.getTarget() == null) { -@@ -342,7 +380,7 @@ public class Guardian extends Monster { - +@@ -452,7 +496,7 @@ public class Guardian extends Monster { + } } -- private static class GuardianMoveControl extends MoveControl { -+ private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - +- static class GuardianMoveControl extends MoveControl { ++ static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables private final Guardian guardian; -@@ -351,8 +389,17 @@ public class Guardian extends Monster { - this.guardian = guardian; + public GuardianMoveControl(Guardian mob) { +@@ -460,8 +504,17 @@ public class Guardian extends Monster { + this.guardian = mob; } -+ // Purpur start ++ // Purpur start - Ridables @Override - public void tick() { + public void purpurTick(Player rider) { @@ -9466,34 +9497,34 @@ index 951f46684623582980901c1ebc1870aa5bcf25a1..da833bf35342f771ecccd5dcac4fe87f + guardian.setDeltaMovement(guardian.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); + guardian.setMoving(guardian.getForwardMot() > 0.0F); // control tail speed + } -+ // Purpur end ++ // Purpur end - Ridables + + @Override -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (this.operation == MoveControl.Operation.MOVE_TO && !this.guardian.getNavigation().isDone()) { - Vec3 vec3d = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ()); - double d0 = vec3d.length(); -@@ -363,7 +410,7 @@ public class Guardian extends Monster { - + Vec3 vec3 = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ()); + double len = vec3.length(); +@@ -471,7 +524,7 @@ public class Guardian extends Monster { + float f = (float)(Mth.atan2(vec3.z, vec3.x) * 180.0F / (float)Math.PI) - 90.0F; this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), f, 90.0F)); this.guardian.yBodyRot = this.guardian.getYRot(); -- float f1 = (float) (this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float f1 = (float) (this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur +- float f1 = (float)(this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); ++ float f1 = (float)(this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables float f2 = Mth.lerp(0.125F, this.guardian.getSpeed(), f1); - this.guardian.setSpeed(f2); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java -index 184fa759db065fb345f3623752229430816d8ad3..7c8ec5cd88fb2083f458a945e716b6f118555db8 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Husk.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java -@@ -21,6 +21,59 @@ public class Husk extends Zombie { - - public Husk(EntityType type, Level world) { - super(type, world); + double d3 = Math.sin((this.guardian.tickCount + this.guardian.getId()) * 0.5) * 0.05; +diff --git a/net/minecraft/world/entity/monster/Husk.java b/net/minecraft/world/entity/monster/Husk.java +index 6155c544ad2722a49c5e41dd7d7b02fedc56474e..a5bfc6f5caba1da8cfcb345524e05e8676672cb0 100644 +--- a/net/minecraft/world/entity/monster/Husk.java ++++ b/net/minecraft/world/entity/monster/Husk.java +@@ -19,8 +19,69 @@ import net.minecraft.world.level.ServerLevelAccessor; + public class Husk extends Zombie { + public Husk(EntityType entityType, Level level) { + super(entityType, level); + this.setShouldBurnInDay(false); // Purpur - API for any mob to burn daylight -+ } -+ -+ // Purpur start + } + ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.huskRidable; @@ -9508,8 +9539,9 @@ index 184fa759db065fb345f3623752229430816d8ad3..7c8ec5cd88fb2083f458a945e716b6f1 + public boolean isControllable() { + return level().purpurConfig.huskControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.huskMaxHealth); @@ -9519,7 +9551,9 @@ index 184fa759db065fb345f3623752229430816d8ad3..7c8ec5cd88fb2083f458a945e716b6f1 + protected void randomizeReinforcementsChance() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.huskSpawnReinforcements); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Configurable jockey options + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.huskJockeyOnlyBaby; @@ -9534,19 +9568,26 @@ index 184fa759db065fb345f3623752229430816d8ad3..7c8ec5cd88fb2083f458a945e716b6f1 + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.huskJockeyTryExistingChickens; + } ++ // Purpur end - Configurable jockey options + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.huskTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.huskAlwaysDropExp; - } - - public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { -@@ -29,7 +82,7 @@ public class Husk extends Zombie { ++ } ++ // Purpur end - Mobs always drop experience ++ + public static boolean checkHuskSpawnRules( + EntityType entityType, ServerLevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random + ) { +@@ -29,7 +90,7 @@ public class Husk extends Zombie { @Override public boolean isSunSensitive() { @@ -9555,15 +9596,15 @@ index 184fa759db065fb345f3623752229430816d8ad3..7c8ec5cd88fb2083f458a945e716b6f1 } @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -index db3aac9ba711dcd18ffc35c4a745ecaec89d0166..2ca241344efc6320d2018bdc772f74470080eeed 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -@@ -59,10 +59,46 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - +diff --git a/net/minecraft/world/entity/monster/Illusioner.java b/net/minecraft/world/entity/monster/Illusioner.java +index 40ca12e391b2adac6b132f1832b1427acb3748bc..9686658b90e886d6236f553d7406771814d18672 100644 +--- a/net/minecraft/world/entity/monster/Illusioner.java ++++ b/net/minecraft/world/entity/monster/Illusioner.java +@@ -57,10 +57,52 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { + } } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.illusionerRidable; @@ -9578,8 +9619,9 @@ index db3aac9ba711dcd18ffc35c4a745ecaec89d0166..2ca241344efc6320d2018bdc772f7447 + public boolean isControllable() { + return level().purpurConfig.illusionerControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed); @@ -9587,42 +9629,47 @@ index db3aac9ba711dcd18ffc35c4a745ecaec89d0166..2ca241344efc6320d2018bdc772f7447 + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.illusionerScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.illusionerTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.illusionerAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal()); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); + this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2)); this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal()); -@@ -71,6 +107,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); +@@ -69,6 +111,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { + this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); - this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -index ae710c3fffc7840a9ff2cbc5cdacef8a2e248253..63caf20256a3deae98b9cd9f54650def172f0e57 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -@@ -24,6 +24,58 @@ public class MagmaCube extends Slime { - super(type, world); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true).setUnseenMemoryTicks(300)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false).setUnseenMemoryTicks(300)); +diff --git a/net/minecraft/world/entity/monster/MagmaCube.java b/net/minecraft/world/entity/monster/MagmaCube.java +index 905ecbd8b22c785ee4ea18004ac50eb1b7005d3f..312d4a3d312b5c326d6ca13ccfc48171e18f4370 100644 +--- a/net/minecraft/world/entity/monster/MagmaCube.java ++++ b/net/minecraft/world/entity/monster/MagmaCube.java +@@ -24,6 +24,64 @@ public class MagmaCube extends Slime { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.magmaCubeRidable; @@ -9642,8 +9689,9 @@ index ae710c3fffc7840a9ff2cbc5cdacef8a2e248253..63caf20256a3deae98b9cd9f54650def + public float getJumpPower() { + return 0.42F * this.getBlockJumpFactor(); // from EntityLiving + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + protected String getMaxHealthEquation() { + return level().purpurConfig.magmaCubeMaxHealth; @@ -9663,68 +9711,77 @@ index ae710c3fffc7840a9ff2cbc5cdacef8a2e248253..63caf20256a3deae98b9cd9f54650def + protected java.util.Map getAttackDamageCache() { + return level().purpurConfig.magmaCubeAttackDamageCache; + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.magmaCubeTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.magmaCubeAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.2F); } -@@ -71,6 +123,7 @@ public class MagmaCube extends Slime { - float f = (float)this.getSize() * 0.1F; - this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z); +@@ -71,6 +129,7 @@ public class MagmaCube extends Slime { + float f = this.getSize() * 0.1F; + this.setDeltaMovement(deltaMovement.x, this.getJumpPower() + f, deltaMovement.z); this.hasImpulse = true; -+ this.actualJump = false; // Purpur ++ this.actualJump = false; // Purpur - Ridables } @Override -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 e2de074bbe7bab0e5a7aecc1fae4c5914a203dd4..c2061f575c731ecc6071384b007517c08e0cf983 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Monster.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java +diff --git a/net/minecraft/world/entity/monster/Monster.java b/net/minecraft/world/entity/monster/Monster.java +index d0d3c825cf8088df4794cf5bfde12a69f4d71754..c1ebb74b0d4a8e2eb8880ccaf20f0f9bc1940094 100644 +--- a/net/minecraft/world/entity/monster/Monster.java ++++ b/net/minecraft/world/entity/monster/Monster.java @@ -88,6 +88,14 @@ public abstract class Monster extends PathfinderMob implements Enemy { } - public static boolean isDarkEnoughToSpawn(ServerLevelAccessor world, BlockPos pos, RandomSource random) { -+ // Purpur start -+ if (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) { -+ net.minecraft.world.level.block.state.BlockState spawnBlock = world.getBlockState(pos.below()); -+ if ((!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) { + public static boolean isDarkEnoughToSpawn(ServerLevelAccessor level, BlockPos pos, RandomSource random) { ++ // Purpur start - Config to disable hostile mob spawn on ice ++ if (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !level.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) { ++ net.minecraft.world.level.block.state.BlockState spawnBlock = level.getBlockState(pos.below()); ++ if ((!level.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) { + return false; + } + } -+ // Purpur end - if (world.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) { ++ // Purpur end - Config to disable hostile mob spawn on ice + if (level.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) { return false; } else { -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 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4ca40e1ed6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -49,6 +49,8 @@ public class Phantom extends FlyingMob implements Enemy { - Vec3 moveTargetPoint; - public BlockPos anchorPoint; - Phantom.AttackPhase attackPhase; -+ private static final net.minecraft.world.item.crafting.Ingredient TORCH = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.item.Items.TORCH, net.minecraft.world.item.Items.SOUL_TORCH); // Purpur -+ Vec3 crystalPosition; // Purpur +diff --git a/net/minecraft/world/entity/monster/Phantom.java b/net/minecraft/world/entity/monster/Phantom.java +index a91aba11ecda561d117c9d8db85c92cdcd81887e..39f94cee78e8fc14d892cb64fb5234bf88d964ad 100644 +--- a/net/minecraft/world/entity/monster/Phantom.java ++++ b/net/minecraft/world/entity/monster/Phantom.java +@@ -47,19 +47,123 @@ public class Phantom extends FlyingMob implements Enemy { + Vec3 moveTargetPoint = Vec3.ZERO; + public BlockPos anchorPoint = BlockPos.ZERO; + Phantom.AttackPhase attackPhase = Phantom.AttackPhase.CIRCLE; ++ Vec3 crystalPosition; // Purpur - Phantoms attracted to crystals and crystals shoot phantoms + // Paper start + @Nullable + public java.util.UUID spawningEntity; + public boolean shouldBurnInDay = true; + // Paper end ++ private static final net.minecraft.world.item.crafting.Ingredient TORCH = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.item.Items.TORCH, net.minecraft.world.item.Items.SOUL_TORCH); // Purpur - Phantoms burn in light - public Phantom(EntityType type, Level world) { - super(type, world); -@@ -58,6 +60,92 @@ public class Phantom extends FlyingMob implements Enemy { + public Phantom(EntityType entityType, Level level) { + super(entityType, level); this.xpReward = 5; this.moveControl = new Phantom.PhantomMoveControl(this); - this.lookControl = new Phantom.PhantomLookControl(this, this); + this.lookControl = new Phantom.PhantomLookControl(this); + this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight -+ } -+ -+ // Purpur start + } + ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.phantomRidable; @@ -9774,13 +9831,15 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + loc.setPitch(-loc.getPitch()); + org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector()); + -+ org.purpurmc.purpur.entity.PhantomFlames flames = new org.purpurmc.purpur.entity.PhantomFlames(level(), this); ++ org.purpurmc.purpur.entity.projectile.PhantomFlames flames = new org.purpurmc.purpur.entity.projectile.PhantomFlames(level(), this); + flames.canGrief = level().purpurConfig.phantomAllowGriefing; + flames.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), 1.0F, 5.0F); + level().addFreshEntity(flames); + return true; + } ++ // Purpur end - Ridables + ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms + @Override + protected void dropFromLootTable(ServerLevel world, DamageSource damageSource, boolean causedByPlayer) { + boolean dropped = false; @@ -9797,56 +9856,74 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + public boolean isCirclingCrystal() { + return crystalPosition != null; + } -+ // Purpur end ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.phantomTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight ++ // Purpur start - API for any mob to burn daylight ++ public boolean shouldBurnInDay() { ++ boolean burnFromDaylight = this.shouldBurnInDay && this.level().purpurConfig.phantomBurnInDaylight; ++ boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight; ++ return burnFromDaylight || burnFromLightSource; ++ } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } ++ // Purpur end - API for any mob to burn daylight ++ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.phantomAlwaysDropExp; - } - ++ } ++ // Purpur end - Mobs always drop experience ++ @Override -@@ -72,9 +160,17 @@ public class Phantom extends FlyingMob implements Enemy { + public boolean isFlapping() { + return (this.getUniqueFlapTickOffset() + this.tickCount) % TICKS_PER_FLAP == 0; +@@ -72,9 +176,17 @@ public class Phantom extends FlyingMob implements Enemy { @Override protected void registerGoals() { - this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal()); - this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal()); - this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal()); -+ // Purpur start -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms + if (level().purpurConfig.phantomOrbitCrystalRadius > 0) { -+ this.goalSelector.addGoal(1, new FindCrystalGoal(this)); -+ this.goalSelector.addGoal(2, new OrbitCrystalGoal(this)); ++ this.goalSelector.addGoal(1, new PhantomFindCrystalGoal(this)); ++ this.goalSelector.addGoal(2, new PhantomOrbitCrystalGoal(this)); + } + this.goalSelector.addGoal(3, new Phantom.PhantomAttackStrategyGoal()); + this.goalSelector.addGoal(4, new Phantom.PhantomSweepAttackGoal()); + this.goalSelector.addGoal(5, new Phantom.PhantomCircleAroundAnchorGoal()); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ // Purpur end ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal()); } -@@ -90,7 +186,10 @@ public class Phantom extends FlyingMob implements Enemy { +@@ -90,7 +202,11 @@ public class Phantom extends FlyingMob implements Enemy { private void updatePhantomSizeInfo() { this.refreshDimensions(); -- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) (6 + this.getPhantomSize())); -+ // Purpur start +- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(6 + this.getPhantomSize()); ++ if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur - Ridables - Phantom flames on swoop ++ // Purpur start - Configurable entity base attributes + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomMaxHealth, () -> this.level().purpurConfig.phantomMaxHealthCache, () -> 20.0D)); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) 6 + this.getPhantomSize())); -+ // Purpur end ++ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) (6 + this.getPhantomSize()))); ++ // Purpur end - Configurable entity base attributes } public int getPhantomSize() { -@@ -115,6 +214,21 @@ public class Phantom extends FlyingMob implements Enemy { +@@ -115,6 +231,23 @@ public class Phantom extends FlyingMob implements Enemy { return true; } ++ // Purpur start - Configurable entity base attributes + private double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { + int size = getPhantomSize(); + Double value = cache.get().get(size); @@ -9861,86 +9938,93 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + } + return value; + } ++ // Purpur end - Configurable entity base attributes + @Override public void tick() { super.tick(); -@@ -135,21 +249,23 @@ public class Phantom extends FlyingMob implements Enemy { - this.level().addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f3, this.getY() + (double) f5, this.getZ() - (double) f4, 0.0D, 0.0D, 0.0D); - } - -+ if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur - } +@@ -146,7 +279,13 @@ public class Phantom extends FlyingMob implements Enemy { @Override public void aiStep() { - if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API -- this.igniteForSeconds(8.0F); -- } -- + // Purpur - implemented in LivingEntity; moved down to shouldBurnInDay() - API for any mob to burn daylight - super.aiStep(); - } ++ // Purpur start - Phantoms burn in light ++ boolean burnFromDaylight = this.shouldBurnInDay && this.isSunBurnTick() && this.level().purpurConfig.phantomBurnInDaylight; ++ boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight; ++ if (this.isAlive() && (burnFromDaylight || burnFromLightSource)) { // Paper - shouldBurnInDay API ++ // Purpur end - Phantoms burn in light ++ if (getRider() == null || !this.isControllable()) // Purpur - Ridables + this.igniteForSeconds(8.0F); + } - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { +@@ -158,7 +297,11 @@ public class Phantom extends FlyingMob implements Enemy { + ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData + ) { this.anchorPoint = this.blockPosition().above(5); - this.setPhantomSize(0); -+ // Purpur start -+ int min = world.getLevel().purpurConfig.phantomMinSize; -+ int max = world.getLevel().purpurConfig.phantomMaxSize; -+ this.setPhantomSize(min == max ? min : world.getRandom().nextInt(max + 1 - min) + min); -+ // Purpur end - return super.finalizeSpawn(world, difficulty, spawnReason, entityData); ++ // Purpur start - Configurable phantom size ++ int min = level.getLevel().purpurConfig.phantomMinSize; ++ int max = level.getLevel().purpurConfig.phantomMaxSize; ++ this.setPhantomSize(min == max ? min : level.getRandom().nextInt(max + 1 - min) + min); ++ // Purpur end - Configurable phantom size + return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } -@@ -165,7 +281,7 @@ public class Phantom extends FlyingMob implements Enemy { - if (nbt.hasUUID("Paper.SpawningEntity")) { - this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); +@@ -175,7 +318,7 @@ public class Phantom extends FlyingMob implements Enemy { + if (compound.hasUUID("Paper.SpawningEntity")) { + this.spawningEntity = compound.getUUID("Paper.SpawningEntity"); } -- if (nbt.contains("Paper.ShouldBurnInDay")) { -+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); +- if (compound.contains("Paper.ShouldBurnInDay")) { ++ if (false && compound.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight + this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay"); } // Paper end -@@ -182,7 +298,7 @@ public class Phantom extends FlyingMob implements Enemy { +@@ -192,7 +335,7 @@ public class Phantom extends FlyingMob implements Enemy { if (this.spawningEntity != null) { - nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); + compound.putUUID("Paper.SpawningEntity", this.spawningEntity); } -- nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); -+ //nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight +- compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); ++ //compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight // Paper end } -@@ -242,8 +358,14 @@ public class Phantom extends FlyingMob implements Enemy { - return this.spawningEntity; +@@ -262,6 +405,7 @@ public class Phantom extends FlyingMob implements Enemy { + List nearbyPlayers = serverLevel.getNearbyPlayers( + this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0, 64.0, 16.0) + ); ++ if (level().purpurConfig.phantomIgnorePlayersWithTorch) nearbyPlayers.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND))); // Purpur - Phantoms burn in light + if (!nearbyPlayers.isEmpty()) { + nearbyPlayers.sort(Comparator.comparing(Entity::getY).reversed()); + +@@ -407,25 +551,160 @@ public class Phantom extends FlyingMob implements Enemy { + } } - public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } -- private boolean shouldBurnInDay = true; -- public boolean shouldBurnInDay() { return shouldBurnInDay; } -+ //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight -+ // Purpur start - API for any mob to burn daylight -+ public boolean shouldBurnInDay() { -+ boolean burnFromDaylight = this.shouldBurnInDay && this.level().purpurConfig.phantomBurnInDaylight; -+ boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight; -+ return burnFromDaylight || burnFromLightSource; + +- static class PhantomLookControl extends LookControl { ++ static class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables + public PhantomLookControl(Mob mob) { + super(mob); + } + ++ // Purpur start - Ridables ++ public void purpurTick(Player rider) { ++ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F); ++ } ++ // Purpur end - Ridables ++ ++ @Override ++ public void vanillaTick() { // Purpur - Ridables ++ } + } -+ // Purpur end - API for any mob to burn daylight - public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } - // Paper end - -@@ -254,7 +376,125 @@ public class Phantom extends FlyingMob implements Enemy { - private AttackPhase() {} - } - -- private class PhantomMoveControl extends MoveControl { -+ // Purpur start -+ class FindCrystalGoal extends Goal { ++ ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms ++ class PhantomFindCrystalGoal extends Goal { + private final Phantom phantom; + private net.minecraft.world.entity.boss.enderdragon.EndCrystal crystal; + private Comparator comparator; + -+ FindCrystalGoal(Phantom phantom) { ++ PhantomFindCrystalGoal(Phantom phantom) { + this.phantom = phantom; + this.comparator = Comparator.comparingDouble(phantom::distanceToSqr); + this.setFlags(EnumSet.of(Flag.LOOK)); @@ -9988,14 +10072,14 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + } + } + -+ class OrbitCrystalGoal extends Goal { ++ class PhantomOrbitCrystalGoal extends Goal { + private final Phantom phantom; + private float offset; + private float radius; + private float verticalChange; + private float direction; + -+ OrbitCrystalGoal(Phantom phantom) { ++ PhantomOrbitCrystalGoal(Phantom phantom) { + this.phantom = phantom; + this.setFlags(EnumSet.of(Flag.MOVE)); + } @@ -10013,8 +10097,8 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + updateOffset(); + } + -+ @Override -+ public void tick() { + @Override + public void tick() { + if (phantom.random.nextInt(350) == 0) { + this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; + } @@ -10048,19 +10132,19 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + this.radius * Mth.cos(this.offset), + -4.0F + this.verticalChange, + this.radius * Mth.sin(this.offset)); -+ } -+ } -+ // Purpur end -+ -+ private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur + } + } ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms +- class PhantomMoveControl extends MoveControl { ++ class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables private float speed = 0.1F; -@@ -262,8 +502,19 @@ public class Phantom extends FlyingMob implements Enemy { - super(entity); + public PhantomMoveControl(final Mob mob) { + super(mob); } -+ // Purpur start ++ // Purpur start - Ridables + public void purpurTick(Player rider) { + if (!Phantom.this.onGround) { + // phantom is always in motion when flying @@ -10069,67 +10153,36 @@ index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4c + } + super.purpurTick(rider); + } -+ // Purpur end ++ // Purpur end - Ridables + @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (Phantom.this.horizontalCollision) { Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F); this.speed = 0.1F; -@@ -309,14 +560,20 @@ public class Phantom extends FlyingMob implements Enemy { - } - } - -- private class PhantomLookControl extends LookControl { -+ private class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - - public PhantomLookControl(final Phantom entity, final Mob phantom) { - super(phantom); - } - -+ // Purpur start -+ public void purpurTick(Player rider) { -+ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F); -+ } -+ // Purpur end -+ - @Override -- public void tick() {} -+ public void vanillaTick() {} // Purpur - } - - private class PhantomBodyRotationControl extends BodyRotationControl { -@@ -403,6 +660,12 @@ public class Phantom extends FlyingMob implements Enemy { +@@ -492,6 +771,12 @@ public class Phantom extends FlyingMob implements Enemy { return false; - } else if (!entityliving.isAlive()) { + } else if (!target.isAlive()) { return false; -+ // Purpur start ++ // Purpur start - Phantoms burn in light + } else if (level().purpurConfig.phantomBurnInLight > 0 && level().getLightEmission(new BlockPos(Phantom.this)) >= level().purpurConfig.phantomBurnInLight) { + return false; -+ } else if (level().purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)))) { ++ } else if (level().purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(target.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(target.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)))) { + return false; -+ // Purpur end - } else { - if (entityliving instanceof Player) { - Player entityhuman = (Player) entityliving; -@@ -549,6 +812,7 @@ public class Phantom extends FlyingMob implements Enemy { - ServerLevel worldserver = getServerLevel(Phantom.this.level()); - List list = worldserver.getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D)); - -+ if (level().purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)));// Purpur - if (!list.isEmpty()) { - list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error - Iterator iterator = list.iterator(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -index 3e8631c7bd1e7591051ca21c6ae7acd87d3c7529..85d5c84a8861905e4546901aa6707078571eb402 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -@@ -64,16 +64,51 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve - super(type, world); ++ // Purpur end - Phantoms burn in light + } else if (target instanceof Player player && (target.isSpectator() || player.isCreative())) { + return false; + } else if (!this.canUse()) { +diff --git a/net/minecraft/world/entity/monster/Pillager.java b/net/minecraft/world/entity/monster/Pillager.java +index e855ebc5be2cef3b96e2c01a8c1d388e433c0d52..a57d869cdc6a05124237933437aa2d26ff72cab3 100644 +--- a/net/minecraft/world/entity/monster/Pillager.java ++++ b/net/minecraft/world/entity/monster/Pillager.java +@@ -63,16 +63,57 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.pillagerRidable; @@ -10144,48 +10197,54 @@ index 3e8631c7bd1e7591051ca21c6ae7acd87d3c7529..85d5c84a8861905e4546901aa6707078 + public boolean isControllable() { + return level().purpurConfig.pillagerControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pillagerMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pillagerScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pillagerTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pillagerAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); // Paper - decomp fix - this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F)); - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2)); + this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); + this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0, 8.0F)); + this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 15.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); -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 c96fbfe448b3e7b722a8db0e1688276776abd94e..98c1934b4895a86cd8748edf906aaa721a87a123 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -77,15 +77,57 @@ public class Ravager extends Raider { +diff --git a/net/minecraft/world/entity/monster/Ravager.java b/net/minecraft/world/entity/monster/Ravager.java +index 129479cedda20e77719f4f7237ec5b9acc5b00c8..ce5cd032203839887a29008c2a1420c6bb6f4fee 100644 +--- a/net/minecraft/world/entity/monster/Ravager.java ++++ b/net/minecraft/world/entity/monster/Ravager.java +@@ -66,14 +66,62 @@ public class Ravager extends Raider { this.setPathfindingMalus(PathType.LEAVES, 0.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.ravagerRidable; @@ -10206,84 +10265,103 @@ index c96fbfe448b3e7b722a8db0e1688276776abd94e..98c1934b4895a86cd8748edf906aaa72 + super.onMount(rider); + getNavigation().stop(); + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ravagerMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ravagerScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ravagerTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ravagerAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); -+ if (level().purpurConfig.ravagerAvoidRabbits) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, net.minecraft.world.entity.animal.Rabbit.class, 6.0F, 1.0D, 1.2D)); // Purpur - this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, true)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D)); ++ if (level().purpurConfig.ravagerAvoidRabbits) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, net.minecraft.world.entity.animal.Rabbit.class, 6.0F, 1.0D, 1.2D)); // Purpur - option to make ravagers afraid of rabbits ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, true)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(2, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving, worldserver) -> { -@@ -138,7 +180,7 @@ public class Ravager extends Raider { + this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entity, level) -> !entity.isBaby())); +@@ -130,7 +178,7 @@ public class Ravager extends Raider { @Override public void aiStep() { super.aiStep(); - if (this.isAlive()) { -+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur ++ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur - Ridables if (this.isImmobile()) { - this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0); } else { -@@ -153,7 +195,7 @@ public class Ravager extends Raider { - if (world instanceof ServerLevel) { - ServerLevel worldserver = (ServerLevel) world; +@@ -141,7 +189,7 @@ public class Ravager extends Raider { -- if (this.horizontalCollision && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (this.horizontalCollision && (worldserver.purpurConfig.ravagerBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur - boolean flag = false; - AABB axisalignedbb = this.getBoundingBox().inflate(0.2D); - Iterator iterator = BlockPos.betweenClosed(Mth.floor(axisalignedbb.minX), Mth.floor(axisalignedbb.minY), Mth.floor(axisalignedbb.minZ), Mth.floor(axisalignedbb.maxX), Mth.floor(axisalignedbb.maxY), Mth.floor(axisalignedbb.maxZ)).iterator(); -@@ -163,7 +205,7 @@ public class Ravager extends Raider { - BlockState iblockdata = worldserver.getBlockState(blockposition); - Block block = iblockdata.getBlock(); + if (this.level() instanceof ServerLevel serverLevel + && this.horizontalCollision +- && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { ++ && serverLevel.purpurConfig.ravagerBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected + boolean flag = false; + AABB aabb = this.getBoundingBox().inflate(0.2); -- if (block instanceof LeavesBlock) { -+ if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur - // CraftBukkit start - if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - continue; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index 6e0f2f6573ed6be9b91de960d55c269417ad8907..e3fefd52c83079fe3eab1a96dd81a183f718192b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -84,7 +84,7 @@ public class Shulker extends AbstractGolem implements VariantHolder= f) { +- int size = this.level().getEntities(EntityType.SHULKER, boundingBox.inflate(8.0), Entity::isAlive).size(); +- float f = (size - 1) / 5.0F; +- if (!(this.level().random.nextFloat() < f)) { ++ // Purpur start - Shulker spawn from bullet options + if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) { -+ // Purpur start + float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance; + if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) { -+ int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size(); ++ int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, boundingBox.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size(); + try { + chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue(); + } catch (javax.script.ScriptException e) { @@ -10362,47 +10432,47 @@ index 6e0f2f6573ed6be9b91de960d55c269417ad8907..e3fefd52c83079fe3eab1a96dd81a183 + } + } + if (this.level().random.nextFloat() <= chance) { - Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level(), EntitySpawnReason.BREEDING); -+ // Purpur end - - if (entityshulker != null) { - entityshulker.setVariant(this.getVariant()); -@@ -590,7 +647,7 @@ public class Shulker extends AbstractGolem implements VariantHolder variant) { -@@ -601,7 +658,7 @@ public class Shulker extends AbstractGolem implements VariantHolder getVariant() { - return Optional.ofNullable(this.getColor()); -+ return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur ++ return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur - Shulker spawn from bullet options } @Nullable -@@ -611,7 +668,7 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true)); } -@@ -165,12 +202,12 @@ public class Silverfish extends Monster { - +@@ -141,7 +184,7 @@ public class Silverfish extends Monster { + return false; + } else { + RandomSource random = this.mob.getRandom(); +- if (getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && random.nextInt(reducedTickDelay(10)) == 0) { ++ if (getServerLevel(this.mob).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && random.nextInt(reducedTickDelay(10)) == 0) { // Purpur - Add mobGriefing bypass to everything affected + this.selectedDirection = Direction.getRandom(random); + BlockPos blockPos = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5, this.mob.getZ()).relative(this.selectedDirection); + BlockState blockState = this.mob.level().getBlockState(blockPos); +@@ -218,7 +261,7 @@ public class Silverfish extends Monster { + Block block = blockState.getBlock(); if (block instanceof InfestedBlock) { // CraftBukkit start -- BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state -+ BlockState afterState = (getServerLevel(world).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state - if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state +- BlockState afterState = getServerLevel(level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? blockState.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(level.getBlockState(blockPos1)); // Paper - fix wrong block state ++ BlockState afterState = getServerLevel(level).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? blockState.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(level.getBlockState(blockPos1)); // Paper - fix wrong block state // Purpur - Add mobGriefing bypass to everything affected + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockPos1, afterState)) { // Paper - fix wrong block state continue; } - // CraftBukkit end -- if (getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (getServerLevel(world).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - world.destroyBlock(blockposition1, true, this.silverfish); - } else { - world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3); -@@ -208,7 +245,7 @@ public class Silverfish extends Monster { - } else { - RandomSource randomsource = this.mob.getRandom(); - -- if (getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { -+ if (getServerLevel((Entity) this.mob).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { // Purpur - this.selectedDirection = Direction.getRandom(randomsource); - BlockPos blockposition = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection); - BlockState iblockdata = this.mob.level().getBlockState(blockposition); -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 3972e2ed0554e2550519e994888e068df0a151e5..3cbe4c1ed514936a00e0181cb1e647fbb58032bb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -17,6 +17,16 @@ import net.minecraft.world.item.Items; - import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.Level; - -+// Purpur start -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.Blocks; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import net.minecraft.world.InteractionHand; -+import net.minecraft.world.InteractionResult; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.core.particles.ParticleTypes; -+// Purpur end -+ - public class Skeleton extends AbstractSkeleton { - - private static final int TOTAL_CONVERSION_TIME = 300; -@@ -29,6 +39,40 @@ public class Skeleton extends AbstractSkeleton { - super(type, world); +diff --git a/net/minecraft/world/entity/monster/Skeleton.java b/net/minecraft/world/entity/monster/Skeleton.java +index 72468b903a9bbebca817d8a1c6796dc05342a29d..7018471a465724e4a1d67fe18bb602176cddd522 100644 +--- a/net/minecraft/world/entity/monster/Skeleton.java ++++ b/net/minecraft/world/entity/monster/Skeleton.java +@@ -25,6 +25,44 @@ public class Skeleton extends AbstractSkeleton { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.skeletonRidable; @@ -10514,58 +10567,62 @@ index 3972e2ed0554e2550519e994888e068df0a151e5..3cbe4c1ed514936a00e0181cb1e647fb + public boolean isControllable() { + return level().purpurConfig.skeletonControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.skeletonMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + -+ // Purpur start ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.skeletonTakeDamageFromWater; + } -+ // Purpur end ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.skeletonAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -@@ -147,4 +191,63 @@ public class Skeleton extends AbstractSkeleton { +@@ -135,4 +173,64 @@ public class Skeleton extends AbstractSkeleton { + this.spawnAtLocation(level, Items.SKELETON_SKULL); } - } + -+ // Purpur start ++ // Purpur start - Skeletons eat wither roses + private int witherRosesFed = 0; + + @Override -+ public InteractionResult mobInteract(Player player, InteractionHand hand) { -+ ItemStack stack = player.getItemInHand(hand); ++ public net.minecraft.world.InteractionResult mobInteract(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) { ++ net.minecraft.world.item.ItemStack stack = player.getItemInHand(hand); + -+ if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == Blocks.WITHER_ROSE.asItem()) { ++ if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == net.minecraft.world.level.block.Blocks.WITHER_ROSE.asItem()) { + return this.feedWitherRose(player, stack); + } + + return super.mobInteract(player, hand); + } + -+ private InteractionResult feedWitherRose(Player player, ItemStack stack) { ++ private net.minecraft.world.InteractionResult feedWitherRose(net.minecraft.world.entity.player.Player player, net.minecraft.world.item.ItemStack stack) { + if (++witherRosesFed < level().purpurConfig.skeletonFeedWitherRoses) { + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } -+ return InteractionResult.CONSUME; ++ return net.minecraft.world.InteractionResult.CONSUME; + } + + WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level(), net.minecraft.world.entity.EntitySpawnReason.CONVERSION); + if (skeleton == null) { -+ return InteractionResult.PASS; ++ return net.minecraft.world.InteractionResult.PASS; + } + + skeleton.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); @@ -10581,8 +10638,8 @@ index 3972e2ed0554e2550519e994888e068df0a151e5..3cbe4c1ed514936a00e0181cb1e647fb + skeleton.setCustomName(this.getCustomName()); + } + -+ if (CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { -+ return InteractionResult.PASS; ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { ++ return net.minecraft.world.InteractionResult.PASS; + } + + this.level().addFreshEntity(skeleton); @@ -10592,47 +10649,38 @@ index 3972e2ed0554e2550519e994888e068df0a151e5..3cbe4c1ed514936a00e0181cb1e647fb + } + + for (int i = 0; i < 15; ++i) { -+ ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, ++ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER, ++ false, true, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, -+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); ++ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0); + } -+ return InteractionResult.SUCCESS; ++ return net.minecraft.world.InteractionResult.SUCCESS; + } -+ // Purpur end ++ // Purpur end - Skeletons eat wither roses } -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 72346a7e5269c91e3143933ac37e65ad9639b791..dad4ef9c672eb4247142de5d045678795951164c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -65,6 +65,7 @@ public class Slime extends Mob implements Enemy { - public float squish; +diff --git a/net/minecraft/world/entity/monster/Slime.java b/net/minecraft/world/entity/monster/Slime.java +index 8db4cba1be6d7a5538295ba8da1fdaf7a77a16d0..240a54b210e23d5b79e6bcaf3806aa454668135d 100644 +--- a/net/minecraft/world/entity/monster/Slime.java ++++ b/net/minecraft/world/entity/monster/Slime.java +@@ -57,6 +57,7 @@ public class Slime extends Mob implements Enemy { public float oSquish; private boolean wasOnGround; -+ protected boolean actualJump; // Purpur + private boolean canWander = true; // Paper - Slime pathfinder events ++ protected boolean actualJump; // Purpur - Ridables - public Slime(EntityType type, Level world) { - super(type, world); -@@ -72,12 +73,89 @@ public class Slime extends Mob implements Enemy { + public Slime(EntityType entityType, Level level) { + super(entityType, level); +@@ -64,12 +65,95 @@ public class Slime extends Mob implements Enemy { this.moveControl = new Slime.SlimeMoveControl(this); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.slimeRidable; + } + + @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.slimeTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.slimeAlwaysDropExp; -+ } -+ -+ @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater; + } @@ -10658,7 +10706,9 @@ index 72346a7e5269c91e3143933ac37e65ad9639b791..dad4ef9c672eb4247142de5d04567879 + } + return true; // do not jump() in wasd controller, let vanilla controller handle + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + protected String getMaxHealthEquation() { + return level().purpurConfig.slimeMaxHealth; + } @@ -10689,60 +10739,74 @@ index 72346a7e5269c91e3143933ac37e65ad9639b791..dad4ef9c672eb4247142de5d04567879 + } + return value; + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes ++ ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.slimeTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience ++ @Override ++ protected boolean isAlwaysExperienceDropper() { ++ return this.level().purpurConfig.slimeAlwaysDropExp; ++ } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this)); this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this)); this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this)); this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving, worldserver) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -102,9 +180,9 @@ public class Slime extends Mob implements Enemy { - this.entityData.set(Slime.ID_SIZE, j); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector + .addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> Math.abs(entity.getY() - this.getY()) <= 4.0)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); +@@ -92,9 +176,9 @@ public class Slime extends Mob implements Enemy { + this.entityData.set(ID_SIZE, i); this.reapplyPosition(); this.refreshDimensions(); -- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j)); -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur - this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j)); -- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur - if (heal) { +- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(i * i); ++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) (size * size))); // Purpur - Configurable entity base attributes + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.2F + 0.1F * i); +- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(i); ++ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) i)); // Purpur - Configurable entity base attributes + if (resetHealth) { this.setHealth(this.getMaxHealth()); } -@@ -386,6 +464,7 @@ public class Slime extends Mob implements Enemy { - - this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); +@@ -371,6 +455,7 @@ public class Slime extends Mob implements Enemy { + Vec3 deltaMovement = this.getDeltaMovement(); + this.setDeltaMovement(deltaMovement.x, this.getJumpPower(), deltaMovement.z); this.hasImpulse = true; -+ this.actualJump = false; // Purpur ++ this.actualJump = false; // Purpur - Ridables } @Nullable -@@ -419,7 +498,7 @@ public class Slime extends Mob implements Enemy { - return super.getDefaultDimensions(pose).scale((float) this.getSize()); +@@ -535,7 +620,7 @@ public class Slime extends Mob implements Enemy { + } } -- private static class SlimeMoveControl extends MoveControl { -+ private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - +- static class SlimeMoveControl extends MoveControl { ++ static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables private float yRot; private int jumpDelay; -@@ -438,21 +517,33 @@ public class Slime extends Mob implements Enemy { + private final Slime slime; +@@ -553,21 +638,33 @@ public class Slime extends Mob implements Enemy { } public void setWantedMovement(double speed) { - this.speedModifier = speed; -+ this.setSpeedModifier(speed); // Purpur ++ this.setSpeedModifier(speed); // Purpur - Ridables this.operation = MoveControl.Operation.MOVE_TO; } @Override public void tick() { -+ // Purpur start ++ // Purpur start - Ridables + if (slime.getRider() != null && slime.isControllable()) { + purpurTick(slime.getRider()); + if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) { @@ -10753,39 +10817,39 @@ index 72346a7e5269c91e3143933ac37e65ad9639b791..dad4ef9c672eb4247142de5d04567879 + jumpDelay = 20; + } + } else { -+ // Purpur end ++ // Purpur end - Ridables this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F)); this.mob.yHeadRot = this.mob.getYRot(); this.mob.yBodyRot = this.mob.getYRot(); - if (this.operation != MoveControl.Operation.MOVE_TO) { -+ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur ++ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur - Ridables this.mob.setZza(0.0F); } else { this.operation = MoveControl.Operation.WAIT; if (this.mob.onGround()) { -- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); -+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur +- this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); ++ this.mob.setSpeed((float)(this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables if (this.jumpDelay-- <= 0) { this.jumpDelay = this.slime.getJumpDelay(); if (this.isAggressive) { -@@ -469,7 +560,7 @@ public class Slime extends Mob implements Enemy { +@@ -584,7 +681,7 @@ public class Slime extends Mob implements Enemy { this.mob.setSpeed(0.0F); } } else { -- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); -+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur +- this.mob.setSpeed((float)(this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); ++ this.mob.setSpeed((float)(this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables } - } -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 91e521414c3ea5722aac7506b7589fbb399e9636..1669acbcf97bee0fa6b0ee91cf53217c53cf55d8 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -51,9 +51,43 @@ public class Spider extends Monster { - super(type, world); + } +diff --git a/net/minecraft/world/entity/monster/Spider.java b/net/minecraft/world/entity/monster/Spider.java +index af0305079a367899708ee2bbac82aefaa9129d2f..ece8d81e29709a1c0cb5c7b55b1193b630ecb113 100644 +--- a/net/minecraft/world/entity/monster/Spider.java ++++ b/net/minecraft/world/entity/monster/Spider.java +@@ -50,15 +50,56 @@ public class Spider extends Monster { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.spiderRidable; @@ -10800,48 +10864,53 @@ index 91e521414c3ea5722aac7506b7589fbb399e9636..1669acbcf97bee0fa6b0ee91cf53217c + public boolean isControllable() { + return level().purpurConfig.spiderControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.spiderMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.spiderScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.spiderTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.spiderAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Armadillo.class, 6.0F, 1.0D, 1.2D, (entityliving) -> { - return !((Armadillo) entityliving).isScared(); - })); -@@ -62,6 +96,7 @@ public class Spider extends Monster { - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); ++ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Armadillo.class, 6.0F, 1.0, 1.2, livingEntity -> !((Armadillo)livingEntity).isScared())); + this.goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4F)); + this.goalSelector.addGoal(4, new Spider.SpiderAttackGoal(this)); + this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this)); this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class)); this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java -index baaf17107584b253d7e268749849bf5b0d0c88ab..bb6984d82e6c0a83f456e725b20e0f21e0cac602 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Stray.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java -@@ -22,6 +22,38 @@ public class Stray extends AbstractSkeleton { - super(type, world); +diff --git a/net/minecraft/world/entity/monster/Stray.java b/net/minecraft/world/entity/monster/Stray.java +index 5fa2b7920a233afb3659b02cbd7ab82307ea9aaf..fdaa01c8bbaf9e613462e41f508fdc96b3467e03 100644 +--- a/net/minecraft/world/entity/monster/Stray.java ++++ b/net/minecraft/world/entity/monster/Stray.java +@@ -22,6 +22,44 @@ public class Stray extends AbstractSkeleton { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.strayRidable; @@ -10856,42 +10925,48 @@ index baaf17107584b253d7e268749849bf5b0d0c88ab..bb6984d82e6c0a83f456e725b20e0f21 + public boolean isControllable() { + return level().purpurConfig.strayControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.strayTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.strayAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + public static boolean checkStraySpawnRules( - EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random + EntityType entityType, ServerLevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random ) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index 711b7eb8e9fdedbc87965828e573fe8d5c357d53..c3b5b34a54de945071692293645b8a8865aed961 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java -@@ -91,12 +91,45 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - super(type, world); - this.steering = new ItemBasedSteering(this.entityData, Strider.DATA_BOOST_TIME, Strider.DATA_SADDLE_ID); +diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java +index cbae85171a1bb64ee3be40ba211d88e68bf672e4..241526239bdbd5d9276f85e7fca46a7051f46a25 100644 +--- a/net/minecraft/world/entity/monster/Strider.java ++++ b/net/minecraft/world/entity/monster/Strider.java +@@ -88,12 +88,51 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + public Strider(EntityType entityType, Level level) { + super(entityType, level); this.blocksBuilding = true; - this.setPathfindingMalus(PathType.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur ++ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage this.setPathfindingMalus(PathType.LAVA, 0.0F); this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.striderRidable; @@ -10906,50 +10981,56 @@ index 711b7eb8e9fdedbc87965828e573fe8d5c357d53..c3b5b34a54de945071692293645b8a88 + public boolean isControllable() { + return level().purpurConfig.striderControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.striderScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.striderBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.striderAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + - public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); - -@@ -158,6 +191,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + public static boolean checkStriderSpawnRules( + EntityType entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random + ) { +@@ -156,6 +195,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override protected void registerGoals() { - this.goalSelector.addGoal(1, new PanicGoal(this, 1.65D)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.temptGoal = new TemptGoal(this, 1.4D, (itemstack) -> { - return itemstack.is(ItemTags.STRIDER_TEMPT_ITEMS); -@@ -412,7 +446,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + this.goalSelector.addGoal(1, new PanicGoal(this, 1.65)); ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(2, new BreedGoal(this, 1.0)); + this.temptGoal = new TemptGoal(this, 1.4, itemStack -> itemStack.is(ItemTags.STRIDER_TEMPT_ITEMS), false); + this.goalSelector.addGoal(3, this.temptGoal); +@@ -370,7 +410,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override public boolean isSensitiveToWater() { - return true; -+ return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur ++ return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage } @Override -@@ -454,6 +488,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { +@@ -414,6 +454,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { - boolean flag = this.isFood(player.getItemInHand(hand)); - + boolean isFood = this.isFood(player.getItemInHand(hand)); + // Purpur start -+ if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) { ++ if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !isFood && isSaddled() && !isVehicle()) { + this.steering.setSaddle(false); + if (!player.getAbilities().instabuild) { + ItemStack saddle = new ItemStack(Items.SADDLE); @@ -10961,27 +11042,27 @@ index 711b7eb8e9fdedbc87965828e573fe8d5c357d53..c3b5b34a54de945071692293645b8a88 + } + // Purpur end + - if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { + if (!isFood && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { if (!this.level().isClientSide) { player.startRiding(this); -@@ -466,7 +513,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - if (!enuminteractionresult.consumesAction()) { - ItemStack itemstack = player.getItemInHand(hand); - -- return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS); -+ return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand)); // Purpur +@@ -424,7 +477,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + InteractionResult interactionResult = super.mobInteract(player, hand); + if (!interactionResult.consumesAction()) { + ItemStack itemInHand = player.getItemInHand(hand); +- return (InteractionResult)(itemInHand.is(Items.SADDLE) ? itemInHand.interactLivingEntity(player, this, hand) : InteractionResult.PASS); ++ return (InteractionResult)(itemInHand.is(Items.SADDLE) ? itemInHand.interactLivingEntity(player, this, hand) : tryRide(player, hand)); // Purpur - Ridables } else { - if (flag && !this.isSilent()) { - this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java -index 183a33b7d666d652b455baa7e8339e9c4a870a58..fe19c0cf6a2c81b547158179518bf26be388cc7a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java -@@ -59,6 +59,66 @@ public class Vex extends Monster implements TraceableEntity { + if (isFood && !this.isSilent()) { + this.level() +diff --git a/net/minecraft/world/entity/monster/Vex.java b/net/minecraft/world/entity/monster/Vex.java +index 7f1cdea810db24182f8f87076c42a19b1b43e98a..0ce9eb1f4108b6afab6df84d5e709be0882e515a 100644 +--- a/net/minecraft/world/entity/monster/Vex.java ++++ b/net/minecraft/world/entity/monster/Vex.java +@@ -58,6 +58,72 @@ public class Vex extends Monster implements TraceableEntity { this.xpReward = 3; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.vexRidable; @@ -11023,92 +11104,97 @@ index 183a33b7d666d652b455baa7e8339e9c4a870a58..fe19c0cf6a2c81b547158179518bf26b + public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { + return false; // no fall damage please + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vexScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.vexTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.vexAlwaysDropExp; + } -+ // Purpur end ++ // Purpur end - Mobs always drop experience + @Override public boolean isFlapping() { - return this.tickCount % Vex.TICKS_PER_FLAP == 0; -@@ -71,7 +131,7 @@ public class Vex extends Monster implements TraceableEntity { + return this.tickCount % TICKS_PER_FLAP == 0; +@@ -70,7 +136,7 @@ public class Vex extends Monster implements TraceableEntity { @Override public void tick() { - this.noPhysics = true; -+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur ++ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur - Ridables super.tick(); this.noPhysics = false; this.setNoGravity(true); -@@ -86,17 +146,19 @@ public class Vex extends Monster implements TraceableEntity { +@@ -84,17 +150,19 @@ public class Vex extends Monster implements TraceableEntity { protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal()); this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal()); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { -- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D); -+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur; +- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0).add(Attributes.ATTACK_DAMAGE, 4.0); ++ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0).add(Attributes.ATTACK_DAMAGE, 4.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur; } @Override -@@ -228,14 +290,14 @@ public class Vex extends Monster implements TraceableEntity { - this.setDropChance(EquipmentSlot.MAINHAND, 0.0F); +@@ -301,13 +369,13 @@ public class Vex extends Monster implements TraceableEntity { + } } -- private class VexMoveControl extends MoveControl { -+ private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - - public VexMoveControl(final Vex entityvex) { - super(entityvex); +- class VexMoveControl extends MoveControl { ++ class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables + public VexMoveControl(final Vex mob) { + super(mob); } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (this.operation == MoveControl.Operation.MOVE_TO) { - Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); - double d0 = vec3d.length(); -@@ -244,7 +306,7 @@ public class Vex extends Monster implements TraceableEntity { + Vec3 vec3 = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); + double len = vec3.length(); +@@ -315,7 +383,7 @@ public class Vex extends Monster implements TraceableEntity { this.operation = MoveControl.Operation.WAIT; - Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D)); + Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5)); } else { -- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0))); -+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur +- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3.scale(this.speedModifier * 0.05 / len))); ++ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3.scale(this.getSpeedModifier() * 0.05 / len))); // Purpur - Ridables if (Vex.this.getTarget() == null) { - Vec3 vec3d1 = Vex.this.getDeltaMovement(); - -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 96b105697c91314148fd1b783501389214b1a3f0..a2c81d2a1077b2977f1595fd592044baf3e81bab 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -@@ -55,15 +55,50 @@ public class Vindicator extends AbstractIllager { - super(type, world); + Vec3 deltaMovement = Vex.this.getDeltaMovement(); + Vex.this.setYRot(-((float)Mth.atan2(deltaMovement.x, deltaMovement.z)) * (180.0F / (float)Math.PI)); +diff --git a/net/minecraft/world/entity/monster/Vindicator.java b/net/minecraft/world/entity/monster/Vindicator.java +index 5f649d1e69d2be8d8e6963544e3aab6848616893..b584f71440a81ac09d24e59763a21e857f290e5a 100644 +--- a/net/minecraft/world/entity/monster/Vindicator.java ++++ b/net/minecraft/world/entity/monster/Vindicator.java +@@ -55,15 +55,56 @@ public class Vindicator extends AbstractIllager { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.vindicatorRidable; @@ -11123,59 +11209,65 @@ index 96b105697c91314148fd1b783501389214b1a3f0..a2c81d2a1077b2977f1595fd592044ba + public boolean isControllable() { + return level().purpurConfig.vindicatorControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vindicatorMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vindicatorScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.vindicatorTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.vindicatorAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2)); this.goalSelector.addGoal(2, new Vindicator.VindicatorBreakDoorGoal(this)); this.goalSelector.addGoal(3, new AbstractIllager.RaiderOpenDoorGoal(this)); this.goalSelector.addGoal(4, new Raider.HoldGroundAttackGoal(this, 10.0F)); this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, false)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true)); -@@ -132,6 +167,11 @@ public class Vindicator extends AbstractIllager { - RandomSource randomSource = world.getRandom(); - this.populateDefaultEquipmentSlots(randomSource, difficulty); - this.populateDefaultEquipmentEnchantments(world, randomSource, difficulty); -+ // Purpur start +@@ -132,6 +173,11 @@ public class Vindicator extends AbstractIllager { + RandomSource random = level.getRandom(); + this.populateDefaultEquipmentSlots(random, difficulty); + this.populateDefaultEquipmentEnchantments(level, random, difficulty); ++ // Purpur start - Special mobs naturally spawn + if (level().purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level().purpurConfig.vindicatorJohnnySpawnChance) { + setCustomName(Component.translatable("Johnny")); + } -+ // Purpur end - return spawnGroupData; ++ // Purpur end - Special mobs naturally spawn + return spawnGroupData1; } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index a03fa8a3e648532a7ffaaf523ca87c13e8af4c0a..313228811d1eff478887511f99b49706efc49774 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -57,6 +57,39 @@ public class Witch extends Raider implements RangedAttackMob { - super(type, world); +diff --git a/net/minecraft/world/entity/monster/Witch.java b/net/minecraft/world/entity/monster/Witch.java +index 9f5676b5fa0f369adb8643391738c5ae33911df7..e4353c64732067198f082cdd266c1f1ee1fe4f4e 100644 +--- a/net/minecraft/world/entity/monster/Witch.java ++++ b/net/minecraft/world/entity/monster/Witch.java +@@ -56,6 +56,45 @@ public class Witch extends Raider implements RangedAttackMob { + super(entityType, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.witchRidable; @@ -11190,49 +11282,55 @@ index a03fa8a3e648532a7ffaaf523ca87c13e8af4c0a..313228811d1eff478887511f99b49706 + public boolean isControllable() { + return level().purpurConfig.witchControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witchMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witchScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.witchTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.witchAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { super.registerGoals(); -@@ -65,10 +98,12 @@ public class Witch extends Raider implements RangedAttackMob { - }); - this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (TargetingConditions.Selector) null); +@@ -64,10 +103,12 @@ public class Witch extends Raider implements RangedAttackMob { + ); + this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, null); this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F)); - this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D)); ++ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0, 60, 10.0F)); + this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(3, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class})); ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class)); this.targetSelector.addGoal(2, this.healRaidersGoal); this.targetSelector.addGoal(3, this.attackPlayersGoal); -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 37d3acda84a984bf4f1c44b3d27e2102839d3e8e..23fc36780a3e15260f8cb1001c8d676464a9df3a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -33,6 +33,39 @@ public class WitherSkeleton extends AbstractSkeleton { +diff --git a/net/minecraft/world/entity/monster/WitherSkeleton.java b/net/minecraft/world/entity/monster/WitherSkeleton.java +index eed8dbefd4d04082dc4e091c858e50309ed5c49b..ff2596f69d00b36c65872ab2e27e5d44a6ffa3e1 100644 +--- a/net/minecraft/world/entity/monster/WitherSkeleton.java ++++ b/net/minecraft/world/entity/monster/WitherSkeleton.java +@@ -34,6 +34,45 @@ public class WitherSkeleton extends AbstractSkeleton { this.setPathfindingMalus(PathType.LAVA, 8.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.witherSkeletonRidable; @@ -11247,36 +11345,42 @@ index 37d3acda84a984bf4f1c44b3d27e2102839d3e8e..23fc36780a3e15260f8cb1001c8d6764 + public boolean isControllable() { + return level().purpurConfig.witherSkeletonControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherSkeletonMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherSkeletonScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.witherSkeletonTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.witherSkeletonAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -index 50b559a92b54c0be7624b1aebc70537573c58666..3f388ea39d2760df946cc027a4ae63704dcaa03a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -@@ -83,6 +83,39 @@ public class Zoglin extends Monster implements HoglinBase { +diff --git a/net/minecraft/world/entity/monster/Zoglin.java b/net/minecraft/world/entity/monster/Zoglin.java +index 8a7418db237553719671f3cd51f42ebed1eb7804..ee59cf42db965296e6e8d4aa4ec7b33dc5142237 100644 +--- a/net/minecraft/world/entity/monster/Zoglin.java ++++ b/net/minecraft/world/entity/monster/Zoglin.java +@@ -83,6 +83,45 @@ public class Zoglin extends Monster implements HoglinBase { this.xpReward = 5; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.zoglinRidable; @@ -11291,57 +11395,63 @@ index 50b559a92b54c0be7624b1aebc70537573c58666..3f388ea39d2760df946cc027a4ae6370 + public boolean isControllable() { + return level().purpurConfig.zoglinControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zoglinScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zoglinTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zoglinAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected Brain.Provider brainProvider() { return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); -@@ -246,6 +279,7 @@ public class Zoglin extends Monster implements HoglinBase { +@@ -246,6 +285,7 @@ public class Zoglin extends Monster implements HoglinBase { @Override - protected void customServerAiStep(ServerLevel world) { + protected void customServerAiStep(ServerLevel level) { + if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + this.getBrain().tick(level, this); this.updateActivity(); } -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 a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac6a17a151 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -99,22 +99,70 @@ public class Zombie extends Monster { +diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java +index cf231380febd6d316eb902d43c636135ee0d7fa4..7af71c777dca26cd94b1807a2a77ea0d30e92976 100644 +--- a/net/minecraft/world/entity/monster/Zombie.java ++++ b/net/minecraft/world/entity/monster/Zombie.java +@@ -89,22 +89,78 @@ public class Zombie extends Monster { + private boolean canBreakDoors; private int inWaterTime; public int conversionTime; - // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time - private boolean shouldBurnInDay = true; // Paper - Add more Zombie API + //private boolean shouldBurnInDay = true; // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight - public Zombie(EntityType type, Level world) { - super(type, world); - 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 - Configurable door breaking difficulty + public Zombie(EntityType entityType, Level level) { + super(entityType, level); + this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(level.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(entityType, level.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty + this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight } - public Zombie(Level world) { - this(EntityType.ZOMBIE, world); + public Zombie(Level level) { + this(EntityType.ZOMBIE, level); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.zombieRidable; @@ -11356,14 +11466,17 @@ index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac + public boolean isControllable() { + return level().purpurConfig.zombieControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombieScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Configurable jockey options + public boolean jockeyOnlyBaby() { + return level().purpurConfig.zombieJockeyOnlyBaby; + } @@ -11375,34 +11488,39 @@ index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.zombieJockeyTryExistingChickens; + } ++ // Purpur end - Configurable jockey options + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zombieTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zombieAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3)); // Paper - Add zombie targets turtle egg config this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables this.addBehaviourGoals(); } -@@ -124,7 +172,19 @@ public class Zombie extends Monster { - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class)); +@@ -114,7 +170,19 @@ public class Zombie extends Monster { + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0)); + this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); -- if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot -+ // Purpur start -+ if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Spigot +- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot ++ // Purpur start - Add option to disable zombie aggressiveness towards villagers ++ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Spigot + @Override + public boolean canUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse(); @@ -11413,26 +11531,23 @@ index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse(); + } + }); -+ // Purpur end ++ // Purpur end - Add option to disable zombie aggressiveness towards villagers this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); } -@@ -239,32 +299,7 @@ public class Zombie extends Monster { +@@ -230,29 +298,7 @@ public class Zombie extends Monster { @Override public void aiStep() { - if (this.isAlive()) { - boolean flag = this.isSunSensitive() && this.isSunBurnTick(); -- - if (flag) { -- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -- -- if (!itemstack.isEmpty()) { -- if (itemstack.isDamageableItem()) { -- Item item = itemstack.getItem(); -- -- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); -- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { +- ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD); +- if (!itemBySlot.isEmpty()) { +- if (itemBySlot.isDamageableItem()) { +- Item item = itemBySlot.getItem(); +- itemBySlot.setDamageValue(itemBySlot.getDamageValue() + this.random.nextInt(2)); +- if (itemBySlot.getDamageValue() >= itemBySlot.getMaxDamage()) { - this.onEquippedItemBroken(item, EquipmentSlot.HEAD); - this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); - } @@ -11451,7 +11566,7 @@ index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac super.aiStep(); } -@@ -324,6 +359,7 @@ public class Zombie extends Monster { +@@ -311,6 +357,7 @@ public class Zombie extends Monster { // CraftBukkit end } @@ -11459,90 +11574,88 @@ index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac public boolean isSunSensitive() { return this.shouldBurnInDay; // Paper - Add more Zombie API } -@@ -462,7 +498,7 @@ public class Zombie extends Monster { - nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); - nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); - nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); -- nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API -+ //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight +@@ -449,7 +496,7 @@ public class Zombie extends Monster { + compound.putBoolean("CanBreakDoors", this.canBreakDoors()); + compound.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); + compound.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); +- compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API ++ //compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight } @Override -@@ -475,7 +511,7 @@ public class Zombie extends Monster { - this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime")); +@@ -462,7 +509,7 @@ public class Zombie extends Monster { + this.startUnderWaterConversion(compound.getInt("DrownedConversionTime")); } // Paper start - Add more Zombie API -- if (nbt.contains("Paper.ShouldBurnInDay")) { -+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); +- if (compound.contains("Paper.ShouldBurnInDay")) { ++ if (false && compound.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight + this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay"); } // Paper end - Add more Zombie API -@@ -528,19 +564,20 @@ public class Zombie extends Monster { +@@ -517,19 +564,18 @@ public class Zombie extends Monster { } - if (object instanceof Zombie.ZombieGroupData entityzombie_groupdatazombie) { -- if (entityzombie_groupdatazombie.isBaby) { + if (spawnGroupData instanceof Zombie.ZombieGroupData zombieGroupData) { +- if (zombieGroupData.isBaby) { - this.setBaby(true); -+ // Purpur start -+ if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby) { -+ this.setBaby(entityzombie_groupdatazombie.isBaby); - if (entityzombie_groupdatazombie.canSpawnJockey) { -- if ((double) randomsource.nextFloat() < 0.05D) { -- List list = world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN); -+ if ((double) randomsource.nextFloat() < jockeyChance()) { -+ List list = jockeyTryExistingChickens() ? world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN) : java.util.Collections.emptyList(); -+ // Purpur end - - if (!list.isEmpty()) { - Chicken entitychicken = (Chicken) list.get(0); - - entitychicken.setChickenJockey(true); - this.startRiding(entitychicken); ++ if (!jockeyOnlyBaby() || zombieGroupData.isBaby) { // Purpur - Configurable jockey options ++ this.setBaby(zombieGroupData.isBaby); // Purpur - Configurable jockey options + if (zombieGroupData.canSpawnJockey) { +- if (random.nextFloat() < 0.05) { +- List entitiesOfClass = level.getEntitiesOfClass( ++ if (random.nextFloat() < jockeyChance()) { // Purpur - Configurable jockey options ++ List entitiesOfClass = jockeyTryExistingChickens() ? level.getEntitiesOfClass( // Purpur - Configurable jockey options + Chicken.class, this.getBoundingBox().inflate(5.0, 3.0, 5.0), EntitySelector.ENTITY_NOT_BEING_RIDDEN +- ); ++ ) : java.util.Collections.emptyList(); // Purpur - Configurable jockey options + if (!entitiesOfClass.isEmpty()) { + Chicken chicken = entitiesOfClass.get(0); + chicken.setChickenJockey(true); + this.startRiding(chicken); - } -- } else if ((double) randomsource.nextFloat() < 0.05D) { -+ } else { // Purpur - Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level(), EntitySpawnReason.JOCKEY); - - if (entitychicken1 != null) { -@@ -550,6 +587,7 @@ public class Zombie extends Monster { - this.startRiding(entitychicken1); - world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit +- } else if (random.nextFloat() < 0.05) { ++ } else { // Purpur - Configurable jockey options + Chicken chicken1 = EntityType.CHICKEN.create(this.level(), EntitySpawnReason.JOCKEY); + if (chicken1 != null) { + chicken1.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F); +@@ -538,6 +584,7 @@ public class Zombie extends Monster { + this.startRiding(chicken1); + level.addFreshEntity(chicken1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit } -+ } // Purpur ++ } // Purpur - Configurable jockey options } } } -@@ -562,11 +600,7 @@ public class Zombie extends Monster { +@@ -550,10 +597,7 @@ public class Zombie extends Monster { } if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { -- LocalDate localdate = LocalDate.now(); -- int i = localdate.get(ChronoField.DAY_OF_MONTH); -- int j = localdate.get(ChronoField.MONTH_OF_YEAR); -- -- if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { -+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); +- LocalDate localDate = LocalDate.now(); +- int i = localDate.get(ChronoField.DAY_OF_MONTH); +- int i1 = localDate.get(ChronoField.MONTH_OF_YEAR); +- if (i1 == 10 && i == 31 && random.nextFloat() < 0.25F) { ++ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(level.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - Halloween options and optimizations + this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(random.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F; } -@@ -608,7 +642,7 @@ public class Zombie extends Monster { +@@ -603,7 +647,7 @@ public class Zombie extends Monster { } protected void randomizeReinforcementsChance() { -- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.10000000149011612D); -+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur +- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.1F); ++ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur - Configurable entity base attributes } @Override -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 18c19e4b675000aacb74344909fc104964231008..6f6b32bf7f68d05e4173c31f2e631a409b858a05 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -85,6 +85,58 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - }); +diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java +index 9061e0b6544d6a31a4dc5b51037f608031a00553..1ca0514732916d325c4a76d73120aaf613c3f780 100644 +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -78,6 +78,66 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + .ifPresent(profession -> this.setVillagerData(this.getVillagerData().setProfession(profession.value()))); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.zombieVillagerRidable; @@ -11557,8 +11670,9 @@ index 18c19e4b675000aacb74344909fc104964231008..6f6b32bf7f68d05e4173c31f2e631a40 + public boolean isControllable() { + return level().purpurConfig.zombieVillagerControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth); @@ -11568,12 +11682,9 @@ index 18c19e4b675000aacb74344909fc104964231008..6f6b32bf7f68d05e4173c31f2e631a40 + protected void randomizeReinforcementsChance() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieVillagerSpawnReinforcements); + } ++ // Purpur end - Configurable entity base attributes + -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.zombieVillagerTakeDamageFromWater; -+ } -+ ++ // Purpur start - Configurable jockey options + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.zombieVillagerJockeyOnlyBaby; @@ -11588,37 +11699,47 @@ index 18c19e4b675000aacb74344909fc104964231008..6f6b32bf7f68d05e4173c31f2e631a40 + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.zombieVillagerJockeyTryExistingChickens; + } ++ // Purpur end - Configurable jockey options + ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.zombieVillagerTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zombieVillagerAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -@@ -177,10 +229,10 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - ItemStack itemstack = player.getItemInHand(hand); - - if (itemstack.is(Items.GOLDEN_APPLE)) { +@@ -156,10 +216,10 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + public InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack itemInHand = player.getItemInHand(hand); + if (itemInHand.is(Items.GOLDEN_APPLE)) { - if (this.hasEffect(MobEffects.WEAKNESS)) { -+ if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur - itemstack.consume(1, player); ++ if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur - Add option to disable zombie villagers cure + itemInHand.consume(1, player); if (!this.level().isClientSide) { - this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600); -+ this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur ++ this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur - Customizable Zombie Villager curing times } return InteractionResult.SUCCESS_SERVER; -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 03e3cbe73119ca76417d4dd192e1560bdfc373ec..8c3271dcc8c9aa58e2e007eba282c11e42b4e0c9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -@@ -63,6 +63,54 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { +diff --git a/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/net/minecraft/world/entity/monster/ZombifiedPiglin.java +index c7eab22fe4a0541ebdba96961521271ee5619cd4..fddbbffafea275dad187b7908386cf4c05c86743 100644 +--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java ++++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java +@@ -63,6 +63,62 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.setPathfindingMalus(PathType.LAVA, 8.0F); } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.zombifiedPiglinRidable; @@ -11633,19 +11754,17 @@ index 03e3cbe73119ca76417d4dd192e1560bdfc373ec..8c3271dcc8c9aa58e2e007eba282c11e + public boolean isControllable() { + return level().purpurConfig.zombifiedPiglinControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombifiedPiglinScale); + } ++ // Purpur end - Configurable entity base attributes + -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.zombifiedPiglinTakeDamageFromWater; -+ } -+ ++ // Purpur start - Configurable jockey options + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.zombifiedPiglinJockeyOnlyBaby; @@ -11660,51 +11779,61 @@ index 03e3cbe73119ca76417d4dd192e1560bdfc373ec..8c3271dcc8c9aa58e2e007eba282c11e + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.zombifiedPiglinJockeyTryExistingChickens; + } ++ // Purpur end - Configurable jockey options + ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.zombifiedPiglinTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zombifiedPiglinAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override - public void setPersistentAngerTarget(@Nullable UUID angryAt) { - this.persistentAngerTarget = angryAt; -@@ -110,7 +158,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + public void setPersistentAngerTarget(@Nullable UUID target) { + this.persistentAngerTarget = target; +@@ -112,7 +168,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.maybeAlertOthers(); } - if (this.isAngry()) { -+ if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur ++ if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - Toggle for Zombified Piglin death always counting as player kill when angry this.lastHurtByPlayerTime = this.tickCount; } -@@ -165,7 +213,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random); +@@ -163,7 +219,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + this.ticksUntilNextAlert = ALERT_INTERVAL.sample(this.random); } -- if (entityliving instanceof Player) { -+ if (entityliving instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - this.setLastHurtByPlayer((Player) entityliving); +- if (livingEntity instanceof Player) { ++ if (livingEntity instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - Toggle for Zombified Piglin death always counting as player kill when angry + this.setLastHurtByPlayer((Player)livingEntity); } -@@ -245,7 +293,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { +@@ -245,7 +301,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @Override protected void randomizeReinforcementsChance() { -- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0D); -+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur +- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0); ++ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur - Configurable entity base attributes } @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java -index 444e67eb9fa1fabff2304896bdd71772747dc437..b48b0124014f1cc9d206d343daf7170691d04b3f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java -+++ b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java -@@ -61,6 +61,29 @@ public class Creaking extends Monster { - this.xpReward = 0; +diff --git a/net/minecraft/world/entity/monster/creaking/Creaking.java b/net/minecraft/world/entity/monster/creaking/Creaking.java +index 6cd7d0f82bd97c6adb521eda3bc84c60f87c0cda..2c6833753950f1bb0941b0cbe54bebddb84b137d 100644 +--- a/net/minecraft/world/entity/monster/creaking/Creaking.java ++++ b/net/minecraft/world/entity/monster/creaking/Creaking.java +@@ -100,6 +100,37 @@ public class Creaking extends Monster { + return this.getHomePos() != null; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.creakingRidable; @@ -11722,73 +11851,66 @@ index 444e67eb9fa1fabff2304896bdd71772747dc437..b48b0124014f1cc9d206d343daf71706 + + @Override + protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + } -+ // Purpur end ++ // Purpur end - Ridables + - @Override - protected BodyRotationControl createBodyControl() { - return new Creaking.CreakingBodyRotationControl(this); -@@ -180,6 +203,14 @@ public class Creaking extends Monster { - return this.isActive() ? null : SoundEvents.CREAKING_AMBIENT; - } - -+ // Purpur start ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creakingMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creakingScale); + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + @Override - protected SoundEvent getHurtSound(DamageSource source) { - return SoundEvents.CREAKING_SWAY; -@@ -291,28 +322,28 @@ public class Creaking extends Monster { + protected BodyRotationControl createBodyControl() { + return new Creaking.CreakingBodyRotationControl(this); +@@ -575,28 +606,28 @@ public class Creaking extends Monster { } } - class CreakingLookControl extends LookControl { -+ class CreakingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - public CreakingLookControl(final Creaking creaking) { - super(creaking); ++ class CreakingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables { + public CreakingLookControl(final Creaking mob) { + super(mob); } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (Creaking.this.canMove()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } } } - class CreakingMoveControl extends MoveControl { -+ class CreakingMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - public CreakingMoveControl(final Creaking creaking) { - super(creaking); ++ class CreakingMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables + public CreakingMoveControl(final Creaking mob) { + super(mob); } @Override - public void tick() { -+ public void vanillaTick() { // Purpur ++ public void vanillaTick() { // Purpur - Ridables if (Creaking.this.canMove()) { - super.tick(); -+ super.vanillaTick(); // Purpur ++ super.vanillaTick(); // Purpur - Ridables } } } -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -index 9802aa9a1e4822684ba4406d2cafa4af69fe2ec5..4930459f7fe91eddb5049be3503bf0f2e3821ec4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -@@ -69,11 +69,49 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { +diff --git a/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/net/minecraft/world/entity/monster/hoglin/Hoglin.java +index f93d6564c59ae9a144b56ea3355c4c7425b99eeb..24725009d7619b0e9043d7d0039f805c611aedf6 100644 +--- a/net/minecraft/world/entity/monster/hoglin/Hoglin.java ++++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java +@@ -90,11 +90,57 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { this.xpReward = 5; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.hoglinRidable; @@ -11803,55 +11925,62 @@ index 9802aa9a1e4822684ba4406d2cafa4af69fe2ec5..4930459f7fe91eddb5049be3503bf0f2 + public boolean isControllable() { + return level().purpurConfig.hoglinControllable; + } ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.hoglinScale); + } -+ // Purpur end ++ // Purpur end - Configurable entity base attributes + @VisibleForTesting public void setTimeInOverworld(int timeInOverworld) { this.timeInOverworld = timeInOverworld; } ++ // Purpur start - Make entity breeding times configurable + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.hoglinBreedingTicks; + } ++ // Purpur end - Make entity breeding times configurable + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.hoglinTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.hoglinAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override public boolean canBeLeashed() { return true; -@@ -139,7 +177,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { +@@ -157,6 +203,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { private int behaviorTick; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); HoglinAi.updateActivity(this); - if (this.isConverting()) { -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 aeedddd4b2ade905455c04ce475d35042c54e741..26d2ce3504efd8077a2d8c7d29a179f9ba47d63b 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 -@@ -94,6 +94,39 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento +diff --git a/net/minecraft/world/entity/monster/piglin/Piglin.java b/net/minecraft/world/entity/monster/piglin/Piglin.java +index 4c30f967c12e11c2e7ae24977509762747dd36de..242b2545b6082f567d0bb7900ef06ded3c0fdcdd 100644 +--- a/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -149,6 +149,45 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento this.xpReward = 5; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.piglinRidable; @@ -11866,78 +11995,94 @@ index aeedddd4b2ade905455c04ce475d35042c54e741..26d2ce3504efd8077a2d8c7d29a179f9 + public boolean isControllable() { + return level().purpurConfig.piglinControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.piglinTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.piglinAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -305,7 +338,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); +@@ -343,6 +382,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento private int behaviorTick; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { -- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + protected void customServerAiStep(ServerLevel level) { ++ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(level, this); PiglinAi.updateActivity(this); - super.customServerAiStep(world); -@@ -401,7 +434,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento +@@ -444,7 +484,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento @Override - public boolean wantsToPickUp(ServerLevel world, ItemStack stack) { -- return world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); -+ return (world.purpurConfig.piglinBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); // Purpur + public boolean wantsToPickUp(ServerLevel level, ItemStack stack) { +- return level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); ++ return level.purpurConfig.piglinBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); // Purpur - Add mobGriefing bypass to everything affected } - protected boolean canReplaceCurrentItem(ItemStack stack) { -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 e283b1296c1e831376bfe9491cbf02ed4b3fffe4..27a6de70530c2a1cbe2f77a7fb493038121710ea 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 -@@ -605,11 +605,18 @@ public class PiglinAi { + protected boolean canReplaceCurrentItem(ItemStack candidate) { +diff --git a/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index d0af8fc156408db7172267d45636b3c9e66d638c..650783aaa4ab1a3198cdfb74fd02edff969d8921 100644 +--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableSet; + import com.mojang.datafixers.util.Pair; + import java.util.Collections; ++import java.util.Iterator; + import java.util.List; + import java.util.Optional; + import net.minecraft.server.level.ServerLevel; +@@ -666,7 +667,7 @@ public class PiglinAi { + + public static boolean isWearingSafeArmor(LivingEntity entity) { + for (ItemStack itemStack : entity.getArmorAndBodyArmorSlots()) { +- if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR)) { ++ if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR) || (entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim && isWearingGoldTrim(itemStack.getItem()))) { // Purpur - piglins ignore gold-trimmed armor + return true; } - - itemstack = (ItemStack) iterator.next(); -- } while (!itemstack.is(ItemTags.PIGLIN_SAFE_ARMOR)); -+ } while (!itemstack.is(ItemTags.PIGLIN_SAFE_ARMOR) && (!entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim || !isWearingGoldTrim(itemstack.getItem()))); // Purpur - - return true; + } +@@ -674,6 +675,13 @@ public class PiglinAi { + return false; } -+ // Purpur start ++ // Purpur start - piglins ignore gold-trimmed armor + private static boolean isWearingGoldTrim(Item itemstack) { + net.minecraft.world.item.equipment.trim.ArmorTrim armorTrim = itemstack.components().get(net.minecraft.core.component.DataComponents.TRIM); + return armorTrim != null && armorTrim.material().is(net.minecraft.world.item.equipment.trim.TrimMaterials.GOLD); + } -+ // Purpur end ++ // Purpur end - piglins ignore gold-trimmed armor + private static void stopWalking(Piglin piglin) { piglin.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); piglin.getNavigation().stop(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -index 0cab5d5aa80f9ca8c34f982f0b81044328ba2d8f..e5e88d4b1d2a0ff043e75cf081eef60e774d3c0f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -@@ -63,6 +63,39 @@ public class PiglinBrute extends AbstractPiglin { +diff --git a/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/net/minecraft/world/entity/monster/piglin/PiglinBrute.java +index e5f91e64f61bdb7b7f7e3f101083e9bd5dbe7551..460dace4818605ecd5409be887013713f06442a5 100644 +--- a/net/minecraft/world/entity/monster/piglin/PiglinBrute.java ++++ b/net/minecraft/world/entity/monster/piglin/PiglinBrute.java +@@ -63,6 +63,45 @@ public class PiglinBrute extends AbstractPiglin { this.xpReward = 20; } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.piglinBruteRidable; @@ -11952,47 +12097,53 @@ index 0cab5d5aa80f9ca8c34f982f0b81044328ba2d8f..e5e88d4b1d2a0ff043e75cf081eef60e + public boolean isControllable() { + return level().purpurConfig.piglinBruteControllable; + } -+ // Purpur end ++ // Purpur end - Ridables + ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinBruteMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinBruteScale); + } ++ // Purpur end - Configurable entity base attributes + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.piglinBruteTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.piglinBruteAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes() .add(Attributes.MAX_HEALTH, 50.0) -@@ -113,6 +146,7 @@ public class PiglinBrute extends AbstractPiglin { +@@ -113,6 +152,7 @@ public class PiglinBrute extends AbstractPiglin { @Override - protected void customServerAiStep(ServerLevel world) { + protected void customServerAiStep(ServerLevel level) { + if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick(world, this); + this.getBrain().tick(level, this); PiglinBruteAi.updateActivity(this); PiglinBruteAi.maybePlayActivitySound(this); -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 64f3204b4b6ac0c57d0eb833a959f666f5259c6b..d0f744597de323f6169e15cabe9b3a80dbdbf5bb 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 -@@ -125,8 +125,32 @@ public class Warden extends Monster implements VibrationSystem { +diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java +index f7b9824519fc22b35a7b5f4f0ef9f9891162a493..26f3fe1c80b0d87b96076432f35fe4f95f92ce13 100644 +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -127,8 +127,32 @@ public class Warden extends Monster implements VibrationSystem { this.setPathfindingMalus(PathType.LAVA, 8.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); -+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur ++ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur - Ridables } -+ // Purpur start ++ // Purpur start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.wardenRidable; @@ -12010,150 +12161,101 @@ index 64f3204b4b6ac0c57d0eb833a959f666f5259c6b..d0f744597de323f6169e15cabe9b3a80 + + @Override + protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables ++ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables + } -+ // Purpur end ++ // Purpur end - Ridables + @Override - public Packet getAddEntityPacket(ServerEntity entityTrackerEntry) { - return new ClientboundAddEntityPacket(this, entityTrackerEntry, this.hasPose(Pose.EMERGING) ? 1 : 0); -@@ -392,17 +416,14 @@ public class Warden extends Monster implements VibrationSystem { + public Packet getAddEntityPacket(ServerEntity entity) { + return new ClientboundAddEntityPacket(this, entity, this.hasPose(Pose.EMERGING) ? 1 : 0); +@@ -391,6 +415,7 @@ public class Warden extends Monster implements VibrationSystem { @Contract("null->false") public boolean canTargetEntity(@Nullable Entity entity) { -- boolean flag; -- -+ if (getRider() != null && isControllable()) return false; // Purpur - if (entity instanceof LivingEntity entityliving) { - if (this.level() == entity.level() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level().getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) { -- flag = true; -- return flag; -+ return true; // Purpur - wtf - } - } - -- flag = false; -- return flag; -+ return false; // Purpur - wtf - } - - public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) { -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 5f656fc726a1dc5f42657095a2f2b7cf85b92d7c..6c74cf1dea99b3b967b8c3d76f405f823c881fb9 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -48,6 +48,7 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent; ++ if (getRider() != null && isControllable()) return false; // Purpur - Ridables + return entity instanceof LivingEntity livingEntity + && this.level() == entity.level() + && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) +diff --git a/net/minecraft/world/entity/npc/AbstractVillager.java b/net/minecraft/world/entity/npc/AbstractVillager.java +index a71d16d968bb90fd7aca6f01a3dd56df4f9a7ce6..b4e79cac5611942240ce85120f7bbee329ae2fb8 100644 +--- a/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -45,6 +45,8 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent; // CraftBukkit end public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { -+ static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur - ++ static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur - Villagers follow emerald blocks ++ // CraftBukkit start @Override -diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -index b0236c7bf9441aa84d3795ffed05dd6099f29636..796dcc0dcf9022b455b8847e045266b8802da0cf 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java + public CraftMerchant getCraftMerchant() { +diff --git a/net/minecraft/world/entity/npc/CatSpawner.java b/net/minecraft/world/entity/npc/CatSpawner.java +index e6d368bc601357cfca694ce328c8e6e47491f3b5..489613b31fe47a0bd43fafddbeab6bb61c17505d 100644 +--- a/net/minecraft/world/entity/npc/CatSpawner.java ++++ b/net/minecraft/world/entity/npc/CatSpawner.java @@ -27,7 +27,7 @@ public class CatSpawner implements CustomSpawner { if (this.nextTick > 0) { return 0; } else { - this.nextTick = 1200; -+ this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur - Player player = world.getRandomPlayer(); - if (player == null) { ++ this.nextTick = level.purpurConfig.catSpawnDelay; // Purpur - Cat spawning options + Player randomPlayer = level.getRandomPlayer(); + if (randomPlayer == null) { return 0; @@ -61,8 +61,12 @@ public class CatSpawner implements CustomSpawner { - private int spawnInVillage(ServerLevel world, BlockPos pos) { + private int spawnInVillage(ServerLevel serverLevel, BlockPos pos) { int i = 48; -- if (world.getPoiManager().getCountInRange(entry -> entry.is(PoiTypes.HOME), pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { -- List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(48.0, 8.0, 48.0)); -+ // Purpur start -+ int range = world.purpurConfig.catSpawnVillageScanRange; +- if (serverLevel.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { +- List entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(48.0, 8.0, 48.0)); ++ // Purpur start - Cat spawning options ++ int range = serverLevel.purpurConfig.catSpawnVillageScanRange; + if (range <= 0) return 0; -+ if (world.getPoiManager().getCountInRange(entry -> entry.is(PoiTypes.HOME), pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { -+ List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); -+ // Purpur end - if (list.size() < 5) { - return this.spawnCat(pos, world); ++ if (serverLevel.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); ++ // Purpur end - Cat spawning options + if (entitiesOfClass.size() < 5) { + return this.spawnCat(pos, serverLevel); } @@ -73,7 +77,11 @@ public class CatSpawner implements CustomSpawner { - private int spawnInHut(ServerLevel world, BlockPos pos) { + private int spawnInHut(ServerLevel serverLevel, BlockPos pos) { int i = 16; -- List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(16.0, 8.0, 16.0)); -+ // Purpur start -+ int range = world.purpurConfig.catSpawnSwampHutScanRange; +- List entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(16.0, 8.0, 16.0)); ++ // Purpur start - Cat spawning options ++ int range = serverLevel.purpurConfig.catSpawnSwampHutScanRange; + if (range <= 0) return 0; -+ List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); -+ // Purpur end - return list.size() < 1 ? this.spawnCat(pos, world) : 0; ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); ++ // Purpur end - Cat spawning options + return entitiesOfClass.size() < 1 ? this.spawnCat(pos, serverLevel) : 0; } -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 bb5a924c203be427e3faf84917b86622fdec5f25..fd373d98f836c057c30c4fbd5d7618cc4e757b78 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -139,6 +139,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - }, MemoryModuleType.MEETING_POINT, (entityvillager, holder) -> { - return holder.is(PoiTypes.MEETING); - }); -+ private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur -+ private int notLobotomizedCount = 0; // Purpur +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index a02cd34bcd643c7abad3a355043cb88d035143a0..347affae3cc18e01474734d2da2699c9b7b17e26 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -179,6 +179,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + ); public long nextGolemPanic = -1; // Pufferfish ++ private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur - Lobotomize stuck villagers ++ private int notLobotomizedCount = 0; // Purpur - Lobotomize stuck villagers -@@ -156,6 +158,93 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE)); + public Villager(EntityType entityType, Level level) { + this(entityType, level, VillagerType.PLAINS); +@@ -193,6 +195,103 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.setVillagerData(this.getVillagerData().setType(villagerType).setProfession(VillagerProfession.NONE)); } -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.villagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.villagerControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); -+ } -+ ++ // Purpur start - Allow leashing villagers + @Override + public boolean canBeLeashed() { + return level().purpurConfig.villagerCanBeLeashed; + } -+ // Purpur end -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.villagerScale); -+ this.getAttribute(Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.villagerTemptRange); // Purpur -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.villagerTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.villagerAlwaysDropExp; -+ } ++ // Purpur end - Allow leashing villagers + ++ // Purpur start - Lobotomize stuck villagers + private boolean checkLobotomized() { + int interval = this.level().purpurConfig.villagerLobotomizeCheckInterval; + boolean shouldCheckForTradeLocked = this.level().purpurConfig.villagerLobotomizeWaitUntilTradeLocked; @@ -12186,8 +12288,8 @@ index bb5a924c203be427e3faf84917b86622fdec5f25..fd373d98f836c057c30c4fbd5d7618cc + } + net.minecraft.world.level.block.Block bottom = state.getBlock(); + if (bottom instanceof net.minecraft.world.level.block.FenceBlock || -+ bottom instanceof net.minecraft.world.level.block.FenceGateBlock || -+ bottom instanceof net.minecraft.world.level.block.WallBlock) { ++ bottom instanceof net.minecraft.world.level.block.FenceGateBlock || ++ bottom instanceof net.minecraft.world.level.block.WallBlock) { + // bottom block is too tall to get over + return false; + } @@ -12195,144 +12297,196 @@ index bb5a924c203be427e3faf84917b86622fdec5f25..fd373d98f836c057c30c4fbd5d7618cc + // only if both blocks have no collision + return !bottom.hasCollision && !top.hasCollision; + } ++ // Purpur end - Lobotomize stuck villagers ++ ++ // Purpur start - Ridables ++ @Override ++ public boolean isRidable() { ++ return level().purpurConfig.villagerRidable; ++ } ++ ++ @Override ++ public boolean dismountsUnderwater() { ++ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater; ++ } ++ ++ @Override ++ public boolean isControllable() { ++ return level().purpurConfig.villagerControllable; ++ } ++ ++ @Override ++ protected void registerGoals() { ++ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); ++ if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - Villagers follow emerald blocks ++ } ++ // Purpur end - Ridables ++ ++ // Purpur start - Configurable entity base attributes ++ @Override ++ public void initAttributes() { ++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth); ++ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.villagerScale); ++ this.getAttribute(Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.villagerTemptRange); // Purpur - Villagers follow emerald blocks ++ } ++ // Purpur end - Configurable entity base attributes ++ ++ // Purpur start - Toggle for water sensitive mob damage ++ @Override ++ public boolean isSensitiveToWater() { ++ return this.level().purpurConfig.villagerTakeDamageFromWater; ++ } ++ // Purpur end - Toggle for water sensitive mob damage ++ ++ // Purpur start - Mobs always drop experience ++ @Override ++ protected boolean isAlwaysExperienceDropper() { ++ return this.level().purpurConfig.villagerAlwaysDropExp; ++ } ++ // Purpur end - Mobs always drop experience + @Override public Brain getBrain() { - return (Brain) super.getBrain(); // CraftBukkit - decompile error -@@ -190,7 +279,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - brain.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5F)); - } else { - brain.setSchedule(Schedule.VILLAGER_DEFAULT); -- brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); -+ brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F, this.level().purpurConfig.villagerClericsFarmWarts), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); // Purpur + return (Brain)super.getBrain(); +@@ -226,7 +325,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + villagerBrain.setSchedule(Schedule.VILLAGER_DEFAULT); + villagerBrain.addActivityWithConditions( + Activity.WORK, +- VillagerGoalPackages.getWorkPackage(profession, 0.5F), ++ VillagerGoalPackages.getWorkPackage(profession, 0.5F, this.level().purpurConfig.villagerClericsFarmWarts), // Purpur - Option for Villager Clerics to farm Nether Wart + ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT)) + ); } - - brain.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(villagerprofession, 0.5F)); -@@ -217,7 +306,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -258,7 +357,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5D); -+ return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5D).add(Attributes.TEMPT_RANGE, 10.0D); // Purpur - add TEMPT_RANGE +- return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5); ++ return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.TEMPT_RANGE, 10.0D); // Purpur - Villagers follow emerald blocks } public boolean assignProfessionWhenSpawned() { -@@ -251,10 +340,18 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -290,10 +389,18 @@ public class Villager extends AbstractVillager implements ReputationEventHandler // Paper start - EAR 2 - this.customServerAiStep(world, false); + this.customServerAiStep(level, false); } -- protected void customServerAiStep(ServerLevel world, final boolean inactive) { -+ protected void customServerAiStep(ServerLevel world, boolean inactive) { // Purpur - not final +- protected void customServerAiStep(ServerLevel level, final boolean inactive) { ++ protected void customServerAiStep(ServerLevel level, boolean inactive) { // Purpur - Lobotomize stuck villagers - not final // Paper end - EAR 2 -+ // Purpur start ++ // Purpur start - Lobotomize stuck villagers + if (this.level().purpurConfig.villagerLobotomizeEnabled) { + // treat as inactive if lobotomized + inactive = inactive || checkLobotomized(); + } else { + this.isLobotomized = false; + } -+ // Purpur end ++ // Purpur end - Lobotomize stuck villagers // Pufferfish start - if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) { -+ if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) { // Purpur - this.getBrain().tick(world, this); // Paper ++ if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) { // Purpur - Ridables + this.getBrain().tick(level, this); // Paper - EAR 2 } // Pufferfish end -@@ -313,7 +410,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { - if (this.isBaby()) { - this.setUnhappy(); -- return InteractionResult.SUCCESS; -+ return tryRide(player, hand, InteractionResult.SUCCESS); // Purpur - } else { - if (!this.level().isClientSide) { - boolean flag = this.getOffers().isEmpty(); -@@ -327,9 +424,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - if (flag) { -- return InteractionResult.CONSUME; -+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - } - -+ if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur -+ if (this.level().purpurConfig.villagerAllowTrading) // Purpur - this.startTrading(player); +@@ -351,7 +458,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + return super.mobInteract(player, hand); + } else if (this.isBaby()) { + this.setUnhappy(); +- return InteractionResult.SUCCESS; ++ return tryRide(player, hand, InteractionResult.SUCCESS); // Purpur - Ridables + } else { + if (!this.level().isClientSide) { + boolean isEmpty = this.getOffers().isEmpty(); +@@ -364,9 +471,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } -@@ -494,7 +593,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); + if (isEmpty) { +- return InteractionResult.CONSUME; ++ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables + } -- merchantrecipe.updateDemand(); -+ merchantrecipe.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur ++ if (level().purpurConfig.villagerRidable && itemInHand.isEmpty()) return tryRide(player, hand); // Purpur - Ridables ++ ++ if (this.level().purpurConfig.villagerAllowTrading) // Purpur - Add config for villager trading + this.startTrading(player); + } + +@@ -505,7 +615,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + private void updateDemand() { + for (MerchantOffer merchantOffer : this.getOffers()) { +- merchantOffer.updateDemand(); ++ merchantOffer.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur - Configurable minimum demand for trades } - } -@@ -727,7 +826,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + +@@ -709,7 +819,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public boolean canBreed() { - return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; -+ return this.level().purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur ++ return this.level().purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur - Configurable villager breeding } private boolean hungry() { -@@ -906,6 +1005,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -878,7 +988,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } public boolean hasFarmSeeds() { - return this.getInventory().hasAnyMatching((itemstack) -> { -+ // Purpur start -+ if (this.level().purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) { -+ return itemstack.is(Items.NETHER_WART); -+ } -+ // Purpur end - return itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS); - }); - } -@@ -963,6 +1067,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +- return this.getInventory().hasAnyMatching(itemStack -> itemStack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)); ++ return this.getInventory().hasAnyMatching(itemStack -> this.level().purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC ? itemStack.is(Items.NETHER_WART) : itemStack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)); // Purpur - Option for Villager Clerics to farm Nether Wart } - public void spawnGolemIfNeeded(ServerLevel world, long time, int requiredCount) { -+ if (world.purpurConfig.villagerSpawnIronGolemRadius > 0 && world.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(world.purpurConfig.villagerSpawnIronGolemRadius)).size() > world.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur - if (this.wantsToSpawnGolem(time)) { - AABB axisalignedbb = this.getBoundingBox().inflate(10.0D, 10.0D, 10.0D); - List list = world.getEntitiesOfClass(Villager.class, axisalignedbb); -@@ -1027,6 +1132,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override +@@ -930,6 +1040,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + + public void spawnGolemIfNeeded(ServerLevel serverLevel, long gameTime, int minVillagerAmount) { ++ if (serverLevel.purpurConfig.villagerSpawnIronGolemRadius > 0 && serverLevel.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(serverLevel.purpurConfig.villagerSpawnIronGolemRadius)).size() > serverLevel.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur - Implement configurable search radius for villagers to spawn iron golems + if (this.wantsToSpawnGolem(gameTime)) { + AABB aabb = this.getBoundingBox().inflate(10.0, 10.0, 10.0); + List entitiesOfClass = serverLevel.getEntitiesOfClass(Villager.class, aabb); +@@ -1003,6 +1114,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public void startSleeping(BlockPos pos) { -+ // Purpur start ++ // Purpur start - Option for beds to explode on villager sleep + if (level().purpurConfig.bedExplodeOnVillagerSleep && this.level().getBlockState(pos).getBlock() instanceof net.minecraft.world.level.block.BedBlock) { + this.level().explode(null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (float) this.level().purpurConfig.bedExplosionPower, this.level().purpurConfig.bedExplosionFire, this.level().purpurConfig.bedExplosionEffect); + return; + } -+ // Purpur end ++ // Purpur end - Option for beds to explode on villager sleep super.startSleeping(pos); - this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error + this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); this.brain.eraseMemory(MemoryModuleType.WALK_TARGET); -diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -index 8734ab1bd8299bbf43906d81a349c2a13e0981a7..3ca83269311cbc18c9ef3ce62cff6a2d4dc0a683 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -+++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java +diff --git a/net/minecraft/world/entity/npc/VillagerProfession.java b/net/minecraft/world/entity/npc/VillagerProfession.java +index 1ec8ad124c76483d11057eb97fcfb9aebee0c301..f783d058a080408d572938d36ba031f33a08e9af 100644 +--- a/net/minecraft/world/entity/npc/VillagerProfession.java ++++ b/net/minecraft/world/entity/npc/VillagerProfession.java @@ -31,7 +31,7 @@ public record VillagerProfession( public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER); public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER); public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER); - public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC); -+ public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur ++ public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur - Option for Villager Clerics to farm Nether Wart public static final VillagerProfession FARMER = register( "farmer", PoiTypes.FARMER, -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 1e77cce428d9e53142aaa2cf780b7f862d536eca..42c91e52060fad4a7a598f9e9ef88fd0e0ff8475 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -72,6 +72,50 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - //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. +diff --git a/net/minecraft/world/entity/npc/WanderingTrader.java b/net/minecraft/world/entity/npc/WanderingTrader.java +index 6655d06e2011e20e7346dfe57527795269094d8a..c3fbcc7956a64d49466874776f257ba27f55f2a4 100644 +--- a/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -69,6 +69,58 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + super(entityType, level); } -+ // Purpur - start ++ // Purpur start - Allow leashing villagers ++ @Override ++ public boolean canBeLeashed() { ++ return level().purpurConfig.wanderingTraderCanBeLeashed; ++ } ++ // Purpur end - Allow leashing villagers ++ ++ // Purpur - start - Ridables + @Override + public boolean isRidable() { + return level().purpurConfig.wanderingTraderRidable; @@ -12347,127 +12501,127 @@ index 1e77cce428d9e53142aaa2cf780b7f862d536eca..42c91e52060fad4a7a598f9e9ef88fd0 + public boolean isControllable() { + return level().purpurConfig.wanderingTraderControllable; + } ++ // Purpur end - Ridables + -+ @Override -+ public boolean canBeLeashed() { -+ return level().purpurConfig.wanderingTraderCanBeLeashed; -+ } -+ // Purpur end -+ ++ // Purpur start - Configurable entity base attributes + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth); -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.wanderingTraderTemptRange); // Purpur ++ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.wanderingTraderTemptRange); // Purpur - Villagers follow emerald blocks + } ++ // Purpur end - Configurable entity base attributes + -+ // Purpur start ++ // Purpur start - Villagers follow emerald blocks + public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() { + return Mob.createMobAttributes().add(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE, 10.0D); + } -+ // Purpur end ++ // Purpur end - Villagers follow emerald blocks + ++ // Purpur start - Toggle for water sensitive mob damage + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.wanderingTraderTakeDamageFromWater; + } ++ // Purpur end - Toggle for water sensitive mob damage + ++ // Purpur start - Mobs always drop experience + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.wanderingTraderAlwaysDropExp; + } ++ // Purpur end - Mobs always drop experience + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); -@@ -79,7 +123,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - 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.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API -+ return level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API // Purpur - })); +@@ -89,7 +141,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + this, + new ItemStack(Items.MILK_BUCKET), + SoundEvents.WANDERING_TRADER_REAPPEARED, +- wanderingTrader -> this.canDrinkMilk && this.level().isDay() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API ++ wanderingTrader -> level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isDay() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API // // Purpur - Milk Keeps Beneficial Effects + ) + ); this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); -@@ -92,6 +136,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - this.goalSelector.addGoal(1, new PanicGoal(this, 0.5D)); +@@ -103,6 +155,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + this.goalSelector.addGoal(1, new PanicGoal(this, 0.5)); this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this)); - this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0D, 0.35D)); -+ if (level().purpurConfig.wanderingTraderFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35D)); - this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35D)); + this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0, 0.35)); ++ if (level().purpurConfig.wanderingTraderFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - Villagers follow emerald blocks + this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35)); + this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35)); this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0F, 1.0F)); -@@ -120,11 +165,13 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill +@@ -130,11 +183,14 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill if (!this.level().isClientSide) { if (this.getOffers().isEmpty()) { - return InteractionResult.CONSUME; -+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur ++ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables } -- -- this.setTradingPlayer(player); -- this.openTradingScreen(player, this.getDisplayName(), 1); -+ if (level().purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur -+ if (this.level().purpurConfig.wanderingTraderAllowTrading) { // Purpur -+ this.setTradingPlayer(player); -+ this.openTradingScreen(player, this.getDisplayName(), 1); -+ } // Purpur ++ if (level().purpurConfig.wanderingTraderRidable && itemInHand.isEmpty()) return tryRide(player, hand); // Purpur - Ridables + ++ if (this.level().purpurConfig.wanderingTraderAllowTrading) { // Purpur - Add config for villager trading + this.setTradingPlayer(player); + this.openTradingScreen(player, this.getDisplayName(), 1); ++ } // Purpur - Add config for villager trading } return InteractionResult.SUCCESS; -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 a728dcbf956f108f01c966c7531449a506a14a87..4c1378132201c1e5d1bc01f8c0cbba91629bcffa 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -160,7 +160,17 @@ public class WanderingTraderSpawner implements CustomSpawner { - int k = pos.getX() + this.random.nextInt(range * 2) - range; - int l = pos.getZ() + this.random.nextInt(range * 2) - range; - int i1 = world.getHeight(Heightmap.Types.WORLD_SURFACE, k, l); -- BlockPos blockposition2 = new BlockPos(k, i1, l); -+ // Purpur start - allow traders to spawn below nether roof -+ BlockPos.MutableBlockPos blockposition2 = new BlockPos.MutableBlockPos(k, i1, l); -+ if (world.dimensionType().hasCeiling()) { +diff --git a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index ef2afb17a22a703470e13d12c989a685e72f0ab8..80a01b8c6dfb0f3bcc6872cdf38b48f7a9ce4b8b 100644 +--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -147,7 +147,17 @@ public class WanderingTraderSpawner implements CustomSpawner { + int i1 = pos.getX() + this.random.nextInt(maxDistance * 2) - maxDistance; + int i2 = pos.getZ() + this.random.nextInt(maxDistance * 2) - maxDistance; + int height = level.getHeight(Heightmap.Types.WORLD_SURFACE, i1, i2); +- BlockPos blockPos1 = new BlockPos(i1, height, i2); ++ // Purpur start - Allow toggling special MobSpawners per world - allow traders to spawn below nether roof ++ BlockPos.MutableBlockPos blockPos1 = new BlockPos.MutableBlockPos(i1, height, i2); ++ if (level.dimensionType().hasCeiling()) { + do { -+ blockposition2.relative(net.minecraft.core.Direction.DOWN); -+ } while (!world.getBlockState(blockposition2).isAir()); ++ blockPos1.relative(net.minecraft.core.Direction.DOWN); ++ } while (!level.getBlockState(blockPos1).isAir()); + do { -+ blockposition2.relative(net.minecraft.core.Direction.DOWN); -+ } while (world.getBlockState(blockposition2).isAir() && blockposition2.getY() > 0); ++ blockPos1.relative(net.minecraft.core.Direction.DOWN); ++ } while (level.getBlockState(blockPos1).isAir() && blockPos1.getY() > 0); + } -+ // Purpur end - - if (spawnplacementtype.isSpawnPositionOk(world, blockposition2, EntityType.WANDERING_TRADER)) { - blockposition1 = blockposition2; -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 7187652d038a5ea7b3555f5ad6f3671263947375..3dc166fd669cecded3d40ef8722bed400f611d03 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -198,11 +198,21 @@ public abstract class Player extends LivingEntity { ++ // Purpur end - Allow toggling special MobSpawners per world + if (placementType.isSpawnPositionOk(level, blockPos1, EntityType.WANDERING_TRADER)) { + blockPos = blockPos1; + break; +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index cf1e036b068531716ab99ecdcda45c17cdfe1c22..35afbef30b9f3b7767db3fe3d552b93fa542343f 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -201,11 +201,22 @@ public abstract class Player extends LivingEntity { private int currentImpulseContextResetGraceTime; public boolean affectsSpawning = true; // Paper - Affects Spawning API public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage -+ public int sixRowEnderchestSlotCount = -1; // Purpur -+ public int burpDelay = 0; // Purpur -+ public boolean canPortalInstant = false; // Purpur ++ public int burpDelay = 0; // Purpur - Burp delay ++ public boolean canPortalInstant = false; // Purpur - Add portal permission bypass ++ public int sixRowEnderchestSlotCount = -1; // Purpur - Barrels and enderchests 6 rows // CraftBukkit start public boolean fauxSleeping; public int oldLevel = -1; -+ public void setAfk(boolean afk) { -+ } ++ // Purpur start - AFK API ++ public abstract void setAfk(boolean afk); + + public boolean isAfk() { + return false; + } ++ // Purpur end - AFK API + @Override - public CraftHumanEntity getBukkitEntity() { - return (CraftHumanEntity) super.getBukkitEntity(); -@@ -211,6 +221,19 @@ public abstract class Player extends LivingEntity { + public org.bukkit.craftbukkit.entity.CraftHumanEntity getBukkitEntity() { + return (org.bukkit.craftbukkit.entity.CraftHumanEntity) super.getBukkitEntity(); +@@ -214,6 +225,19 @@ public abstract class Player extends LivingEntity { public final int sendAllPlayerInfoBucketIndex; // Gale - Purpur - spread out sending all player info -+ // Purpur start ++ // Purpur start - Ridables + public abstract void resetLastActionTime(); + + @Override @@ -12478,29 +12632,29 @@ index 7187652d038a5ea7b3555f5ad6f3671263947375..3dc166fd669cecded3d40ef8722bed40 + } + return false; + } -+ // Purpur end ++ // Purpur end - Ridables + - public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) { - super(EntityType.PLAYER, world); - this.lastItemInMainHand = ItemStack.EMPTY; -@@ -256,6 +279,12 @@ public abstract class Player extends LivingEntity { + public Player(Level level, BlockPos pos, float yRot, GameProfile gameProfile) { + super(EntityType.PLAYER, level); + this.setUUID(gameProfile.getId()); +@@ -266,6 +290,12 @@ public abstract class Player extends LivingEntity { @Override public void tick() { -+ // Purpur start ++ // Purpur start - Burp delay + if (this.burpDelay > 0 && --this.burpDelay == 0) { + this.level().playSound(null, getX(), getY(), getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 1.0F, this.level().random.nextFloat() * 0.1F + 0.9F); + } -+ // Purpur end ++ // Purpur end - Burp delay + this.noPhysics = this.isSpectator(); - if (this.isSpectator()) { + if (this.isSpectator() || this.isPassenger()) { this.setOnGround(false); -@@ -340,6 +369,17 @@ public abstract class Player extends LivingEntity { +@@ -348,6 +378,17 @@ public abstract class Player extends LivingEntity { this.turtleHelmetTick(); } -+ // Purpur start ++ // Purpur start - Full netherite armor grants fire resistance + if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) { + if (this.getItemBySlot(EquipmentSlot.HEAD).is(Items.NETHERITE_HELMET) + && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE) @@ -12509,45 +12663,36 @@ index 7187652d038a5ea7b3555f5ad6f3671263947375..3dc166fd669cecded3d40ef8722bed40 + this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level().purpurConfig.playerNetheriteFireResistanceDuration, this.level().purpurConfig.playerNetheriteFireResistanceAmplifier, this.level().purpurConfig.playerNetheriteFireResistanceAmbient, this.level().purpurConfig.playerNetheriteFireResistanceShowParticles, this.level().purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR); + } + } -+ // Purpur end ++ // Purpur end - Full netherite armor grants fire resistance + this.cooldowns.tick(); this.updatePlayerPose(); if (this.currentImpulseContextResetGraceTime > 0) { -@@ -630,7 +670,7 @@ public abstract class Player extends LivingEntity { - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); +@@ -618,7 +659,7 @@ public abstract class Player extends LivingEntity { + List list = Lists.newArrayList(); + for (Entity entity : entities) { - if (entity.getType() == EntityType.EXPERIENCE_ORB) { -+ if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur - list1.add(entity); ++ if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur - Configurable player pickup exp delay + list.add(entity); } else if (!entity.isRemoved()) { this.touch(entity); -@@ -1282,7 +1322,7 @@ public abstract class Player extends LivingEntity { - flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits - if (flag2) { - damagesource = damagesource.critical(true); // Paper start - critical damage API -- f *= 1.5F; -+ f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur - } +@@ -1277,7 +1318,7 @@ public abstract class Player extends LivingEntity { + flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits + if (flag2) { + damageSource = damageSource.critical(true); // Paper start - critical damage API +- f *= 1.5F; ++ f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur - Add config change multiplier critical damage value + } - float f3 = f + f1; -@@ -1648,7 +1688,7 @@ public abstract class Player extends LivingEntity { - } + float f2 = f + f1; +@@ -1890,7 +1931,23 @@ public abstract class Player extends LivingEntity { @Override -- protected boolean canGlide() { -+ public boolean canGlide() { // Purpur - return !this.abilities.flying && super.canGlide(); - } - -@@ -1908,7 +1948,23 @@ public abstract class Player extends LivingEntity { - - @Override - protected int getBaseExperienceReward(ServerLevel world) { -- return !world.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0; -+ // Purpur start -+ if (!world.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) { + protected int getBaseExperienceReward(ServerLevel level) { +- return !level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0; ++ // Purpur start - Add player death exp control options ++ if (!level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) { + int toDrop; + try { + toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " + @@ -12562,37 +12707,37 @@ index 7187652d038a5ea7b3555f5ad6f3671263947375..3dc166fd669cecded3d40ef8722bed40 + } else { + return 0; + } -+ // Purpur end ++ // Purpur end - Add player death exp control options } @Override -@@ -1986,6 +2042,13 @@ public abstract class Player extends LivingEntity { +@@ -1974,6 +2031,13 @@ public abstract class Player extends LivingEntity { return slot != EquipmentSlot.BODY; } -+ // Purpur start ++ // Purpur start - Player ridable in water option + @Override + public boolean dismountsUnderwater() { + return !level().purpurConfig.playerRidableInWater; + } -+ // Purpur end ++ // Purpur end - Player ridable in water option + - public boolean setEntityOnShoulder(CompoundTag entityNbt) { - if (!this.isPassenger() && this.onGround() && !this.isInWater() && !this.isInPowderSnow) { - if (this.getShoulderEntityLeft().isEmpty()) { -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 6e313a45356ed2b043ef626c7ca191b294acaf70..5a6dc4366f8fb9f5d5df44c29597a1e174d95569 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -80,6 +80,7 @@ public abstract class AbstractArrow extends Projectile { - public ItemStack pickupItemStack; + public boolean setEntityOnShoulder(CompoundTag entityCompound) { + if (this.isPassenger() || !this.onGround() || this.isInWater() || this.isInPowderSnow) { + return false; +diff --git a/net/minecraft/world/entity/projectile/AbstractArrow.java b/net/minecraft/world/entity/projectile/AbstractArrow.java +index d206ac2b9cade292b0d69e9aeb0f81227ec0b49e..f9b538f4cd831aa5a22bb778c2bf3cd97a27f9dd 100644 +--- a/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -74,6 +74,7 @@ public abstract class AbstractArrow extends Projectile { + public ItemStack pickupItemStack = this.getDefaultPickupItem(); // Paper - private -> public @Nullable - public ItemStack firedFromWeapon; + public ItemStack firedFromWeapon = null; // Paper - private -> public + public net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments = net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY; // Purpur - Add an option to fix MC-3304 projectile looting - // Spigot Start - @Override -@@ -623,6 +624,12 @@ public abstract class AbstractArrow extends Projectile { + protected AbstractArrow(EntityType entityType, Level level) { + super(entityType, level); +@@ -560,6 +561,12 @@ public abstract class AbstractArrow extends Projectile { return this.firedFromWeapon; } @@ -12605,227 +12750,226 @@ index 6e313a45356ed2b043ef626c7ca191b294acaf70..5a6dc4366f8fb9f5d5df44c29597a1e1 protected SoundEvent getDefaultHitGroundSoundEvent() { return SoundEvents.ARROW_HIT; } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -index 2f00676f62478897ae4931ea06e047567c407535..55ea7f82fac9a3de6d7e0725a9b6ea08088bc85c 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -@@ -23,13 +23,13 @@ public class LargeFireball extends Fireball { +diff --git a/net/minecraft/world/entity/projectile/LargeFireball.java b/net/minecraft/world/entity/projectile/LargeFireball.java +index 4a752ace041228f095af7b1b4878a03c5ed2381f..3e8b5d042eddb817dee2504ff9aa263f6195b1c7 100644 +--- a/net/minecraft/world/entity/projectile/LargeFireball.java ++++ b/net/minecraft/world/entity/projectile/LargeFireball.java +@@ -18,20 +18,20 @@ public class LargeFireball extends Fireball { - public LargeFireball(EntityType type, Level world) { - super(type, world); -- this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit -+ this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // CraftBukkit // Purpur + public LargeFireball(EntityType entityType, Level level) { + super(entityType, level); +- this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit ++ this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected } - public LargeFireball(Level world, LivingEntity owner, Vec3 velocity, int explosionPower) { - super(EntityType.FIREBALL, owner, velocity, world); + public LargeFireball(Level level, LivingEntity owner, Vec3 movement, int explosionPower) { + super(EntityType.FIREBALL, owner, movement, level); this.explosionPower = explosionPower; -- this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit -+ this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // CraftBukkit // Purpur +- this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit ++ this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected } @Override -@@ -38,7 +38,7 @@ public class LargeFireball extends Fireball { - Level world = this.level(); - - if (world instanceof ServerLevel worldserver) { -- boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ boolean flag = worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - + protected void onHit(HitResult result) { + super.onHit(result); + if (this.level() instanceof ServerLevel serverLevel) { +- boolean _boolean = serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); ++ boolean _boolean = serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected // CraftBukkit start - fire ExplosionPrimeEvent - ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -index 958ea103cc80da7366cc33dc385b76d4f5c809f2..0b7f27a6cc6be58fa5b60002059c9fbb3b1b7b67 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java + org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); + this.level().getCraftServer().getPluginManager().callEvent(event); +diff --git a/net/minecraft/world/entity/projectile/LlamaSpit.java b/net/minecraft/world/entity/projectile/LlamaSpit.java +index 4880db97135d54fa72f64c108b2bd4ded096438b..bc102b049047d6e2a1d29e10f92cdf5ae2c140bd 100644 +--- a/net/minecraft/world/entity/projectile/LlamaSpit.java ++++ b/net/minecraft/world/entity/projectile/LlamaSpit.java @@ -33,6 +33,12 @@ public class LlamaSpit extends Projectile { - this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F)); + ); } -+ // Purpur start -+ public void super_tick() { ++ // Purpur start - Ridables ++ public void projectileTick() { + super.tick(); + } -+ // Purpur end ++ // Purpur end - Ridables + @Override protected double getDefaultGravity() { - return 0.06D; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 507a7f2116b020a5af4b8fff15b73dba9904874f..a7fe724fd2aec7a72781e7b3ab74ff317cec8fbf 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -519,7 +519,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - public boolean mayInteract(ServerLevel world, BlockPos pos) { - Entity entity = this.getOwner(); - -- return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.purpurConfig.projectilesBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + return 0.06; +diff --git a/net/minecraft/world/entity/projectile/Projectile.java b/net/minecraft/world/entity/projectile/Projectile.java +index 4e9ca9cc3600dc8bbd4f386cf4e4c426d1664904..bd93ee97982038789114f17ee369208fc6413796 100644 +--- a/net/minecraft/world/entity/projectile/Projectile.java ++++ b/net/minecraft/world/entity/projectile/Projectile.java +@@ -493,7 +493,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + @Override + public boolean mayInteract(ServerLevel level, BlockPos pos) { + Entity owner = this.getOwner(); +- return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); ++ return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.purpurConfig.projectilesBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected } - public boolean mayBreak(ServerLevel world) { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -index bb159ea4baf208aab6d6fcfbbddacd5b089b55c8..588b07ec4501924a49264183b414a7fd64bb6550 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -@@ -30,7 +30,7 @@ public class SmallFireball extends Fireball { - super(EntityType.SMALL_FIREBALL, owner, velocity, world); + public boolean mayBreak(ServerLevel level) { +diff --git a/net/minecraft/world/entity/projectile/SmallFireball.java b/net/minecraft/world/entity/projectile/SmallFireball.java +index 8c84cea43fc0e42a576004663670977eac99f1a6..808aa5dcb27c87b6ba5c1eee639486067447e370 100644 +--- a/net/minecraft/world/entity/projectile/SmallFireball.java ++++ b/net/minecraft/world/entity/projectile/SmallFireball.java +@@ -25,7 +25,7 @@ public class SmallFireball extends Fireball { + super(EntityType.SMALL_FIREBALL, owner, movement, level); // CraftBukkit start if (this.getOwner() != null && this.getOwner() instanceof Mob) { -- this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // Purpur +- this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); ++ this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.purpurConfig.fireballsBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected } // CraftBukkit end } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -index 70961e151666a0ecf5b791853f4581eaebbdcc8b..0db58e7d63a5c1b43a2224c247979f23a1d3f899 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -@@ -58,11 +58,41 @@ public class Snowball extends ThrowableItemProjectile { - protected void onHitEntity(EntityHitResult entityHitResult) { - super.onHitEntity(entityHitResult); - Entity entity = entityHitResult.getEntity(); +diff --git a/net/minecraft/world/entity/projectile/Snowball.java b/net/minecraft/world/entity/projectile/Snowball.java +index c57bbdc13221d2ce349f3f1d894193f80ff1e24b..1d399532c67c213c95c06837b0c7855384f1a25c 100644 +--- a/net/minecraft/world/entity/projectile/Snowball.java ++++ b/net/minecraft/world/entity/projectile/Snowball.java +@@ -52,10 +52,40 @@ public class Snowball extends ThrowableItemProjectile { + protected void onHitEntity(EntityHitResult result) { + super.onHitEntity(result); + Entity entity = result.getEntity(); - int i = entity instanceof Blaze ? 3 : 0; -+ int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - - entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float) i); ++ int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - Add configurable snowball damage + entity.hurt(this.damageSources().thrown(this, this.getOwner()), i); } -+ // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire ++ // Purpur start - options to extinguish fire blocks with snowballs - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire + @Override + protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) { + super.onHitBlock(blockHitResult); + + if (!this.level().isClientSide) { -+ net.minecraft.core.BlockPos blockposition = blockHitResult.getBlockPos(); -+ net.minecraft.core.BlockPos blockposition1 = blockposition.relative(blockHitResult.getDirection()); ++ net.minecraft.core.BlockPos pos = blockHitResult.getBlockPos(); ++ net.minecraft.core.BlockPos relativePos = pos.relative(blockHitResult.getDirection()); + -+ net.minecraft.world.level.block.state.BlockState iblockdata = this.level().getBlockState(blockposition); ++ net.minecraft.world.level.block.state.BlockState blockState = this.level().getBlockState(pos); + -+ if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(blockposition1).is(net.minecraft.world.level.block.Blocks.FIRE)) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { -+ this.level().removeBlock(blockposition1, false); ++ if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(relativePos).is(net.minecraft.world.level.block.Blocks.FIRE)) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, relativePos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { ++ this.level().removeBlock(relativePos, false); + } -+ } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(iblockdata)) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) { -+ net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, iblockdata, this.level(), blockposition); ++ } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(blockState)) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) { ++ net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, blockState, this.level(), pos); + } -+ } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(iblockdata)) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) { -+ this.level().levelEvent(null, 1009, blockposition, 0); -+ net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), blockposition, iblockdata); -+ this.level().setBlockAndUpdate(blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false)); ++ } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(blockState)) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) { ++ this.level().levelEvent(null, 1009, pos, 0); ++ net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), pos, blockState); ++ this.level().setBlockAndUpdate(pos, blockState.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false)); + } + } + } + } -+ // Purpur end ++ // Purpur end - options to extinguish fire blocks with snowballs + @Override - protected void onHit(HitResult hitResult) { - super.onHit(hitResult); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -index bd2684528157f928460f2143dd71a48e11983123..0720df603b4f89dd6aa346091b13033ad5d62907 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -@@ -152,10 +152,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - return; + protected void onHit(HitResult result) { + super.onHit(result); +diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +index 128bf1555b996c454e753b1ed004cfeae4b0436f..1d240ebbf81154d361cc5449d0f43ea467bfb097 100644 +--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java ++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +@@ -133,9 +133,10 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + return; + } + // CraftBukkit end +- if (this.random.nextFloat() < 0.05F && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { ++ if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Configurable Ender Pearl RNG + Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED); + if (endermite != null) { ++ endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API + endermite.moveTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot()); + serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); } - // CraftBukkit end -- if (this.random.nextFloat() < 0.05F && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { -+ if (this.random.nextFloat() < worldserver.purpurConfig.enderPearlEndermiteChance && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(worldserver, EntitySpawnReason.TRIGGERED); +@@ -155,7 +156,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + if (serverPlayer1 != null) { + serverPlayer1.resetFallDistance(); + serverPlayer1.resetCurrentImpulseContext(); +- serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API ++ serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage + } - if (entityendermite != null) { -+ entityendermite.setPlayerSpawned(true); // Purpur - entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); - worldserver.addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); - } -@@ -170,7 +171,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - if (entityplayer1 != null) { - entityplayer1.resetFallDistance(); - entityplayer1.resetCurrentImpulseContext(); -- entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API -+ entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - } + this.playSound(serverLevel, vec3); +diff --git a/net/minecraft/world/entity/projectile/ThrownTrident.java b/net/minecraft/world/entity/projectile/ThrownTrident.java +index 2c66f788759330fba412ccb2187945b6ca0cacb6..251e9a368800afab1a47bb9162077b2e76f4397f 100644 +--- a/net/minecraft/world/entity/projectile/ThrownTrident.java ++++ b/net/minecraft/world/entity/projectile/ThrownTrident.java +@@ -64,7 +64,7 @@ public class ThrownTrident extends AbstractArrow { - this.playSound(worldserver, vec3d); -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 322733266fdca8ce43434a8ffea304c51794bcbb..489c26423a7f5bc9da45d247de57ec989cc74119 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -69,7 +69,7 @@ public class ThrownTrident extends AbstractArrow { - Entity entity = this.getOwner(); - byte b0 = (Byte) this.entityData.get(ThrownTrident.ID_LOYALTY); - -- if (b0 > 0 && (this.dealtDamage || this.isNoPhysics()) && entity != null) { -+ if (b0 > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && entity != null) { // Purpur + Entity owner = this.getOwner(); + int i = this.entityData.get(ID_LOYALTY); +- if (i > 0 && (this.dealtDamage || this.isNoPhysics()) && owner != null) { ++ if (i > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && owner != null) { // Purpur - Add option to allow loyalty on tridents to work in the void if (!this.isAcceptibleReturnOwner()) { - Level world = this.level(); - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -index 4c47b30867e30d84908abf93dbefc252bc8c3453..e63b408594b5d2673148e39c1deafc8510537bee 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -@@ -103,7 +103,7 @@ public class WitherSkull extends AbstractHurtingProjectile { + if (this.level() instanceof ServerLevel serverLevel && this.pickup == AbstractArrow.Pickup.ALLOWED) { + this.spawnAtLocation(serverLevel, this.getPickupItem(), 0.1F); +diff --git a/net/minecraft/world/entity/projectile/WitherSkull.java b/net/minecraft/world/entity/projectile/WitherSkull.java +index a83839fce264429e2a8fd3b19cd2d0a6d88585e0..9af37bd40649f602d700fc7b683c646ae9189eb9 100644 +--- a/net/minecraft/world/entity/projectile/WitherSkull.java ++++ b/net/minecraft/world/entity/projectile/WitherSkull.java +@@ -92,7 +92,7 @@ public class WitherSkull extends AbstractHurtingProjectile { + super.onHit(result); if (!this.level().isClientSide) { // CraftBukkit start - // this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB); -- ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false); -+ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.level().purpurConfig.witherExplosionRadius, false); // Purpur +- org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false); ++ org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), this.level().purpurConfig.witherExplosionRadius, false); // Purpur - Config for wither explosion radius this.level().getCraftServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { -@@ -115,6 +115,19 @@ public class WitherSkull extends AbstractHurtingProjectile { - +@@ -103,6 +103,21 @@ public class WitherSkull extends AbstractHurtingProjectile { + } } -+ // Purpur start ++ // Purpur start - Add canSaveToDisk to Entity ++ @Override ++ public boolean canSaveToDisk() { ++ return false; ++ } ++ // Purpur end - Add canSaveToDisk to Entity ++ ++ // Purpur start - Ridables + @Override + public boolean canHitEntity(Entity target) { + // do not hit rider + return target != this.getRider() && super.canHitEntity(target); + } -+ -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ // Purpur end ++ // Purpur end - Ridables + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { - builder.define(WitherSkull.DATA_DANGEROUS, false); -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 ab132041982df2a701e4baea8195873f31b4a5fb..722c1660cf6b93d0f9c05cafe587b1834c5c3a22 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -345,7 +345,7 @@ public abstract class Raider extends PatrollingMonster { + builder.define(DATA_DANGEROUS, false); +diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java +index 8270d76a753bfd26a4c8ef6610bee5c24ee59cfe..c06b589e669b055a26f662df60070d5908256220 100644 +--- a/net/minecraft/world/entity/raid/Raider.java ++++ b/net/minecraft/world/entity/raid/Raider.java +@@ -399,7 +399,7 @@ public abstract class Raider extends PatrollingMonster { } private boolean cannotPickUpBanner() { - if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items -+ if ((!this.mob.level().purpurConfig.pillagerBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur ++ if (!this.mob.level().purpurConfig.pillagerBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur - Add mobGriefing bypass to everything affected if (!this.mob.hasActiveRaid()) { return true; } else if (this.mob.getCurrentRaid().isOver()) { -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java -index 439d61d8689fabe940006b9b317a6810175dccfb..6b30941a84054efb5fcccb5d9e6c80d713a23889 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raids.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java -@@ -26,6 +26,7 @@ import net.minecraft.world.phys.Vec3; - public class Raids extends SavedData { +diff --git a/net/minecraft/world/entity/raid/Raids.java b/net/minecraft/world/entity/raid/Raids.java +index 34eb038725d1577f1a2d7c35c897b1270eac5749..497915338d406815d6b54c661fbbf4024bdca7ea 100644 +--- a/net/minecraft/world/entity/raid/Raids.java ++++ b/net/minecraft/world/entity/raid/Raids.java +@@ -25,6 +25,7 @@ import net.minecraft.world.phys.Vec3; + public class Raids extends SavedData { private static final String RAID_FILE_ID = "raids"; -+ public final Map playerCooldowns = Maps.newHashMap(); ++ public final Map playerCooldowns = Maps.newHashMap(); // Purpur - Raid cooldown setting public final Map raidMap = Maps.newHashMap(); private final ServerLevel level; private int nextAvailableID; -@@ -51,6 +52,17 @@ public class Raids extends SavedData { +@@ -46,6 +47,17 @@ public class Raids extends SavedData { public void tick() { - ++this.tick; -+ // Purpur start + this.tick++; ++ // Purpur start - Raid cooldown setting + if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) { + com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> { + if (i < 1) { @@ -12835,100 +12979,100 @@ index 439d61d8689fabe940006b9b317a6810175dccfb..6b30941a84054efb5fcccb5d9e6c80d7 + } + }); + } -+ // Purpur end ++ // Purpur end - Raid cooldown setting Iterator iterator = this.raidMap.values().iterator(); while (iterator.hasNext()) { -@@ -122,11 +134,13 @@ public class Raids extends SavedData { +@@ -119,11 +131,13 @@ public class Raids extends SavedData { */ if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished -+ if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur ++ if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur - Raid cooldown setting // CraftBukkit start if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) { player.removeEffect(net.minecraft.world.effect.MobEffects.RAID_OMEN); return null; } -+ if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur ++ if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur - Raid cooldown setting if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) { this.raidMap.put(raid.getId(), raid); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java -index 1fdbef16cd29c8fc74578ac3328f985eca61088d..56c265940208bc94f531a5af94f564b59f35ebf3 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java -@@ -499,6 +499,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { - - if (f > 0.0F) { - this.landFriction = f; -+ if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur +diff --git a/net/minecraft/world/entity/vehicle/AbstractBoat.java b/net/minecraft/world/entity/vehicle/AbstractBoat.java +index 467783b764eec361b190566cb3d9050bb0821864..b1b312e45ed4514eaa6fb3941af64b641220c5bd 100644 +--- a/net/minecraft/world/entity/vehicle/AbstractBoat.java ++++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java +@@ -483,6 +483,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { + float groundFriction = this.getGroundFriction(); + if (groundFriction > 0.0F) { + this.landFriction = groundFriction; ++ if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur - Add option for boats to eject players on land return AbstractBoat.Status.ON_LAND; } else { return AbstractBoat.Status.IN_AIR; -@@ -929,7 +930,13 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { +@@ -878,7 +879,13 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { @Override public final ItemStack getPickResult() { -- return new ItemStack((ItemLike) this.dropItem.get()); -+ // Purpur start -+ final ItemStack boat = new ItemStack((ItemLike) this.dropItem.get()); +- return new ItemStack(this.dropItem.get()); ++ // Purpur start - Apply display names from item forms of entities to entities and vice versa ++ final ItemStack boat = new ItemStack(this.dropItem.get()); + if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) { + boat.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, null); + } + return boat; -+ // Purpur end ++ // Purpur end - Apply display names from item forms of entities to entities and vice versa } public static enum Status { -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 cdc8606ffe5c75ee19d92e9f86f26b2a502d765e..b31940441596079aae1cd2a38b9d22be18358448 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -92,6 +92,10 @@ public abstract class AbstractMinecart extends VehicleEntity { +diff --git a/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 9e15e7159cf98b3928110df9eae6de93793bf44e..6df4d736d94b9e49a3eb3d59a329e37127aa64cd 100644 +--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -83,6 +83,10 @@ public abstract class AbstractMinecart extends VehicleEntity { private double flyingY = 0.95; private double flyingZ = 0.95; public Double maxSpeed; -+ // Purpur start ++ // Purpur start - Minecart settings and WASD controls + public double storedMaxSpeed; + public boolean isNewBehavior; -+ // Purpur end - // CraftBukkit end ++ // Purpur end - Minecart settings and WASD controls public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + // CraftBukkit end -@@ -100,8 +104,13 @@ public abstract class AbstractMinecart extends VehicleEntity { +@@ -91,8 +95,13 @@ public abstract class AbstractMinecart extends VehicleEntity { this.blocksBuilding = true; - if (AbstractMinecart.useExperimentalMovement(world)) { + if (useExperimentalMovement(level)) { this.behavior = new NewMinecartBehavior(this); -+ this.isNewBehavior = true; // Purpur ++ this.isNewBehavior = true; // Purpur - Minecart settings and WASD controls } else { this.behavior = new OldMinecartBehavior(this); -+ // Purpur start ++ // Purpur start - Minecart settings and WASD controls + this.isNewBehavior = false; -+ maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; -+ // Purpur end ++ maxSpeed = storedMaxSpeed = level.purpurConfig.minecartMaxSpeed; ++ // Purpur end - Minecart settings and WASD controls } - } -@@ -289,6 +298,14 @@ public abstract class AbstractMinecart extends VehicleEntity { + +@@ -258,6 +267,14 @@ public abstract class AbstractMinecart extends VehicleEntity { @Override public void tick() { -+ // Purpur start ++ // Purpur start - Minecart settings and WASD controls + if (!this.isNewBehavior) { + if (storedMaxSpeed != level().purpurConfig.minecartMaxSpeed) { + maxSpeed = storedMaxSpeed = level().purpurConfig.minecartMaxSpeed; + } + } -+ // Purpur end ++ // Purpur end - Minecart settings and WASD controls + // CraftBukkit start double prevX = this.getX(); double prevY = this.getY(); -@@ -426,16 +443,62 @@ public abstract class AbstractMinecart extends VehicleEntity { - this.behavior.moveAlongTrack(world); +@@ -394,15 +411,61 @@ public abstract class AbstractMinecart extends VehicleEntity { + this.behavior.moveAlongTrack(level); } -+ // Purpur start ++ // Purpur start - Minecart settings and WASD controls + private Double lastSpeed; + + public double getControllableSpeed() { @@ -12945,25 +13089,25 @@ index cdc8606ffe5c75ee19d92e9f86f26b2a502d765e..b31940441596079aae1cd2a38b9d22be + } + return lastSpeed = speed; + } -+ // Purpur end ++ // Purpur end - Minecart settings and WASD controls + - protected void comeOffTrack(ServerLevel world) { - double d0 = this.getMaxSpeed(world); - Vec3 vec3d = this.getDeltaMovement(); - - this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); + protected void comeOffTrack(ServerLevel level) { + double maxSpeed = this.getMaxSpeed(level); + Vec3 deltaMovement = this.getDeltaMovement(); + this.setDeltaMovement(Mth.clamp(deltaMovement.x, -maxSpeed, maxSpeed), deltaMovement.y, Mth.clamp(deltaMovement.z, -maxSpeed, maxSpeed)); + -+ // Purpur start ++ // Purpur start - Minecart settings and WASD controls + if (level().purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) { + Entity passenger = passengers.get(0); -+ if (passenger instanceof Player) { -+ Player player = (Player) passenger; -+ if (player.jumping && this.onGround) { ++ if (passenger instanceof net.minecraft.server.level.ServerPlayer player) { ++ net.minecraft.world.entity.player.Input lastClientInput = player.getLastClientInput(); ++ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F); ++ if (lastClientInput.jump() && this.onGround) { + setDeltaMovement(new Vec3(getDeltaMovement().x, level().purpurConfig.minecartControllableHopBoost, getDeltaMovement().z)); + } -+ if (player.zza != 0.0F) { -+ Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed()); -+ if (player.zza < 0.0) { ++ if (forward != 0.0F) { ++ org.bukkit.util.Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed()); ++ if (forward < 0.0) { + velocity.multiply(-0.5); + } + setDeltaMovement(new Vec3(velocity.getX(), getDeltaMovement().y, velocity.getZ())); @@ -12976,118 +13120,122 @@ index cdc8606ffe5c75ee19d92e9f86f26b2a502d765e..b31940441596079aae1cd2a38b9d22be + } else { + maxUpStep = 0.0F; + } -+ // Purpur end -+ ++ // Purpur end - Minecart settings and WASD controls if (this.onGround()) { // CraftBukkit start - replace magic numbers with our variables this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ)); // CraftBukkit end } -+ else if (level().purpurConfig.minecartControllable) setDeltaMovement(new Vec3(getDeltaMovement().x * derailedX, getDeltaMovement().y, getDeltaMovement().z * derailedZ)); // Purpur ++ else if (level().purpurConfig.minecartControllable) setDeltaMovement(new Vec3(getDeltaMovement().x * derailedX, getDeltaMovement().y, getDeltaMovement().z * derailedZ)); // Purpur - Minecart settings and WASD controls this.move(MoverType.SELF, this.getDeltaMovement()); if (!this.onGround()) { -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java -index f43439b31a14b9db4744512465d81134ebe5b3e1..0b9741dfebd0bb95e8c0e1f55ce18dfb353f693a 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java -@@ -426,7 +426,7 @@ public class NewMinecartBehavior extends MinecartBehavior { - private Vec3 calculateBoostTrackSpeed(Vec3 velocity, BlockPos railPos, BlockState railState) { - if (railState.is(Blocks.POWERED_RAIL) && (Boolean) railState.getValue(PoweredRailBlock.POWERED)) { - if (velocity.length() > 0.01D) { -- return velocity.normalize().scale(velocity.length() + 0.06D); -+ return velocity.normalize().scale(velocity.length() + this.level().purpurConfig.poweredRailBoostModifier); // Purpur +diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +index f386bfa64ac11150845666b26e938a8ae0efe574..724466d14c925704671e510cea1919ee95a2ae02 100644 +--- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java ++++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +@@ -391,7 +391,7 @@ public class NewMinecartBehavior extends MinecartBehavior { + private Vec3 calculateBoostTrackSpeed(Vec3 speed, BlockPos pos, BlockState state) { + if (state.is(Blocks.POWERED_RAIL) && state.getValue(PoweredRailBlock.POWERED)) { + if (speed.length() > 0.01) { +- return speed.normalize().scale(speed.length() + 0.06); ++ return speed.normalize().scale(speed.length() + this.level().purpurConfig.poweredRailBoostModifier); // Purpur - Configurable powered rail boost modifier } else { - Vec3 vec3d1 = this.minecart.getRedstoneDirection(railPos); - -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java -index 04a622f52353ebcc21f41c233f5a0fd67690cf4a..f10ce069ef427df16fd0ce0e60b85c805ca703f0 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java -@@ -310,9 +310,9 @@ public class OldMinecartBehavior extends MinecartBehavior { - vec3d5 = this.getDeltaMovement(); - d17 = vec3d5.horizontalDistance(); - if (d17 > 0.01D) { -- double d19 = 0.06D; -+ double d19 = world.purpurConfig.poweredRailBoostModifier; // Purpur - -- this.setDeltaMovement(vec3d5.add(vec3d5.x / d17 * 0.06D, 0.0D, vec3d5.z / d17 * 0.06D)); -+ this.setDeltaMovement(vec3d5.add(vec3d5.x / d17 * world.purpurConfig.poweredRailBoostModifier, 0.0D, vec3d5.z / d17 * world.purpurConfig.poweredRailBoostModifier)); // Purpur + Vec3 redstoneDirection = this.minecart.getRedstoneDirection(pos); + return redstoneDirection.lengthSqr() <= 0.0 ? speed : redstoneDirection.scale(speed.length() + 0.2); +diff --git a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java +index d12d0c71479ea890ce41e5e43a135606db16fb21..8d1b70e526892860cc286314642fe51e5a44d7dc 100644 +--- a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java ++++ b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java +@@ -279,8 +279,8 @@ public class OldMinecartBehavior extends MinecartBehavior { + Vec3 deltaMovement1 = this.getDeltaMovement(); + double d13 = deltaMovement1.horizontalDistance(); + if (d13 > 0.01) { +- double d14 = 0.06; +- this.setDeltaMovement(deltaMovement1.add(deltaMovement1.x / d13 * 0.06, 0.0, deltaMovement1.z / d13 * 0.06)); ++ double d14 = level.purpurConfig.poweredRailBoostModifier; // Purpur - Configurable powered rail boost modifier ++ this.setDeltaMovement(deltaMovement1.add(deltaMovement1.x / d13 * level.purpurConfig.poweredRailBoostModifier, 0.0, deltaMovement1.z / d13 * level.purpurConfig.poweredRailBoostModifier)); // Purpur - Configurable powered rail boost modifier } else { - Vec3 vec3d6 = this.getDeltaMovement(); - double d20 = vec3d6.x; -diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java -index 6a686be6a69ae890d519a54ca099d4ba14e5b9e1..b8b0b89b7f0a21ecff4ab6286f8a114e2d6b6b39 100644 ---- a/src/main/java/net/minecraft/world/food/FoodData.java -+++ b/src/main/java/net/minecraft/world/food/FoodData.java -@@ -44,6 +44,7 @@ public class FoodData { - org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityplayer, foodinfo.nutrition() + oldFoodLevel, itemstack); - + Vec3 deltaMovement2 = this.getDeltaMovement(); + double d15 = deltaMovement2.x; +diff --git a/net/minecraft/world/food/FoodData.java b/net/minecraft/world/food/FoodData.java +index d46b27913797c5a2f433efe086463b91a9c31f63..f205401b24cdf0f43d531fb33e58d7183f98e510 100644 +--- a/net/minecraft/world/food/FoodData.java ++++ b/net/minecraft/world/food/FoodData.java +@@ -36,6 +36,7 @@ public class FoodData { + int oldFoodLevel = this.foodLevel; + org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(serverPlayer, foodProperties.nutrition() + oldFoodLevel, stack); if (!event.isCancelled()) { -+ if (entityplayer.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) entityplayer.burpDelay = entityplayer.level().purpurConfig.playerBurpDelay; // Purpur - this.add(event.getFoodLevel() - oldFoodLevel, foodinfo.saturation()); ++ if (serverPlayer.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) serverPlayer.burpDelay = serverPlayer.level().purpurConfig.playerBurpDelay; // Purpur - Burp after eating food fills hunger bar completely + this.add(event.getFoodLevel() - oldFoodLevel, foodProperties.saturation()); } - -@@ -96,7 +97,7 @@ public class FoodData { - ++this.tickTimer; + serverPlayer.getBukkitEntity().sendHealthUpdate(); +@@ -84,7 +85,7 @@ public class FoodData { + this.tickTimer++; if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation - if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) { -- player.hurtServer(worldserver, player.damageSources().starve(), 1.0F); -+ player.hurtServer(worldserver, player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur + if (player.getHealth() > 10.0F || difficulty == Difficulty.HARD || player.getHealth() > 1.0F && difficulty == Difficulty.NORMAL) { +- player.hurtServer(serverLevel, player.damageSources().starve(), 1.0F); ++ player.hurtServer(serverLevel, player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur - Configurable hunger starvation damage } this.tickTimer = 0; -diff --git a/src/main/java/net/minecraft/world/food/FoodProperties.java b/src/main/java/net/minecraft/world/food/FoodProperties.java -index 882b72799ae532f4e181214d5756ec024af223e2..9a3f2a95debcf8b94f7deb375922ea09b30aabab 100644 ---- a/src/main/java/net/minecraft/world/food/FoodProperties.java -+++ b/src/main/java/net/minecraft/world/food/FoodProperties.java -@@ -33,7 +33,7 @@ public record FoodProperties(int nutrition, float saturation, boolean canAlwaysE - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), (SoundEvent) consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, randomsource.triangle(1.0F, 0.4F)); - if (user instanceof Player entityhuman) { - entityhuman.getFoodData().eat(this, stack, (ServerPlayer) entityhuman); // CraftBukkit -- world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); -+ //world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); // Purpur - moved to Player#tick() +diff --git a/net/minecraft/world/food/FoodProperties.java b/net/minecraft/world/food/FoodProperties.java +index 5e0d447409dc2223bb56cb8bb932e241bf88c78d..6e1544121c556cd8761dc86d4246e7270c08e732 100644 +--- a/net/minecraft/world/food/FoodProperties.java ++++ b/net/minecraft/world/food/FoodProperties.java +@@ -42,9 +42,11 @@ public record FoodProperties(int nutrition, float saturation, boolean canAlwaysE + level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, random.triangle(1.0F, 0.4F)); + if (entity instanceof Player player) { + player.getFoodData().eat(this, stack, (net.minecraft.server.level.ServerPlayer) player); // CraftBukkit +- level.playSound( +- null, player.getX(), player.getY(), player.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(random, 0.9F, 1.0F) +- ); ++ // Purpur start - Burp delay - moved to Player#tick() ++ //level.playSound( ++ // null, player.getX(), player.getY(), player.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(random, 0.9F, 1.0F) ++ //); ++ // Purpur end - Burp delay - moved to Player#tick() } - } -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 4680f77a275d8d2b226018db89a571ac25998dd8..bfc90524bd739ed1d91fe9912e38093b3c28928f 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -80,6 +80,7 @@ public abstract class AbstractContainerMenu { + +diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java +index 50af953a4698a3c6e16b840fab764dd733b3fbc9..3dcd8df0b395a8fed8bc0cbe0ff78f4ae0056fd3 100644 +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -65,6 +65,7 @@ public abstract class AbstractContainerMenu { @Nullable private ContainerSynchronizer synchronizer; private boolean suppressRemoteUpdates; + @Nullable protected ItemStack activeQuickItem = null; // Purpur - Anvil API - // CraftBukkit start public boolean checkReachable = true; -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -index 1240df9368855f836412b06cf564926a18bfe90d..e559eabed82d2f402908e5b80d1505076ccc53a2 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -@@ -114,7 +114,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu { - } else if (slot != 1 && slot != 0) { - if (this.canSmelt(itemstack1)) { - if (!this.moveItemStackTo(itemstack1, 0, 1, false)) { + public abstract org.bukkit.inventory.InventoryView getBukkitView(); +diff --git a/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/net/minecraft/world/inventory/AbstractFurnaceMenu.java +index d3814f351e3b0cd00b2b9ad0d122ca376c18e6a3..a23ff3defe4e49cd04008b7d793994bf2bf95159 100644 +--- a/net/minecraft/world/inventory/AbstractFurnaceMenu.java ++++ b/net/minecraft/world/inventory/AbstractFurnaceMenu.java +@@ -121,7 +121,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu { + } else if (index != 1 && index != 0) { + if (this.canSmelt(item)) { + if (!this.moveItemStackTo(item, 0, 1, false)) { - return ItemStack.EMPTY; -+ // Purpur start - fix #625 -+ if (this.isFuel(itemstack1)) { -+ if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { ++ // Purpur start - Added the ability to add combustible items ++ if (this.isFuel(item)) { ++ if (!this.moveItemStackTo(item, 1, 2, false)) { + return ItemStack.EMPTY; + } + } -+ // Purpur end ++ // Purpur end - Added the ability to add combustible items } - } else if (this.isFuel(itemstack1)) { - if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { -diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c58962d05 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -25,6 +25,12 @@ import org.slf4j.Logger; - import org.bukkit.craftbukkit.inventory.view.CraftAnvilView; - // CraftBukkit end + } else if (this.isFuel(item)) { + if (!this.moveItemStackTo(item, 1, 2, false)) { +diff --git a/net/minecraft/world/inventory/AnvilMenu.java b/net/minecraft/world/inventory/AnvilMenu.java +index aaa022ac3656d68bad8dbd4c80a90b62fb6f9a16..fa468068552b29661d3d258f5365a3ef06f68920 100644 +--- a/net/minecraft/world/inventory/AnvilMenu.java ++++ b/net/minecraft/world/inventory/AnvilMenu.java +@@ -20,6 +20,12 @@ import net.minecraft.world.level.block.AnvilBlock; + import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; +// Purpur start - Anvil API +import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket; @@ -13096,10 +13244,10 @@ index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c +// Purpur end - Anvil API + public class AnvilMenu extends ItemCombinerMenu { - public static final int INPUT_SLOT = 0; -@@ -55,6 +61,10 @@ public class AnvilMenu extends ItemCombinerMenu { - private CraftAnvilView bukkitEntity; + public static final int ADDITIONAL_SLOT = 1; +@@ -49,6 +55,10 @@ public class AnvilMenu extends ItemCombinerMenu { + private org.bukkit.craftbukkit.inventory.view.CraftAnvilView bukkitEntity; // CraftBukkit end public boolean bypassEnchantmentLevelRestriction = false; // Paper - bypass anvil level restrictions + // Purpur start - Anvil API @@ -13107,14 +13255,14 @@ index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c + public boolean canDoUnsafeEnchants = false; + // Purpur end - Anvil API - public AnvilMenu(int syncId, Inventory inventory) { - this(syncId, inventory, ContainerLevelAccess.NULL); -@@ -82,12 +92,17 @@ public class AnvilMenu extends ItemCombinerMenu { + public AnvilMenu(int containerId, Inventory playerInventory) { + this(containerId, playerInventory, ContainerLevelAccess.NULL); +@@ -74,12 +84,17 @@ public class AnvilMenu extends ItemCombinerMenu { @Override - protected boolean mayPickup(Player player, boolean present) { -- return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item -+ return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && (this.bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur - Anvil API + protected boolean mayPickup(Player player, boolean hasStack) { +- return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && hasStack; // CraftBukkit - allow cost 0 like a free item ++ return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && (this.bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && hasStack; // CraftBukkit - allow cost 0 like a free item // Purpur - Anvil API } @Override @@ -13128,7 +13276,7 @@ index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c player.giveExperienceLevels(-this.cost.get()); } -@@ -138,6 +153,12 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -126,13 +141,19 @@ public class AnvilMenu extends ItemCombinerMenu { @Override public void createResult() { @@ -13138,61 +13286,65 @@ index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c + if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); + // Purpur end - Anvil API + - ItemStack itemstack = this.inputSlots.getItem(0); - + ItemStack item = this.inputSlots.getItem(0); this.onlyRenaming = false; -@@ -146,7 +167,7 @@ public class AnvilMenu extends ItemCombinerMenu { - long j = 0L; - byte b0 = 0; - -- if (!itemstack.isEmpty() && EnchantmentHelper.canStoreEnchantments(itemstack)) { -+ if (!itemstack.isEmpty() && this.canDoUnsafeEnchants || EnchantmentHelper.canStoreEnchantments(itemstack)) { // Purpur - Anvil API - ItemStack itemstack1 = itemstack.copy(); - ItemStack itemstack2 = this.inputSlots.getItem(1); - ItemEnchantments.Mutable itemenchantments_a = new ItemEnchantments.Mutable(EnchantmentHelper.getEnchantmentsForCrafting(itemstack1)); -@@ -213,7 +234,10 @@ public class AnvilMenu extends ItemCombinerMenu { - - i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); - Enchantment enchantment = (Enchantment) holder.value(); -- boolean flag3 = enchantment.canEnchant(itemstack); + this.cost.set(1); + int i = 0; + long l = 0L; + int i1 = 0; +- if (!item.isEmpty() && EnchantmentHelper.canStoreEnchantments(item)) { ++ if (!item.isEmpty() && this.canDoUnsafeEnchants || EnchantmentHelper.canStoreEnchantments(item)) { // Purpur - Anvil API + ItemStack itemStack = item.copy(); + ItemStack item1 = this.inputSlots.getItem(1); + ItemEnchantments.Mutable mutable = new ItemEnchantments.Mutable(EnchantmentHelper.getEnchantmentsForCrafting(itemStack)); +@@ -191,23 +212,36 @@ public class AnvilMenu extends ItemCombinerMenu { + int intValue = entry.getIntValue(); + intValue = level == intValue ? intValue + 1 : Math.max(intValue, level); + Enchantment enchantment = holder.value(); +- boolean canEnchant = enchantment.canEnchant(item); + // Purpur start - Config to allow unsafe enchants -+ boolean flag3 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || enchantment.canEnchant(itemstack); // whether the enchantment can be applied on specific item type -+ boolean flag4 = true; // whether two incompatible enchantments can be applied on a single item ++ boolean canEnchant = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || enchantment.canEnchant(item); // whether the enchantment can be applied on specific item type ++ boolean canEnchant1 = true; // whether two incompatible enchantments can be applied on a single item + // Purpur end - Config to allow unsafe enchants + if (this.player.getAbilities().instabuild || item.is(Items.ENCHANTED_BOOK)) { + canEnchant = true; + } - if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { - flag3 = true; -@@ -225,16 +249,22 @@ public class AnvilMenu extends ItemCombinerMenu { - Holder holder1 = (Holder) iterator1.next(); - +- for (Holder holder1 : mutable.keySet()) { ++ // Purpur start - Config to allow unsafe enchants ++ java.util.Iterator> mutableIterator = mutable.keySet().iterator(); ++ while (mutableIterator.hasNext()) { ++ Holder holder1 = mutableIterator.next(); ++ // Purpur end - Config to allow unsafe enchants if (!holder1.equals(holder) && !Enchantment.areCompatible(holder, holder1)) { -- flag3 = false; -+ flag4 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants; // Purpur - Anvil API // Purpur - flag3 -> flag4 - Config to allow unsafe enchants +- canEnchant = false; +- i++; ++ canEnchant1 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants; // Purpur - Anvil API // Purpur - flag3 -> canEnchant1 - Config to allow unsafe enchants + // Purpur start - Config to allow unsafe enchants -+ if (!flag4 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) { -+ iterator1.remove(); // replace current enchant with the incompatible one trying to be applied -+ flag4 = true; ++ if (!canEnchant1 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) { ++ mutableIterator.remove(); // replace current enchant with the incompatible one trying to be applied // TODO: is this needed? ++ canEnchant1 = true; + } + // Purpur end - Config to allow unsafe enchants - ++i; ++ ++i; } } -- if (!flag3) { -+ if (!flag3 || !flag4) { // Purpur - Config to allow unsafe enchants - flag2 = true; - } else { +- if (!canEnchant) { ++ if (!canEnchant || !canEnchant1) { // Purpur - Config to allow unsafe enchants flag1 = true; -- if (i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions -+ if (!org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels && i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions // Purpur - Config to allow unsafe enchants - i2 = enchantment.getMaxLevel(); + } else { + flag = true; +- if (intValue > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions ++ if (!org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels && intValue > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions // Purpur - Config to allow unsafe enchants + intValue = enchantment.getMaxLevel(); } -@@ -264,6 +294,54 @@ public class AnvilMenu extends ItemCombinerMenu { - if (!this.itemName.equals(itemstack.getHoverName().getString())) { - b0 = 1; - i += b0; -+ // Purpur start +@@ -236,6 +270,54 @@ public class AnvilMenu extends ItemCombinerMenu { + if (!this.itemName.equals(item.getHoverName().getString())) { + i1 = 1; + i += i1; ++ // Purpur start - Allow anvil colors + if (this.player != null) { + org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity(); + String name = this.itemName; @@ -13236,14 +13388,14 @@ index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c + if (removeItalics) { + component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } -+ itemstack1.set(DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); ++ itemStack.set(DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); + } + else -+ // Purpur end - itemstack1.set(DataComponents.CUSTOM_NAME, Component.literal(this.itemName)); ++ // Purpur end - Allow anvil colors + itemStack.set(DataComponents.CUSTOM_NAME, Component.literal(this.itemName)); } - } else if (itemstack.has(DataComponents.CUSTOM_NAME)) { -@@ -287,6 +365,12 @@ public class AnvilMenu extends ItemCombinerMenu { + } else if (item.has(DataComponents.CUSTOM_NAME)) { +@@ -260,6 +342,12 @@ public class AnvilMenu extends ItemCombinerMenu { this.onlyRenaming = true; } @@ -13254,53 +13406,53 @@ index 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c + // Purpur end - Anvil API + if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit - itemstack1 = ItemStack.EMPTY; + itemStack = ItemStack.EMPTY; } -@@ -307,6 +391,13 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -280,6 +368,13 @@ public class AnvilMenu extends ItemCombinerMenu { - org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemStack); // CraftBukkit this.broadcastChanges(); + + // Purpur start - Anvil API -+ if ((this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants) && itemstack1 != ItemStack.EMPTY) { // Purpur - Config to allow unsafe enchants -+ ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 2, itemstack1)); ++ if ((this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants) && itemStack != ItemStack.EMPTY) { // Purpur - Config to allow unsafe enchants ++ ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 2, itemStack)); + ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetDataPacket(this.containerId, 0, this.cost.get())); + } + // Purpur end - Anvil API } else { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item -@@ -315,7 +406,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -288,7 +383,7 @@ public class AnvilMenu extends ItemCombinerMenu { } - public static int calculateIncreasedRepairCost(int cost) { -- return (int) Math.min((long) cost * 2L + 1L, 2147483647L); -+ return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? (int) Math.min((long) cost * 2L + 1L, 2147483647L) : 0; // Purpur - Make anvil cumulative cost configurable + public static int calculateIncreasedRepairCost(int oldRepairCost) { +- return (int)Math.min(oldRepairCost * 2L + 1L, 2147483647L); ++ return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? (int)Math.min(oldRepairCost * 2L + 1L, 2147483647L) : 0; // Purpur - Make anvil cumulative cost configurable } - public boolean setItemName(String newItemName) { -diff --git a/src/main/java/net/minecraft/world/inventory/ArmorSlot.java b/src/main/java/net/minecraft/world/inventory/ArmorSlot.java -index 6c0b6abb1698fac9bb902f695b725d4ab783ee90..091e3c3514fcb378b68098114106d09f04d8fb0d 100644 ---- a/src/main/java/net/minecraft/world/inventory/ArmorSlot.java -+++ b/src/main/java/net/minecraft/world/inventory/ArmorSlot.java -@@ -45,7 +45,7 @@ class ArmorSlot extends Slot { + public boolean setItemName(String itemName) { +diff --git a/net/minecraft/world/inventory/ArmorSlot.java b/net/minecraft/world/inventory/ArmorSlot.java +index 6a1c0f2b11a289c419b9070feb08d9464570c5b7..f9d82d7f7e76032a13d196bc1d0a209015b461ee 100644 +--- a/net/minecraft/world/inventory/ArmorSlot.java ++++ b/net/minecraft/world/inventory/ArmorSlot.java +@@ -42,7 +42,7 @@ class ArmorSlot extends Slot { @Override - public boolean mayPickup(Player playerEntity) { - ItemStack itemStack = this.getItem(); -- return (itemStack.isEmpty() || playerEntity.isCreative() || !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) -+ return (itemStack.isEmpty() || playerEntity.isCreative() || (!EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) || playerEntity.level().purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(net.minecraft.world.effect.MobEffects.WEAKNESS))) - && super.mayPickup(playerEntity); + public boolean mayPickup(Player player) { + ItemStack item = this.getItem(); +- return (item.isEmpty() || player.isCreative() || !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) ++ return (item.isEmpty() || player.isCreative() || (!EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) || player.level().purpurConfig.playerRemoveBindingWithWeakness && player.hasEffect(net.minecraft.world.effect.MobEffects.WEAKNESS))) // Purpur - Config to remove curse of binding with weakness + && super.mayPickup(player); } -diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java -index 48a6b6136ac3414ca735f93a14b1a8d76210603c..27321b07cd04814bc1ff720c65770d7755625bb6 100644 ---- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java +diff --git a/net/minecraft/world/inventory/ChestMenu.java b/net/minecraft/world/inventory/ChestMenu.java +index 280169afbd637eeb67ddf7eaeb4eecd464a128d5..ba7730a24831efa33de4c5ffce57bfa7177f89d6 100644 +--- a/net/minecraft/world/inventory/ChestMenu.java ++++ b/net/minecraft/world/inventory/ChestMenu.java @@ -66,10 +66,30 @@ public class ChestMenu extends AbstractContainerMenu { - return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6); + return new ChestMenu(MenuType.GENERIC_9x6, containerId, playerInventory, 6); } -+ // Purpur start ++ // Purpur start - Barrels and enderchests 6 rows + public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1); + } @@ -13308,13 +13460,13 @@ index 48a6b6136ac3414ca735f93a14b1a8d76210603c..27321b07cd04814bc1ff720c65770d77 + public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2); + } -+ // Purpur end ++ // Purpur end - Barrels and enderchests 6 rows + - public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) { - return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3); + public static ChestMenu threeRows(int containerId, Inventory playerInventory, Container container) { + return new ChestMenu(MenuType.GENERIC_9x3, containerId, playerInventory, container, 3); } -+ // Purpur start ++ // Purpur start - Barrels and enderchests 6 rows + public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4); + } @@ -13322,119 +13474,105 @@ index 48a6b6136ac3414ca735f93a14b1a8d76210603c..27321b07cd04814bc1ff720c65770d77 + public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5); + } -+ // Purpur end ++ // Purpur end - Barrels and enderchests 6 rows + - public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) { - return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6); + public static ChestMenu sixRows(int containerId, Inventory playerInventory, Container container) { + return new ChestMenu(MenuType.GENERIC_9x6, containerId, playerInventory, container, 6); } -diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -index 50a735dd97daab4fb9579f922a4c63de60204f29..5b8ad051347f73553acb65c5ddc690d2b7eaa754 100644 ---- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -@@ -42,6 +42,12 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent; - import org.bukkit.entity.Player; - // CraftBukkit end - -+// Purpur start -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.EnchantingTableBlockEntity; -+import org.bukkit.craftbukkit.entity.CraftHumanEntity; -+// Purpur end -+ - public class EnchantmentMenu extends AbstractContainerMenu { - - static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = ResourceLocation.withDefaultNamespace("item/empty_slot_lapis_lazuli"); -@@ -76,6 +82,22 @@ public class EnchantmentMenu extends AbstractContainerMenu { - return context.getLocation(); +diff --git a/net/minecraft/world/inventory/EnchantmentMenu.java b/net/minecraft/world/inventory/EnchantmentMenu.java +index 1bba36afb00ad2a63bbfba60aab0f614b4aa8174..0936e08f5f465dc34a1b4b5370fcc3e0d57eab0a 100644 +--- a/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -63,6 +63,22 @@ public class EnchantmentMenu extends AbstractContainerMenu { + return access.getLocation(); } // CraftBukkit end + -+ // Purpur start ++ // Purpur start - Enchantment Table Persists Lapis + @Override -+ public void onClose(CraftHumanEntity who) { ++ public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) { + super.onClose(who); + + if (who.getHandle().level().purpurConfig.enchantmentTableLapisPersists) { + access.execute((level, pos) -> { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { ++ net.minecraft.world.level.block.entity.BlockEntity blockEntity = level.getBlockEntity(pos); ++ if (blockEntity instanceof net.minecraft.world.level.block.entity.EnchantingTableBlockEntity enchantmentTable) { + enchantmentTable.setLapis(this.getItem(1).getCount()); + } + }); + } + } -+ // Purpur end ++ // Purpur end - Enchantment Table Persists Lapis }; - this.random = RandomSource.create(); - this.enchantmentSeed = DataSlot.standalone(); -@@ -100,6 +122,16 @@ public class EnchantmentMenu extends AbstractContainerMenu { - return Pair.of(InventoryMenu.BLOCK_ATLAS, EnchantmentMenu.EMPTY_SLOT_LAPIS_LAZULI); + // Paper end - Add missing InventoryHolders + this.access = access; +@@ -83,6 +99,16 @@ public class EnchantmentMenu extends AbstractContainerMenu { + return EnchantmentMenu.EMPTY_SLOT_LAPIS_LAZULI; } }); -+ // Purpur start ++ // Purpur start - Enchantment Table Persists Lapis + access.execute((level, pos) -> { + if (level.purpurConfig.enchantmentTableLapisPersists) { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { ++ net.minecraft.world.level.block.entity.BlockEntity blockEntity = level.getBlockEntity(pos); ++ if (blockEntity instanceof net.minecraft.world.level.block.entity.EnchantingTableBlockEntity enchantmentTable) { + this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); + } + } + }); -+ // Purpur end ++ // Purpur end - Enchantment Table Persists Lapis this.addStandardInventorySlots(playerInventory, 8, 84); this.addDataSlot(DataSlot.shared(this.costs, 0)); this.addDataSlot(DataSlot.shared(this.costs, 1)); -@@ -329,6 +361,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { - public void removed(net.minecraft.world.entity.player.Player player) { +@@ -294,7 +320,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { + @Override + public void removed(Player player) { super.removed(player); - this.access.execute((world, blockposition) -> { -+ if (world.purpurConfig.enchantmentTableLapisPersists) this.getSlot(1).set(ItemStack.EMPTY); // Purpur - this.clearContainer(player, this.enchantSlots); - }); +- this.access.execute((level, blockPos) -> this.clearContainer(player, this.enchantSlots)); ++ this.access.execute((level, blockPos) -> {if (level.purpurConfig.enchantmentTableLapisPersists) this.getSlot(1).set(ItemStack.EMPTY);this.clearContainer(player, this.enchantSlots);}); // Purpur - Enchantment Table Persists Lapis } -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 5687f492fc76f699e2a388790ca5380d9b8c8d0a..111da7435f0abb5a57bd2c5fecead2380ac4347a 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -96,12 +96,14 @@ public class GrindstoneMenu extends AbstractContainerMenu { + @Override +diff --git a/net/minecraft/world/inventory/GrindstoneMenu.java b/net/minecraft/world/inventory/GrindstoneMenu.java +index f85bd2a90c2694d96f67cc3701a9bbf081fe8475..2da45bd93f12aadae4e28357b3225353dba89427 100644 +--- a/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -91,11 +91,13 @@ public class GrindstoneMenu extends AbstractContainerMenu { @Override - public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { -+ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur - context.execute((world, blockposition) -> { - if (world instanceof ServerLevel) { + public void onTake(Player player, ItemStack stack) { + access.execute((level, blockPos) -> { ++ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur - Grindstone API + if (level instanceof ServerLevel) { // Paper start - Fire BlockExpEvent on grindstone use - org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition), this.getExperienceAmount(world)); + org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos), this.getExperienceAmount(level)); event.callEvent(); -- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); -+ org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), event.getExpToDrop()); grindstoneTakeResultEvent.callEvent(); // Purpur -+ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Purpur +- ExperienceOrb.award((ServerLevel) level, Vec3.atCenterOf(blockPos), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); ++ org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), event.getExpToDrop()); grindstoneTakeResultEvent.callEvent(); // Purpur - Grindstone API ++ ExperienceOrb.award((ServerLevel) level, Vec3.atCenterOf(blockPos), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Purpur - Grindstone API // Paper end - Fire BlockExpEvent on grindstone use } -@@ -135,7 +137,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - Holder holder = (Holder) entry.getKey(); - int k = entry.getIntValue(); - +@@ -124,7 +126,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + for (Entry> entry : enchantmentsForCrafting.entrySet()) { + Holder holder = entry.getKey(); + int intValue = entry.getIntValue(); - if (!holder.is(EnchantmentTags.CURSE)) { -+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value())) { // Purpur - j += ((Enchantment) holder.value()).getMinCost(k); ++ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value())) { // Purpur - Config for grindstones + i += holder.value().getMinCost(intValue); } } -@@ -222,7 +224,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - Entry> entry = (Entry) iterator.next(); - Holder holder = (Holder) entry.getKey(); +@@ -202,15 +204,75 @@ public class GrindstoneMenu extends AbstractContainerMenu { -- if (!holder.is(EnchantmentTags.CURSE) || itemenchantments_a.getLevel(holder) == 0) { -+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()) || itemenchantments_a.getLevel(holder) == 0) { // Purpur - itemenchantments_a.upgrade(holder, entry.getIntValue()); + for (Entry> entry : enchantmentsForCrafting.entrySet()) { + Holder holder = entry.getKey(); +- if (!holder.is(EnchantmentTags.CURSE) || mutable.getLevel(holder) == 0) { ++ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()) || mutable.getLevel(holder) == 0) { // Purpur - Config for grindstones + mutable.upgrade(holder, entry.getIntValue()); } } -@@ -230,10 +232,70 @@ public class GrindstoneMenu extends AbstractContainerMenu { }); } -+ // Purpur start ++ // Purpur start - Config for grindstones + private java.util.List> GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST = java.util.List.of( + // DataComponents.MAX_STACK_SIZE, + // DataComponents.DAMAGE, @@ -13493,21 +13631,19 @@ index 5687f492fc76f699e2a388790ca5380d9b8c8d0a..111da7435f0abb5a57bd2c5fecead238 + // DataComponents.LOCK, + // DataComponents.CONTAINER_LOOT, + ); -+ // Purpur end ++ // Purpur end - Config for grindstones private ItemStack removeNonCursesFrom(ItemStack item) { - ItemEnchantments itemenchantments = EnchantmentHelper.updateEnchantments(item, (itemenchantments_a) -> { - itemenchantments_a.removeIf((holder) -> { -- return !holder.is(EnchantmentTags.CURSE); -+ return !org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()); // Purpur - }); - }); - -@@ -248,6 +310,23 @@ public class GrindstoneMenu extends AbstractContainerMenu { +- ItemEnchantments itemEnchantments = EnchantmentHelper.updateEnchantments(item, mutable -> mutable.removeIf(holder -> !holder.is(EnchantmentTags.CURSE))); ++ ItemEnchantments itemEnchantments = EnchantmentHelper.updateEnchantments(item, mutable -> mutable.removeIf(holder -> !org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()))); // Purpur - Config for grindstones + if (item.is(Items.ENCHANTED_BOOK) && itemEnchantments.isEmpty()) { + item = item.transmuteCopy(Items.BOOK); + } +@@ -222,6 +284,23 @@ public class GrindstoneMenu extends AbstractContainerMenu { } item.set(DataComponents.REPAIR_COST, i); + -+ // Purpur start ++ // Purpur start - Config for grindstones + net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder(); + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes) { + item.getComponents().forEach(typedDataComponent -> { @@ -13521,431 +13657,420 @@ index 5687f492fc76f699e2a388790ca5380d9b8c8d0a..111da7435f0abb5a57bd2c5fecead238 + builder.remove(DataComponents.LORE); + } + item.applyComponents(builder.build()); -+ // Purpur end ++ // Purpur end - Config for grindstones + return item; } -@@ -309,7 +388,9 @@ public class GrindstoneMenu extends AbstractContainerMenu { +@@ -278,7 +357,9 @@ public class GrindstoneMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } -+ this.activeQuickItem = itemstack; // Purpur - slot1.onTake(player, itemstack1); -+ this.activeQuickItem = null; // Purpur ++ this.activeQuickItem = itemStack; // Purpur - Grindstone API + slot.onTake(player, item); ++ this.activeQuickItem = null; // Purpur - Grindstone API } - return itemstack; -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index a5d53a656513ae81cc3f9fc506caf6adaba62a8e..ac9df238ef0f3d009f25976b95e0b750e963e952 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -164,7 +164,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + return itemStack; +diff --git a/net/minecraft/world/inventory/ItemCombinerMenu.java b/net/minecraft/world/inventory/ItemCombinerMenu.java +index 34d52c941395645e77de810855b14012c259cf02..c605bd700fd9f5a6596a2bf9648492786306b025 100644 +--- a/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -156,7 +156,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } -+ this.activeQuickItem = itemstack; // Purpur - Anvil API - slot1.onTake(player, itemstack1); ++ this.activeQuickItem = itemStack; // Purpur - Anvil API + slot.onTake(player, item); + this.activeQuickItem = null; // Purpur - Anvil API } - return itemstack; -diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -index a15d5ff872dbd77f3c3145e0328f3d02e431ff8c..1dcf36d502990d32fc4cd3ea69c3ea334baed69a 100644 ---- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -@@ -31,11 +31,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { + return itemStack; +diff --git a/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/net/minecraft/world/inventory/PlayerEnderChestContainer.java +index a6a359bab2a727f4631b633a8bb370dd40decc75..d2d75e5c34c97300ce5da8c7ea70958aba31fa4a 100644 +--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java ++++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java +@@ -25,11 +25,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { } public PlayerEnderChestContainer(Player owner) { - super(27); -+ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur ++ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur - Barrels and enderchests 6 rows this.owner = owner; // CraftBukkit end } -+ // Purpur start ++ // Purpur start - Barrels and enderchests 6 rows + @Override + public int getContainerSize() { + return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount; + } -+ // Purpur end ++ // Purpur end - Barrels and enderchests 6 rows + - public void setActiveChest(EnderChestBlockEntity blockEntity) { - this.activeChest = blockEntity; + public void setActiveChest(EnderChestBlockEntity enderChestBlockEntity) { + this.activeChest = enderChestBlockEntity; } -diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -index cb4baebe22eeab17aed67a5ecc506b932fe2230b..c1a6734cc08de1c9fc413b1fa81a199ac9547ec2 100644 ---- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java -+++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -@@ -59,6 +59,14 @@ public class ArmorStandItem extends Item { +diff --git a/net/minecraft/world/item/ArmorStandItem.java b/net/minecraft/world/item/ArmorStandItem.java +index d82e6651999a2650ec8884c4c3d8de4133cb42a4..a26b9fe964c79da57aaa0f755a81934f51a79913 100644 +--- a/net/minecraft/world/item/ArmorStandItem.java ++++ b/net/minecraft/world/item/ArmorStandItem.java +@@ -51,6 +51,10 @@ public class ArmorStandItem extends Item { return InteractionResult.FAIL; } // CraftBukkit end -+ // Purpur start -+ if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { -+ entityarmorstand.setCustomName(null); -+ } -+ if (world.purpurConfig.armorstandSetNameVisible && entityarmorstand.getCustomName() != null) { -+ entityarmorstand.setCustomNameVisible(true); -+ } -+ // Purpur end - worldserver.addFreshEntityWithPassengers(entityarmorstand); - world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F); - entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer()); -diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java -index abff08f2d61014944235ffe2f5494a718a28cc10..dc2c415ab227e1357533079ada4903e9f69d4f55 100644 ---- a/src/main/java/net/minecraft/world/item/AxeItem.java -+++ b/src/main/java/net/minecraft/world/item/AxeItem.java ++ // Purpur start - Apply display names from item forms of entities to entities and vice versa ++ if (!serverLevel.purpurConfig.persistentDroppableEntityDisplayNames) armorStand.setCustomName(null); ++ if (serverLevel.purpurConfig.armorstandSetNameVisible && armorStand.getCustomName() != null) armorStand.setCustomNameVisible(true); ++ // Purpur end - Apply display names from item forms of entities to entities and vice versa + serverLevel.addFreshEntityWithPassengers(armorStand); + level.playSound( + null, armorStand.getX(), armorStand.getY(), armorStand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F +diff --git a/net/minecraft/world/item/AxeItem.java b/net/minecraft/world/item/AxeItem.java +index d4b79f897ee7950893d608dc343073dbcff6ab14..7cf309c1b119ef8432c04c2b3df248d7d96dc15c 100644 +--- a/net/minecraft/world/item/AxeItem.java ++++ b/net/minecraft/world/item/AxeItem.java @@ -62,13 +62,15 @@ public class AxeItem extends DiggerItem { if (playerHasShieldUseIntent(context)) { return InteractionResult.PASS; } else { -- Optional optional = this.evaluateNewBlockState(level, blockPos, player, level.getBlockState(blockPos)); -+ Optional optional = this.evaluateActionable(level, blockPos, player, level.getBlockState(blockPos)); // Purpur +- Optional optional = this.evaluateNewBlockState(level, clickedPos, player, level.getBlockState(clickedPos)); ++ Optional optional = this.evaluateActionable(level, clickedPos, player, level.getBlockState(clickedPos)); // Purpur - Tool actionable options if (optional.isEmpty()) { return InteractionResult.PASS; } else { -+ org.purpurmc.purpur.tool.Actionable actionable = optional.get(); // Purpur -+ BlockState state = actionable.into().withPropertiesOf(level.getBlockState(blockPos)); // Purpur - ItemStack itemStack = context.getItemInHand(); ++ org.purpurmc.purpur.tool.Actionable actionable = optional.get(); // Purpur - Tool actionable options ++ BlockState state = actionable.into().withPropertiesOf(level.getBlockState(clickedPos)); // Purpur - Tool actionable options + ItemStack itemInHand = context.getItemInHand(); // Paper start - EntityChangeBlockEvent -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { // Purpur +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, optional.get())) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, state)) { // Purpur - Tool actionable options return InteractionResult.PASS; } // Paper end @@ -76,8 +78,15 @@ public class AxeItem extends DiggerItem { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand); } -- level.setBlock(blockPos, optional.get(), 11); -- level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional.get())); -+ // Purpur start -+ level.setBlock(blockPos, state, 11); +- level.setBlock(clickedPos, optional.get(), 11); +- level.gameEvent(GameEvent.BLOCK_CHANGE, clickedPos, GameEvent.Context.of(player, optional.get())); ++ // Purpur start - Tool actionable options ++ level.setBlock(clickedPos, state, 11); + actionable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { -+ Block.popResourceFromFace(level, blockPos, context.getClickedFace(), new ItemStack(drop)); ++ Block.popResourceFromFace(level, clickedPos, context.getClickedFace(), new ItemStack(drop)); + } + }); -+ level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, state)); -+ // Purpur end ++ level.gameEvent(GameEvent.BLOCK_CHANGE, clickedPos, GameEvent.Context.of(player, state)); ++ // Purpur end - Tool actionable options if (player != null) { - itemStack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(context.getHand())); + itemInHand.hurtAndBreak(1, player, LivingEntity.getSlotForHand(context.getHand())); } @@ -92,22 +101,24 @@ public class AxeItem extends DiggerItem { return context.getHand().equals(InteractionHand.MAIN_HAND) && player.getOffhandItem().is(Items.SHIELD) && !player.isSecondaryUseActive(); } -- private Optional evaluateNewBlockState(Level world, BlockPos pos, @Nullable Player player, BlockState state) { -- Optional optional = this.getStripped(state); -+ private Optional evaluateActionable(Level world, BlockPos pos, @Nullable Player player, BlockState state) { // Purpur -+ Optional optional = Optional.ofNullable(world.purpurConfig.axeStrippables.get(state.getBlock())); // Purpur - if (optional.isPresent()) { -- world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound(STRIPPABLES.containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - return optional; +- private Optional evaluateNewBlockState(Level level, BlockPos pos, @Nullable Player player, BlockState state) { +- Optional stripped = this.getStripped(state); ++ private Optional evaluateActionable(Level level, BlockPos pos, @Nullable Player player, BlockState state) { // Purpur - Tool actionable options ++ Optional stripped = Optional.ofNullable(level.purpurConfig.axeStrippables.get(state.getBlock())); // Purpur - Tool actionable options + if (stripped.isPresent()) { +- level.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); ++ level.playSound(STRIPPABLES.containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound + return stripped; } else { -- Optional optional2 = WeatheringCopper.getPrevious(state); -+ Optional optional2 = Optional.ofNullable(world.purpurConfig.axeWeatherables.get(state.getBlock())); // Purpur - if (optional2.isPresent()) { -- world.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound(WeatheringCopper.getPrevious(state).isPresent() ? player : null, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - world.levelEvent(player, 3005, pos, 0); - return optional2; +- Optional previous = WeatheringCopper.getPrevious(state); ++ Optional previous = Optional.ofNullable(level.purpurConfig.axeWeatherables.get(state.getBlock())); // Purpur - Tool actionable options + if (previous.isPresent()) { +- level.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); ++ level.playSound(WeatheringCopper.getPrevious(state).isPresent() ? player : null, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - Tool actionable options - force sound + level.levelEvent(player, 3005, pos, 0); + return previous; } else { -- Optional optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(state.getBlock())) +- Optional optional = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(state.getBlock())) - .map(block -> block.withPropertiesOf(state)); -+ // Purpur start -+ Optional optional3 = Optional.ofNullable(world.purpurConfig.axeWaxables.get(state.getBlock())); ++ // Purpur start - Tool actionable options ++ Optional optional = Optional.ofNullable(level.purpurConfig.axeWaxables.get(state.getBlock())); + // .map(block -> block.withPropertiesOf(state)); -+ // Purpur end - if (optional3.isPresent()) { -- world.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound(HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - world.levelEvent(player, 3004, pos, 0); - return optional3; ++ // Purpur end - Tool actionable options + if (optional.isPresent()) { +- level.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); ++ level.playSound(HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - Tool actionable options - force sound + level.levelEvent(player, 3004, pos, 0); + return optional; } else { -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index c816c935ecc74a811ffdffbe6ded73c06e92324a..d58619d1d63a03598b8740dd789d4b6f2c93f8d0 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -151,7 +151,16 @@ public class BlockItem extends Item { +diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java +index 68e50c6ade879d263424f244070677cb81c34c33..1df587402862d67a76ed090df60bfa20c3a9209d 100644 +--- a/net/minecraft/world/item/BlockItem.java ++++ b/net/minecraft/world/item/BlockItem.java +@@ -152,7 +152,16 @@ public class BlockItem extends Item { } - protected boolean updateCustomBlockEntityTag(BlockPos pos, Level world, @Nullable Player player, ItemStack stack, BlockState state) { -- return BlockItem.updateCustomBlockEntityTag(world, player, pos, stack); -+ // Purpur start -+ boolean handled = updateCustomBlockEntityTag(world, player, pos, stack); -+ if (world.purpurConfig.persistentTileEntityLore) { -+ BlockEntity blockEntity1 = world.getBlockEntity(pos); + protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, @Nullable Player player, ItemStack stack, BlockState state) { +- return updateCustomBlockEntityTag(level, player, pos, stack); ++ // Purpur start - Persistent BlockEntity Lore and DisplayName ++ boolean handled = updateCustomBlockEntityTag(level, player, pos, stack); ++ if (level.purpurConfig.persistentTileEntityLore) { ++ BlockEntity blockEntity1 = level.getBlockEntity(pos); + if (blockEntity1 != null) { + blockEntity1.setPersistentLore(stack.getOrDefault(DataComponents.LORE, net.minecraft.world.item.component.ItemLore.EMPTY)); + } + } + return handled; -+ // Purpur end ++ // Purpur end - Persistent BlockEntity Lore and DisplayName } @Nullable -@@ -213,6 +222,7 @@ public class BlockItem extends Item { - - if (tileentity != null) { - if (!world.isClientSide && tileentity.onlyOpCanSetNbt() && (player == null || !(player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place"))))) { // Spigot - add permission -+ if (!(!world.isClientSide && world.purpurConfig.silkTouchEnabled && tileentity instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity && player.getBukkitEntity().hasPermission("purpur.drop.spawners"))) - return false; +@@ -217,6 +226,7 @@ public class BlockItem extends Item { } -@@ -248,6 +258,7 @@ public class BlockItem extends Item { - ItemContainerContents itemcontainercontents = (ItemContainerContents) entity.getItem().set(DataComponents.CONTAINER, ItemContainerContents.EMPTY); + if (!type.onlyOpCanSetNbt() || player != null && (player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place")))) { // Spigot - add permission ++ if (!(level.purpurConfig.silkTouchEnabled && blockEntity instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity && player.getBukkitEntity().hasPermission("purpur.drop.spawners"))) // Purpur - Silk touch spawners + return customData.loadInto(blockEntity, level.registryAccess()); + } - if (itemcontainercontents != null) { -+ if (entity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed && this.getBlock() instanceof ShulkerBoxBlock) // Purpur - ItemUtils.onContainerDestroyed(entity, itemcontainercontents.nonEmptyItemsCopy()); +@@ -264,6 +274,7 @@ public class BlockItem extends Item { + public void onDestroyed(ItemEntity itemEntity) { + ItemContainerContents itemContainerContents = itemEntity.getItem().set(DataComponents.CONTAINER, ItemContainerContents.EMPTY); + if (itemContainerContents != null) { ++ if (itemEntity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed && this.getBlock() instanceof ShulkerBoxBlock) // Purpur - option to disable shulker box items from dropping contents when destroyed + ItemUtils.onContainerDestroyed(itemEntity, itemContainerContents.nonEmptyItemsCopy()); } - -diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java -index e51ffd6c5047ee907a58f3029f0ea7fc66aedfa7..78d41d57df9cb61b295f1f54db1e1d62c13db701 100644 ---- a/src/main/java/net/minecraft/world/item/BoatItem.java -+++ b/src/main/java/net/minecraft/world/item/BoatItem.java -@@ -71,6 +71,11 @@ public class BoatItem extends Item { + } +diff --git a/net/minecraft/world/item/BoatItem.java b/net/minecraft/world/item/BoatItem.java +index 13ce174e4f7e406f57a68ea0d3ef0ee3367f3f3b..ca86122e38688b29340cd8413ccf1746315e292a 100644 +--- a/net/minecraft/world/item/BoatItem.java ++++ b/net/minecraft/world/item/BoatItem.java +@@ -63,6 +63,7 @@ public class BoatItem extends Item { return InteractionResult.FAIL; } else { - abstractboat.setYRot(user.getYRot()); -+ // Purpur start -+ if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { -+ abstractboat.setCustomName(null); -+ } -+ // Purpur end - if (!world.noCollision(abstractboat, abstractboat.getBoundingBox())) { + boat.setYRot(player.getYRot()); ++ if (!level.purpurConfig.persistentDroppableEntityDisplayNames) boat.setCustomName(null); // Purpur - Apply display names from item forms of entities to entities and vice versa + if (!level.noCollision(boat, boat.getBoundingBox())) { return InteractionResult.FAIL; } else { -diff --git a/src/main/java/net/minecraft/world/item/BowItem.java b/src/main/java/net/minecraft/world/item/BowItem.java -index bb593209c95c9cf1f9c5d52d52fab4a33ddbabcf..58fa528e4b2589d362eb976afd6221cd94f2623c 100644 ---- a/src/main/java/net/minecraft/world/item/BowItem.java -+++ b/src/main/java/net/minecraft/world/item/BowItem.java +diff --git a/net/minecraft/world/item/BowItem.java b/net/minecraft/world/item/BowItem.java +index 57c933af200551162774f1d473437521e5a85833..b3e003694ce0da357e91ab3ce2b1380f9ab0a32a 100644 +--- a/net/minecraft/world/item/BowItem.java ++++ b/net/minecraft/world/item/BowItem.java @@ -28,6 +28,11 @@ public class BowItem extends ProjectileWeaponItem { return false; } else { - ItemStack itemStack = player.getProjectile(stack); -+ // Purpur start -+ if (world.purpurConfig.infinityWorksWithoutArrows && itemStack.isEmpty() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, stack) > 0) { -+ itemStack = new ItemStack(Items.ARROW); + ItemStack projectile = player.getProjectile(stack); ++ // Purpur start - Infinity bow settings ++ if (level.purpurConfig.infinityWorksWithoutArrows && projectile.isEmpty() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, stack) > 0) { ++ projectile = new ItemStack(Items.ARROW); + } -+ // Purpur end - if (itemStack.isEmpty()) { ++ // Purpur end - Infinity bow settings + if (projectile.isEmpty()) { return false; } else { @@ -38,7 +43,7 @@ public class BowItem extends ProjectileWeaponItem { } else { - List list = draw(stack, itemStack, player); - if (world instanceof ServerLevel serverLevel && !list.isEmpty()) { -- this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, f * 3.0F, 1.0F, f == 1.0F, null); -+ this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, f * 3.0F, (float) world.purpurConfig.bowProjectileOffset, f == 1.0F, null); // Purpur + List list = draw(stack, projectile, player); + if (level instanceof ServerLevel serverLevel && !list.isEmpty()) { +- this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, powerForTime * 3.0F, 1.0F, powerForTime == 1.0F, null); ++ this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, powerForTime * 3.0F, (float) serverLevel.purpurConfig.bowProjectileOffset, powerForTime == 1.0F, null); // Purpur - Projectile offset config } - world.playSound( + level.playSound( @@ -89,7 +94,7 @@ public class BowItem extends ProjectileWeaponItem { - public InteractionResult use(Level world, Player user, InteractionHand hand) { - ItemStack itemStack = user.getItemInHand(hand); - boolean bl = !user.getProjectile(itemStack).isEmpty(); -- if (!user.hasInfiniteMaterials() && !bl) { -+ if (!user.hasInfiniteMaterials() && !bl && !(world.purpurConfig.infinityWorksWithoutArrows && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, itemStack) > 0)) { // Purpur + public InteractionResult use(Level level, Player player, InteractionHand hand) { + ItemStack itemInHand = player.getItemInHand(hand); + boolean flag = !player.getProjectile(itemInHand).isEmpty(); +- if (!player.hasInfiniteMaterials() && !flag) { ++ if (!player.hasInfiniteMaterials() && !flag && !(level.purpurConfig.infinityWorksWithoutArrows && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, itemInHand) > 0)) { // Purpur - Infinity bow settings return InteractionResult.FAIL; } else { - user.startUsingItem(hand); -diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java -index 3bddfb6f7412ab86e0c090d0cbc6cf254b3f891c..6aa8ee091d3a7d2826d08ab9a03f970ef71a81ea 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -196,7 +196,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + player.startUsingItem(hand); +diff --git a/net/minecraft/world/item/BucketItem.java b/net/minecraft/world/item/BucketItem.java +index 62d79cb25879e6e48a1541f864d0b3782d926313..4dccebf97b720aa2189d3ae7b8e2d8066fa13573 100644 +--- a/net/minecraft/world/item/BucketItem.java ++++ b/net/minecraft/world/item/BucketItem.java +@@ -147,7 +147,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { // CraftBukkit end - if (!flag2) { - return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit -- } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { -+ } else if ((world.dimensionType().ultraWarm() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur - int i = blockposition.getX(); - int j = blockposition.getY(); - int k = blockposition.getZ(); -@@ -204,7 +204,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); + if (!flag) { + return result != null && this.emptyContents(player, level, result.getBlockPos().relative(result.getDirection()), null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit +- } else if (level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { ++ } else if ((level.dimensionType().ultraWarm() || (level.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur - Add allow water in end world option + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); +@@ -156,7 +156,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + ); - for (int l = 0; l < 8; ++l) { -- world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D); -+ ((ServerLevel) world).sendParticles(null, ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D, true); // Purpur + for (int i = 0; i < 8; i++) { +- level.addParticle(ParticleTypes.LARGE_SMOKE, x + Math.random(), y + Math.random(), z + Math.random(), 0.0, 0.0, 0.0); ++ ((net.minecraft.server.level.ServerLevel) level).sendParticlesSource(null, ParticleTypes.LARGE_SMOKE, false, true, x + Math.random(), y + Math.random(), z + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D); // Purpur - Add allow water in end world option } return true; -diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java -index 52c40eafc77e50a6fd21b9a7a250cea501f11690..86204c2ab5bbd5d45ddb1d626f844d91ccae6b4f 100644 ---- a/src/main/java/net/minecraft/world/item/CrossbowItem.java -+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -69,7 +69,7 @@ public class CrossbowItem extends ProjectileWeaponItem { - ItemStack itemStack = user.getItemInHand(hand); - ChargedProjectiles chargedProjectiles = itemStack.get(DataComponents.CHARGED_PROJECTILES); +diff --git a/net/minecraft/world/item/CrossbowItem.java b/net/minecraft/world/item/CrossbowItem.java +index 96ece6ff2c4fe2ad4d9cea2340596cfd6a59c569..1131e984fd30e40c1b99054b5db9462ffe55b5f1 100644 +--- a/net/minecraft/world/item/CrossbowItem.java ++++ b/net/minecraft/world/item/CrossbowItem.java +@@ -70,7 +70,7 @@ public class CrossbowItem extends ProjectileWeaponItem { + ItemStack itemInHand = player.getItemInHand(hand); + ChargedProjectiles chargedProjectiles = itemInHand.get(DataComponents.CHARGED_PROJECTILES); if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) { -- this.performShooting(world, user, hand, itemStack, getShootingPower(chargedProjectiles), 1.0F, null); -+ this.performShooting(world, user, hand, itemStack, getShootingPower(chargedProjectiles), (float) world.purpurConfig.crossbowProjectileOffset, null); // Purpur +- this.performShooting(level, player, hand, itemInHand, getShootingPower(chargedProjectiles), 1.0F, null); ++ this.performShooting(level, player, hand, itemInHand, getShootingPower(chargedProjectiles), (float) level.purpurConfig.crossbowProjectileOffset, null); // Purpur - Projectile offset config return InteractionResult.CONSUME; - } else if (!user.getProjectile(itemStack).isEmpty()) { + } else if (!player.getProjectile(itemInHand).isEmpty()) { this.startSoundPlayed = false; -diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java -index 79dc7cf5bfe92b4df21d164f39726dfe618331e4..6721432f9cdd11c9658c34f0ac407be217f9d276 100644 ---- a/src/main/java/net/minecraft/world/item/DyeColor.java -+++ b/src/main/java/net/minecraft/world/item/DyeColor.java -@@ -103,4 +103,10 @@ public enum DyeColor implements StringRepresentable { - public String getSerializedName() { - return this.name; +diff --git a/net/minecraft/world/item/DyeColor.java b/net/minecraft/world/item/DyeColor.java +index 028bb51c6753d44cbae76890412aa55b070f8054..597e2357040af91fc8cfb81ff7e5d8815d61898d 100644 +--- a/net/minecraft/world/item/DyeColor.java ++++ b/net/minecraft/world/item/DyeColor.java +@@ -213,4 +213,10 @@ public enum DyeColor implements StringRepresentable { + private static CraftingInput makeCraftColorInput(DyeColor first, DyeColor second) { + return CraftingInput.of(2, 1, List.of(new ItemStack(DyeItem.byColor(first)), new ItemStack(DyeItem.byColor(second)))); } + -+ // Purpur start ++ // Purpur start - Shulker spawn from bullet options + public static DyeColor random(net.minecraft.util.RandomSource random) { + return values()[random.nextInt(values().length)]; + } -+ // Purpur end ++ // Purpur end - Shulker spawn from bullet options } -diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java -index 3ddd34e5d05fa1355a2affd329d72dea216cd0e4..770bdb3fb2426083ff6785f1c38ffe9d11f898e5 100644 ---- a/src/main/java/net/minecraft/world/item/EggItem.java -+++ b/src/main/java/net/minecraft/world/item/EggItem.java -@@ -27,7 +27,7 @@ public class EggItem extends Item implements ProjectileItem { - if (world instanceof ServerLevel worldserver) { +diff --git a/net/minecraft/world/item/EggItem.java b/net/minecraft/world/item/EggItem.java +index f22a3ad6228326f837e6c83576e14d9e4fa9c882..3560091fb5840d8170d6ea80f6db734591fe85fc 100644 +--- a/net/minecraft/world/item/EggItem.java ++++ b/net/minecraft/world/item/EggItem.java +@@ -26,7 +26,7 @@ public class EggItem extends Item implements ProjectileItem { + if (level instanceof ServerLevel serverLevel) { // CraftBukkit start // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); -+ final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, 1.5F, (float) worldserver.purpurConfig.eggProjectileOffset); // Purpur - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity()); +- final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.eggProjectileOffset); // Purpur - Projectile offset config + com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity()); if (event.callEvent() && thrownEgg.attemptSpawn()) { if (event.shouldConsume()) { -diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -index 9f92b9addaedb4bae06b32226a74c8e5ddc6c2a2..0a05fedc9108fa5cf5a5d9e3395bcbbd735fc87d 100644 ---- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java -+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -@@ -27,7 +27,7 @@ public class EndCrystalItem extends Item { - BlockPos blockposition = context.getClickedPos(); - BlockState iblockdata = world.getBlockState(blockposition); - -- if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { -+ if (!world.purpurConfig.endCrystalPlaceAnywhere && !iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { +diff --git a/net/minecraft/world/item/EndCrystalItem.java b/net/minecraft/world/item/EndCrystalItem.java +index a1570503d3e4dcc9f1cd0b119ab2e60f7c63b6d8..3c84f7ce77e58ba49567aff5bc718e6928000bc2 100644 +--- a/net/minecraft/world/item/EndCrystalItem.java ++++ b/net/minecraft/world/item/EndCrystalItem.java +@@ -24,7 +24,7 @@ public class EndCrystalItem extends Item { + Level level = context.getLevel(); + BlockPos clickedPos = context.getClickedPos(); + BlockState blockState = level.getBlockState(clickedPos); +- if (!blockState.is(Blocks.OBSIDIAN) && !blockState.is(Blocks.BEDROCK)) { ++ if (!level.purpurConfig.endCrystalPlaceAnywhere && !blockState.is(Blocks.OBSIDIAN) && !blockState.is(Blocks.BEDROCK)) { // Purpur - place end crystal on any block return InteractionResult.FAIL; } else { - BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER -diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -index b232390d8ee8e449e61c0ea7f3af60df507abb97..4039d300debadf29e6c544e8b4c950b7121a02d1 100644 ---- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java + BlockPos blockPos = clickedPos.above(); final BlockPos aboveBlockPosition = blockPos; // Paper - OBFHELPER +diff --git a/net/minecraft/world/item/EnderpearlItem.java b/net/minecraft/world/item/EnderpearlItem.java +index 37c8c5484d00aee827914bcf7ec1579b38107919..8a0c32da9e41c4c5ce070b0fcbd3ae2d29a9d35c 100644 +--- a/net/minecraft/world/item/EnderpearlItem.java ++++ b/net/minecraft/world/item/EnderpearlItem.java @@ -24,7 +24,7 @@ public class EnderpearlItem extends Item { - if (world instanceof ServerLevel worldserver) { + if (level instanceof ServerLevel serverLevel) { // CraftBukkit start // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); -+ final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, 1.5F, (float) worldserver.purpurConfig.enderPearlProjectileOffset); // Purpur - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity()); +- final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.enderPearlProjectileOffset); // Purpur - Projectile offset config + com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity()); if (event.callEvent() && thrownEnderpearl.attemptSpawn()) { if (event.shouldConsume()) { -@@ -35,6 +35,7 @@ public class EnderpearlItem extends Item { - - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); - user.awardStat(Stats.ITEM_USED.get(this)); -+ user.getCooldowns().addCooldown(itemstack, user.getAbilities().instabuild ? world.purpurConfig.enderPearlCooldownCreative : world.purpurConfig.enderPearlCooldown); // Purpur +@@ -44,6 +44,7 @@ public class EnderpearlItem extends Item { + 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F) + ); + player.awardStat(Stats.ITEM_USED.get(this)); ++ player.getCooldowns().addCooldown(itemInHand, player.getAbilities().instabuild ? level.purpurConfig.enderPearlCooldownCreative : level.purpurConfig.enderPearlCooldown); // Purpur - Configurable Ender Pearl cooldown } else { // Paper end - PlayerLaunchProjectileEvent - if (user instanceof net.minecraft.server.level.ServerPlayer) { -diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -index 29a048a9b09166838616ac7ba1d31625d56b0bca..184e6d9bf393188fc1f1c7acd545b4ac6d31f6a4 100644 ---- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -66,6 +66,18 @@ public class FireworkRocketItem extends Item implements ProjectileItem { - com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); + player.containerMenu.sendAllDataToRemote(); +diff --git a/net/minecraft/world/item/FireworkRocketItem.java b/net/minecraft/world/item/FireworkRocketItem.java +index 75a9bd205f32b77c5d242cb9fac0f571ce36045a..b03f182c62c699cc222e67c1ae6eadf99c45d48d 100644 +--- a/net/minecraft/world/item/FireworkRocketItem.java ++++ b/net/minecraft/world/item/FireworkRocketItem.java +@@ -66,6 +66,19 @@ public class FireworkRocketItem extends Item implements ProjectileItem { + com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); if (event.callEvent() && delayed.attemptSpawn()) { - user.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below + player.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below + -+ // Purpur start -+ if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { -+ List list = net.minecraft.world.entity.EquipmentSlot.VALUES.stream().filter((enumitemslot) -> net.minecraft.world.entity.LivingEntity.canGlideUsing(user.getItemBySlot(enumitemslot), enumitemslot)).toList(); -+ net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, user.random); ++ // Purpur start - Implement elytra settings ++ if (level.purpurConfig.elytraDamagePerFireworkBoost > 0) { ++ List list = net.minecraft.world.entity.EquipmentSlot.VALUES.stream().filter((enumitemslot) -> net.minecraft.world.entity.LivingEntity.canGlideUsing(player.getItemBySlot(enumitemslot), enumitemslot)).toList(); ++ net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, player.random); + -+ ItemStack glideItem = user.getItemBySlot(enumitemslot); -+ if (user.canGlide()) { -+ glideItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, enumitemslot); ++ ItemStack glideItem = player.getItemBySlot(enumitemslot); ++ if (player.canGlide()) { ++ glideItem.hurtAndBreak(level.purpurConfig.elytraDamagePerFireworkBoost, player, enumitemslot); + } + } -+ // Purpur end - if (event.shouldConsume() && !user.hasInfiniteMaterials()) { - itemStack.shrink(1); // Moved up from below - } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -diff --git a/src/main/java/net/minecraft/world/item/HangingEntityItem.java b/src/main/java/net/minecraft/world/item/HangingEntityItem.java -index cdc17ad948d8ac5de62f14b1a561433d33211f32..44a7cee7df2927a923455e8cedaab59307b42506 100644 ---- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java -+++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java -@@ -75,6 +75,11 @@ public class HangingEntityItem extends Item { - - if (!customdata.isEmpty()) { - EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, customdata); -+ // Purpur start -+ if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { -+ ((Entity) object).setCustomName(null); -+ } -+ // Purpur end ++ // Purpur end - Implement elytra settings ++ + if (event.shouldConsume() && !player.hasInfiniteMaterials()) { + itemInHand.shrink(1); // Moved up from below + } else { +diff --git a/net/minecraft/world/item/HangingEntityItem.java b/net/minecraft/world/item/HangingEntityItem.java +index 85980c7e5ad63398e0f0948fb0250f580251fe63..cd9e0398876567afc337db8f6ff0ebb0ee162383 100644 +--- a/net/minecraft/world/item/HangingEntityItem.java ++++ b/net/minecraft/world/item/HangingEntityItem.java +@@ -62,6 +62,7 @@ public class HangingEntityItem extends Item { + CustomData customData = itemInHand.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY); + if (!customData.isEmpty()) { + EntityType.updateCustomEntityTag(level, player, hangingEntity, customData); ++ if (!level.purpurConfig.persistentDroppableEntityDisplayNames) hangingEntity.setCustomName(null); // Purpur - Apply display names from item forms of entities to entities and vice versa } - if (((HangingEntity) object).survives()) { -diff --git a/src/main/java/net/minecraft/world/item/HoeItem.java b/src/main/java/net/minecraft/world/item/HoeItem.java -index d2871bb4fd670ae4133d13f290b3256c9177d8e6..0936bdc945f73c7750c20a34276aead2921eeb61 100644 ---- a/src/main/java/net/minecraft/world/item/HoeItem.java -+++ b/src/main/java/net/minecraft/world/item/HoeItem.java -@@ -46,15 +46,23 @@ public class HoeItem extends DiggerItem { + if (hangingEntity.survives()) { +diff --git a/net/minecraft/world/item/HoeItem.java b/net/minecraft/world/item/HoeItem.java +index 4966b6cfb46c01691e087c466e636f58a65f45a7..acef797b884f6072ada4b9d5af53daf13273edca 100644 +--- a/net/minecraft/world/item/HoeItem.java ++++ b/net/minecraft/world/item/HoeItem.java +@@ -46,15 +46,25 @@ public class HoeItem extends DiggerItem { public InteractionResult useOn(UseOnContext context) { Level level = context.getLevel(); - BlockPos blockPos = context.getClickedPos(); -- Pair, Consumer> pair = TILLABLES.get(level.getBlockState(blockPos).getBlock()); + BlockPos clickedPos = context.getClickedPos(); +- Pair, Consumer> pair = TILLABLES.get(level.getBlockState(clickedPos).getBlock()); - if (pair == null) { -- return InteractionResult.PASS; -- } else { ++ // Purpur start - Tool actionable options ++ Block clickedBlock = level.getBlockState(clickedPos).getBlock(); ++ org.purpurmc.purpur.tool.Tillable tillable = level.purpurConfig.hoeTillables.get(clickedBlock); ++ if (tillable == null) { + return InteractionResult.PASS; + } else { - Predicate predicate = pair.getFirst(); - Consumer consumer = pair.getSecond(); -+ // Purpur start -+ Block clickedBlock = level.getBlockState(blockPos).getBlock(); -+ var tillable = level.purpurConfig.hoeTillables.get(clickedBlock); -+ if (tillable == null) { return InteractionResult.PASS; } else { + Predicate predicate = tillable.condition().predicate(); + Consumer consumer = (ctx) -> { -+ level.setBlock(blockPos, tillable.into().defaultBlockState(), 11); ++ level.setBlock(clickedPos, tillable.into().defaultBlockState(), 11); + tillable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { -+ Block.popResourceFromFace(level, blockPos, ctx.getClickedFace(), new ItemStack(drop)); ++ Block.popResourceFromFace(level, clickedPos, ctx.getClickedFace(), new ItemStack(drop)); + } + }); + }; -+ // Purpur end ++ // Purpur end - Tool actionable options if (predicate.test(context)) { Player player = context.getPlayer(); -- level.playSound(player, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); -+ if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound +- level.playSound(player, clickedPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); ++ if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, clickedPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - Tool actionable options - force sound if (!level.isClientSide) { consumer.accept(context); if (player != null) { -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 9de79c7087d3ce55631f08191a9b7994567f1788..1029499ce8fb236a23beb9dae168b82039734e59 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -503,6 +503,7 @@ public final class ItemStack implements DataComponentHolder { - world.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent - for (BlockState blockstate : blocks) { - blockstate.update(true, false); -+ ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index 7fbfbe7777cc66170cc616565a8b94f2081da50f..aa2c00be86f42a6674694a20545399e441b75199 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -461,6 +461,7 @@ public final class ItemStack implements DataComponentHolder { + serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent + for (org.bukkit.block.BlockState blockstate : blocks) { + blockstate.update(true, false); ++ ((org.bukkit.craftbukkit.block.CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur - Store placer on Block when placed + } + serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent + serverLevel.preventPoiUpdated = false; +@@ -486,6 +487,7 @@ public final class ItemStack implements DataComponentHolder { + if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically + block.onPlace(serverLevel, newPos, oldBlock, true, context); } - world.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent - world.preventPoiUpdated = false; -@@ -535,6 +536,7 @@ public final class ItemStack implements DataComponentHolder { - if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically - block.onPlace(world, newblockposition, oldBlock, true, context); - } -+ block.getBlock().forgetPlacer(); // Purpur ++ block.getBlock().forgetPlacer(); // Purpur - Store placer on Block when placed - world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point - } -@@ -681,6 +683,26 @@ public final class ItemStack implements DataComponentHolder { + serverLevel.notifyAndUpdatePhysics(newPos, null, oldBlock, block, serverLevel.getBlockState(newPos), updateFlag, net.minecraft.world.level.block.Block.UPDATE_LIMIT); // send null chunk as chunk.k() returns false by this point + } +@@ -627,6 +629,26 @@ public final class ItemStack implements DataComponentHolder { return this.isDamageableItem() && this.getDamageValue() > 0; } @@ -13970,23 +14095,25 @@ index 9de79c7087d3ce55631f08191a9b7994567f1788..1029499ce8fb236a23beb9dae168b820 + // Purpur end - Add option to mend the most damaged equipment first + public int getDamageValue() { - return Mth.clamp((Integer) this.getOrDefault(DataComponents.DAMAGE, 0), 0, this.getMaxDamage()); + return Mth.clamp(this.getOrDefault(DataComponents.DAMAGE, Integer.valueOf(0)), 0, this.getMaxDamage()); } -@@ -761,6 +783,12 @@ public final class ItemStack implements DataComponentHolder { +@@ -711,6 +733,14 @@ public final class ItemStack implements DataComponentHolder { org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent } // CraftBukkit end -+ // Purpur start ++ ++ // Purpur start - Implement elytra settings + if (this.has(DataComponents.GLIDER)) { + setDamageValue(this.getMaxDamage() - 1); + return; + } -+ // Purpur end - ++ // Purpur end - Implement elytra settings ++ this.shrink(1); - breakCallback.accept(item); -@@ -1325,6 +1353,12 @@ public final class ItemStack implements DataComponentHolder { - return !((ItemEnchantments) this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)).isEmpty(); + onBreak.accept(item); + } +@@ -1252,6 +1282,12 @@ public final class ItemStack implements DataComponentHolder { + return !this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).isEmpty(); } + // Purpur start - Config to allow unsafe enchants @@ -13996,262 +14123,252 @@ index 9de79c7087d3ce55631f08191a9b7994567f1788..1029499ce8fb236a23beb9dae168b820 + // Purpur end - Config to allow unsafe enchants + public ItemEnchantments getEnchantments() { - return (ItemEnchantments) this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); } -diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java -index 5a70111cd39af50981cfd440c021340da1de5eab..580bd63fdbf9555f867362d3c1f39f41fd750089 100644 ---- a/src/main/java/net/minecraft/world/item/Items.java -+++ b/src/main/java/net/minecraft/world/item/Items.java -@@ -363,7 +363,7 @@ public class Items { +diff --git a/net/minecraft/world/item/Items.java b/net/minecraft/world/item/Items.java +index 932565a2a3bf48b30ef53031d92afeb18623d549..d55e5e6deca43c7fddb15657fa5ceaeed8f95c67 100644 +--- a/net/minecraft/world/item/Items.java ++++ b/net/minecraft/world/item/Items.java +@@ -367,7 +367,7 @@ public class Items { public static final Item PURPUR_BLOCK = registerBlock(Blocks.PURPUR_BLOCK); public static final Item PURPUR_PILLAR = registerBlock(Blocks.PURPUR_PILLAR); public static final Item PURPUR_STAIRS = registerBlock(Blocks.PURPUR_STAIRS); - public static final Item SPAWNER = registerBlock(Blocks.SPAWNER); -+ public static final Item SPAWNER = registerBlock(Blocks.SPAWNER, org.purpurmc.purpur.item.SpawnerItem::new, new Item.Properties().rarity(Rarity.EPIC)); // Purpur ++ public static final Item SPAWNER = registerBlock(Blocks.SPAWNER, org.purpurmc.purpur.item.SpawnerItem::new, new Item.Properties().rarity(Rarity.EPIC)); // Purpur - Silk touch spawners public static final Item CREAKING_HEART = registerBlock(Blocks.CREAKING_HEART); - public static final Item CHEST = registerBlock(Blocks.CHEST, settings -> settings.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); + public static final Item CHEST = registerBlock(Blocks.CHEST, properties -> properties.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE); -@@ -2104,7 +2104,7 @@ public class Items { +@@ -2010,7 +2010,7 @@ public class Items { "sweet_berries", createBlockItemWithCustomItemName(Blocks.SWEET_BERRY_BUSH), new Item.Properties().food(Foods.SWEET_BERRIES) ); public static final Item GLOW_BERRIES = registerItem( - "glow_berries", createBlockItemWithCustomItemName(Blocks.CAVE_VINES), new Item.Properties().food(Foods.GLOW_BERRIES) -+ "glow_berries", settings -> new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, settings.useItemDescriptionPrefix()), new Item.Properties().food(Foods.GLOW_BERRIES) // Purpur ++ "glow_berries", settings -> new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, settings.useItemDescriptionPrefix()), new Item.Properties().food(Foods.GLOW_BERRIES) // Purpur - Eating glow berries adds glow effect ); - public static final Item CAMPFIRE = registerBlock(Blocks.CAMPFIRE, settings -> settings.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); - public static final Item SOUL_CAMPFIRE = registerBlock( -diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 571f2540a1e9422025efe651167e26b44b437daa..c2f3c8b3d8eeb609b6d6067c4fb404aefbf94ec5 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -194,6 +194,7 @@ public class MapItem extends Item { - public static void renderBiomePreviewMap(ServerLevel world, ItemStack map) { - MapItemSavedData mapItemSavedData = getSavedData(map, world); - if (mapItemSavedData != null) { -+ mapItemSavedData.isExplorerMap = true; // Purpur - if (world.dimension() == mapItemSavedData.dimension) { - int i = 1 << mapItemSavedData.scale; - int j = mapItemSavedData.centerX; -diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java -index 7153d9ed12276a0f2d8b8a17c79734aa25ed1fa5..dc49ea6454e04ae8ec68af12c4bf2ff022540671 100644 ---- a/src/main/java/net/minecraft/world/item/MinecartItem.java -+++ b/src/main/java/net/minecraft/world/item/MinecartItem.java -@@ -35,8 +35,9 @@ public class MinecartItem extends Item { - BlockState iblockdata = world.getBlockState(blockposition); - - if (!iblockdata.is(BlockTags.RAILS)) { + public static final Item CAMPFIRE = registerBlock( + Blocks.CAMPFIRE, properties -> properties.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY) +diff --git a/net/minecraft/world/item/MapItem.java b/net/minecraft/world/item/MapItem.java +index 8795d54cff569c911e0a535f38a0ec4130f7b4d5..309392d414ecbe60474abd0af534184740951707 100644 +--- a/net/minecraft/world/item/MapItem.java ++++ b/net/minecraft/world/item/MapItem.java +@@ -196,6 +196,7 @@ public class MapItem extends Item { + public static void renderBiomePreviewMap(ServerLevel serverLevel, ItemStack stack) { + MapItemSavedData savedData = getSavedData(stack, serverLevel); + if (savedData != null) { ++ savedData.isExplorerMap = true; // Purpur - Explorer Map API + if (serverLevel.dimension() == savedData.dimension) { + int i = 1 << savedData.scale; + int i1 = savedData.centerX; +diff --git a/net/minecraft/world/item/MinecartItem.java b/net/minecraft/world/item/MinecartItem.java +index 620069daba04d48b57fc933328eda77f6ca9333e..0403b9b01994269d394820e8c8710ba1b9808bf0 100644 +--- a/net/minecraft/world/item/MinecartItem.java ++++ b/net/minecraft/world/item/MinecartItem.java +@@ -30,8 +30,9 @@ public class MinecartItem extends Item { + BlockPos clickedPos = context.getClickedPos(); + BlockState blockState = level.getBlockState(clickedPos); + if (!blockState.is(BlockTags.RAILS)) { - return InteractionResult.FAIL; - } else { -+ if (!world.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL; // Purpur -+ if (iblockdata.isSolid()) blockposition = blockposition.relative(context.getClickedFace()); -+ } // else { // Purpur - place minecarts anywhere - ItemStack itemstack = context.getItemInHand(); - RailShape blockpropertytrackposition = iblockdata.getBlock() instanceof BaseRailBlock ? (RailShape) iblockdata.getValue(((BaseRailBlock) iblockdata.getBlock()).getShapeProperty()) : RailShape.NORTH_SOUTH; - double d0 = 0.0D; -@@ -80,6 +81,6 @@ public class MinecartItem extends Item { - itemstack.shrink(1); ++ if (!level.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL; // Purpur - Minecart settings and WASD controls ++ if (blockState.isSolid()) clickedPos = clickedPos.relative(context.getClickedFace()); ++ } // else { // Purpur - Minecart settings and WASD controls + ItemStack itemInHand = context.getItemInHand(); + RailShape railShape = blockState.getBlock() instanceof BaseRailBlock + ? blockState.getValue(((BaseRailBlock)blockState.getBlock()).getShapeProperty()) +@@ -72,6 +73,6 @@ public class MinecartItem extends Item { + itemInHand.shrink(1); return InteractionResult.SUCCESS; } - } -+ // } // Purpur - place minecarts anywhere ++ // } // Purpur - Minecart settings and WASD controls } } -diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java -index 000d1863bfba98b5132dfc6743362d687b2f54f3..20fece9908382f40b4082f7b1fb7d41914ae31be 100644 ---- a/src/main/java/net/minecraft/world/item/NameTagItem.java -+++ b/src/main/java/net/minecraft/world/item/NameTagItem.java -@@ -23,6 +23,7 @@ public class NameTagItem extends Item { - if (!event.callEvent()) return InteractionResult.PASS; +diff --git a/net/minecraft/world/item/NameTagItem.java b/net/minecraft/world/item/NameTagItem.java +index 438d98b11275d792f18301c643254dfb733c0dd6..98fa29283994ec50c15591b13331bf9ae87683c6 100644 +--- a/net/minecraft/world/item/NameTagItem.java ++++ b/net/minecraft/world/item/NameTagItem.java +@@ -24,6 +24,7 @@ public class NameTagItem extends Item { + LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null); -+ if (user.level().purpurConfig.armorstandFixNametags && entity instanceof net.minecraft.world.entity.decoration.ArmorStand) entity.setCustomNameVisible(true); // Purpur ++ if (player.level().purpurConfig.armorstandFixNametags && target instanceof net.minecraft.world.entity.decoration.ArmorStand) target.setCustomNameVisible(true); // Purpur - Set name visible when using a Name Tag on an Armor Stand if (event.isPersistent() && newEntity instanceof Mob mob) { - // Paper end - Add PlayerNameEntityEvent + // Paper end - Add PlayerNameEntityEvent mob.setPersistenceRequired(); -diff --git a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -index 78ba170a83f8c026bd110eae494c52577182ed61..c2ae50872cead7202246b9cce4db6e0a81e1cf5f 100644 ---- a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -+++ b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -@@ -105,6 +105,8 @@ public abstract class ProjectileWeaponItem extends Item { - entityarrow.setCritArrow(true); +diff --git a/net/minecraft/world/item/ProjectileWeaponItem.java b/net/minecraft/world/item/ProjectileWeaponItem.java +index d3e45d6ad45b8f0b681d422edd0edd51ca07f252..f20c38c1ff978d00dc0c9810c050506deed44ebd 100644 +--- a/net/minecraft/world/item/ProjectileWeaponItem.java ++++ b/net/minecraft/world/item/ProjectileWeaponItem.java +@@ -108,6 +108,8 @@ public abstract class ProjectileWeaponItem extends Item { + abstractArrow.setCritArrow(true); } -+ entityarrow.setActualEnchantments(weaponStack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting ++ abstractArrow.setActualEnchantments(weapon.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting + - return entityarrow; + return abstractArrow; } -diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java -index 55c18f182166f4905d623d6f5e909eefd5ed2483..d10c4705cc9e7faabd4a5619e1da107231bdb37e 100644 ---- a/src/main/java/net/minecraft/world/item/ShovelItem.java -+++ b/src/main/java/net/minecraft/world/item/ShovelItem.java +diff --git a/net/minecraft/world/item/ShovelItem.java b/net/minecraft/world/item/ShovelItem.java +index 75bbe901e79d9ba3250ed2426a36c1c3363c19c1..e93f35f3bf82994c9e901341c4d6194ef574e3c6 100644 +--- a/net/minecraft/world/item/ShovelItem.java ++++ b/net/minecraft/world/item/ShovelItem.java @@ -47,9 +47,12 @@ public class ShovelItem extends DiggerItem { - BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); - BlockState blockState3 = null; + BlockState blockState1 = FLATTENABLES.get(blockState.getBlock()); + BlockState blockState2 = null; Runnable afterAction = null; // Paper -- if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { -- afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper -- blockState3 = blockState2; -+ // Purpur start -+ var flattenable = level.purpurConfig.shovelFlattenables.get(blockState.getBlock()); -+ if (flattenable != null && level.getBlockState(blockPos.above()).isAir()) { -+ afterAction = () -> {if (!FLATTENABLES.containsKey(blockState.getBlock())) level.playSound(null, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);}; // Paper -+ blockState3 = flattenable.into().defaultBlockState(); -+ // Purpur end ++ org.purpurmc.purpur.tool.Flattenable flattenable = level.purpurConfig.shovelFlattenables.get(blockState.getBlock()); // Purpur - Tool actionable options + if (blockState1 != null && level.getBlockState(clickedPos.above()).isAir()) { +- afterAction = () -> level.playSound(player, clickedPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper +- blockState2 = blockState1; ++ // Purpur start - Tool actionable options ++ afterAction = () -> {if (!FLATTENABLES.containsKey(blockState.getBlock())) level.playSound(player, clickedPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);}; // Paper ++ blockState2 = flattenable.into().defaultBlockState(); ++ // Purpur end - Tool actionable options } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { afterAction = () -> { // Paper if (!level.isClientSide()) { -diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java -index 57872ebef6beb8cdc03c9f8f19de94652ee19062..60a064c26de0566aaf9f8be886402e291c03ca3b 100644 ---- a/src/main/java/net/minecraft/world/item/SnowballItem.java -+++ b/src/main/java/net/minecraft/world/item/SnowballItem.java -@@ -27,7 +27,7 @@ public class SnowballItem extends Item implements ProjectileItem { - // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); - if (world instanceof ServerLevel worldserver) { +diff --git a/net/minecraft/world/item/SnowballItem.java b/net/minecraft/world/item/SnowballItem.java +index 8eec16040fb9ae6bcccbd71bbe93521cdce5ccce..38b82537209449407922491506a7ca6224229ca9 100644 +--- a/net/minecraft/world/item/SnowballItem.java ++++ b/net/minecraft/world/item/SnowballItem.java +@@ -26,7 +26,7 @@ public class SnowballItem extends Item implements ProjectileItem { + // CraftBukkit start - moved down + if (level instanceof ServerLevel serverLevel) { // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); -+ final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, 1.5F, (float) worldserver.purpurConfig.snowballProjectileOffset); // Purpur - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity()); +- final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, serverLevel, itemInHand, player, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F); ++ final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, serverLevel, itemInHand, player, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.snowballProjectileOffset); // Purpur - Projectile offset config + com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity()); if (event.callEvent() && snowball.attemptSpawn()) { - user.awardStat(Stats.ITEM_USED.get(this)); -diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -index 9956ed42df55daa6d97fd6e3ab5368dad91cfaf0..e0e746d6c78421b40777125ba49f0a04809f5415 100644 ---- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java -+++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -@@ -73,6 +73,24 @@ public class SpawnEggItem extends Item { - Spawner spawner = (Spawner) tileentity; - - entitytypes = this.getType(itemstack); -+ -+ // Purpur start + player.awardStat(Stats.ITEM_USED.get(this)); +diff --git a/net/minecraft/world/item/SpawnEggItem.java b/net/minecraft/world/item/SpawnEggItem.java +index bdb7bfd5212f066517587877a331485afc898cbf..f4728999e41ce01eefe6ce2f359a7c32a268105f 100644 +--- a/net/minecraft/world/item/SpawnEggItem.java ++++ b/net/minecraft/world/item/SpawnEggItem.java +@@ -57,6 +57,23 @@ public class SpawnEggItem extends Item { + if (level.getBlockEntity(clickedPos) instanceof Spawner spawner) { + if (level.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation + EntityType type = this.getType(level.registryAccess(), itemInHand); ++ // Purpur start - PlayerSetSpawnerTypeWithEggEvent + if (spawner instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity) { -+ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); -+ org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); ++ org.bukkit.block.Block bukkitBlock = level.getWorld().getBlockAt(clickedPos.getX(), clickedPos.getY(), clickedPos.getZ()); ++ org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(type.getName())); + if (!event.callEvent()) { + return InteractionResult.FAIL; + } -+ entitytypes = EntityType.getFromBukkitType(event.getEntityType()); ++ type = EntityType.getFromBukkitType(event.getEntityType()); + } else if (spawner instanceof net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity) { -+ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); -+ org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.TrialSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); ++ org.bukkit.block.Block bukkitBlock = level.getWorld().getBlockAt(clickedPos.getX(), clickedPos.getY(), clickedPos.getZ()); ++ org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.TrialSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(type.getName())); + if (!event.callEvent()) { + return InteractionResult.FAIL; + } -+ entitytypes = EntityType.getFromBukkitType(event.getEntityType()); ++ type = EntityType.getFromBukkitType(event.getEntityType()); + } -+ // Purpur end - spawner.setEntityId(entitytypes, world.getRandom()); - world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); - world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_CHANGE, blockposition); -diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -index fa9d2ae44fcdd06f8f33cd14ffca422b20a01451..ffbc71ca2a27800d7758e3db339bf06a39ef1f11 100644 ---- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -+++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -@@ -21,7 +21,7 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem { - ItemStack itemStack = user.getItemInHand(hand); - if (world instanceof ServerLevel serverLevel) { ++ // Purpur end - PlayerSetSpawnerTypeWithEggEvent + spawner.setEntityId(type, level.getRandom()); + level.sendBlockUpdated(clickedPos, blockState, blockState, 3); + level.gameEvent(context.getPlayer(), GameEvent.BLOCK_CHANGE, clickedPos); +diff --git a/net/minecraft/world/item/ThrowablePotionItem.java b/net/minecraft/world/item/ThrowablePotionItem.java +index 2a72ad6686f28127a85faf02024cc6119fa76c58..6a8d6c9835754d142841a6798b7692e64b237fe5 100644 +--- a/net/minecraft/world/item/ThrowablePotionItem.java ++++ b/net/minecraft/world/item/ThrowablePotionItem.java +@@ -23,7 +23,7 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem { + ItemStack itemInHand = player.getItemInHand(hand); + if (level instanceof ServerLevel serverLevel) { // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, 0.5F, 1.0F); -+ final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, 0.5F, (float) serverLevel.purpurConfig.throwablePotionProjectileOffset); // Purpur - // Paper start - PlayerLaunchProjectileEvent - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity()); +- final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F); ++ final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.throwablePotionProjectileOffset); // Purpur - Projectile offset config + com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity()); if (event.callEvent() && thrownPotion.attemptSpawn()) { -diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java -index 8b9a93ef71164cce8a616735b71d96d37e83b1a8..23e04d0d68ffa0e07ab295e7121f8a4963f4914a 100644 ---- a/src/main/java/net/minecraft/world/item/TridentItem.java -+++ b/src/main/java/net/minecraft/world/item/TridentItem.java -@@ -88,7 +88,7 @@ public class TridentItem extends Item implements ProjectileItem { - // itemstack.hurtWithoutBreaking(1, entityhuman); // CraftBukkit - moved down - if (f == 0.0F) { + if (event.shouldConsume()) { +diff --git a/net/minecraft/world/item/TridentItem.java b/net/minecraft/world/item/TridentItem.java +index 23284dbeff327d1b8dc89f3a0dc0ee549cec2daa..7ea7db834e7b627a1d7d37ca87cd43eb61408565 100644 +--- a/net/minecraft/world/item/TridentItem.java ++++ b/net/minecraft/world/item/TridentItem.java +@@ -90,7 +90,7 @@ public class TridentItem extends Item implements ProjectileItem { + // stack.hurtWithoutBreaking(1, player); // CraftBukkit - moved down + if (tridentSpinAttackStrength == 0.0F) { + Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent +- ThrownTrident::new, serverLevel, stack, player, 0.0F, 2.5F, 1.0F ++ ThrownTrident::new, serverLevel, stack, player, 0.0F, 2.5F, (float) serverLevel.purpurConfig.tridentProjectileOffset // Purpur - Projectile offset config + ); // Paper start - PlayerLaunchProjectileEvent -- Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); -+ Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, (float) worldserver.purpurConfig.tridentProjectileOffset); // Purpur - // Paper start - PlayerLaunchProjectileEvent - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity()); - if (!event.callEvent() || !tridentDelayed.attemptSpawn()) { -@@ -100,6 +100,9 @@ public class TridentItem extends Item implements ProjectileItem { + com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity()); +@@ -101,6 +101,9 @@ public class TridentItem extends Item implements ProjectileItem { return false; } - ThrownTrident entitythrowntrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent + ThrownTrident thrownTrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent + -+ entitythrowntrident.setActualEnchantments(stack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting ++ thrownTrident.setActualEnchantments(stack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting + - if (event.shouldConsume()) stack.hurtWithoutBreaking(1, entityhuman); // Paper - PlayerLaunchProjectileEvent - entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved + if (event.shouldConsume()) stack.hurtWithoutBreaking(1, player); // Paper - PlayerLaunchProjectileEvent + thrownTrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved // CraftBukkit end -@@ -132,6 +135,18 @@ public class TridentItem extends Item implements ProjectileItem { - f4 *= f / f6; - f5 *= f / f6; - org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(entityhuman, stack, f3, f4, f5); // CraftBukkit +@@ -130,6 +133,18 @@ public class TridentItem extends Item implements ProjectileItem { + f1 *= tridentSpinAttackStrength / squareRoot; + f2 *= tridentSpinAttackStrength / squareRoot; + org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(player, stack, f, f1, f2); // CraftBukkit + -+ // Purpur start -+ List list = EquipmentSlot.VALUES.stream().filter((enumitemslot) -> LivingEntity.canGlideUsing(entityhuman.getItemBySlot(enumitemslot), enumitemslot)).toList(); ++ // Purpur start - Implement elytra settings ++ List list = EquipmentSlot.VALUES.stream().filter((enumitemslot) -> LivingEntity.canGlideUsing(entity.getItemBySlot(enumitemslot), enumitemslot)).toList(); + if (!list.isEmpty()) { -+ EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, entityhuman.random); -+ ItemStack glideItem = entityhuman.getItemBySlot(enumitemslot); -+ if (glideItem.has(net.minecraft.core.component.DataComponents.GLIDER) && world.purpurConfig.elytraDamagePerTridentBoost > 0) { -+ glideItem.hurtAndBreak(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, enumitemslot); ++ EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, entity.random); ++ ItemStack glideItem = entity.getItemBySlot(enumitemslot); ++ if (glideItem.has(net.minecraft.core.component.DataComponents.GLIDER) && level.purpurConfig.elytraDamagePerTridentBoost > 0) { ++ glideItem.hurtAndBreak(level.purpurConfig.elytraDamagePerTridentBoost, entity, enumitemslot); + } + } -+ // Purpur end ++ // Purpur end - Implement elytra settings + - entityhuman.push((double) f3, (double) f4, (double) f5); - entityhuman.startAutoSpinAttack(20, 8.0F, stack); - if (entityhuman.onGround()) { -diff --git a/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java b/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java -index 0651c2af040e3f248860cfb3c5effce91589380e..d884df481b4bbb978113a4ac7a1feac31cf2f951 100644 ---- a/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java -+++ b/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java -@@ -24,6 +24,12 @@ public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect { + player.push(f, f1, f2); + player.startAutoSpinAttack(20, 8.0F, stack); + if (player.onGround()) { +diff --git a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java +index 41e1e076a4567d3d3202cf8e426a1ebb391d85e8..2d710dd3f20cbea06c16f14a558b575b369c6ca2 100644 +--- a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java ++++ b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java +@@ -20,6 +20,12 @@ public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect { @Override // CraftBukkit start - public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { -+ // Purpur start -+ net.minecraft.world.effect.MobEffectInstance badOmen = entityliving.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN); -+ if (!world.purpurConfig.milkCuresBadOmen && itemstack.is(net.minecraft.world.item.Items.MILK_BUCKET) && badOmen != null) { -+ return entityliving.removeAllEffects(cause) && entityliving.addEffect(badOmen); + public boolean apply(Level level, ItemStack stack, LivingEntity entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { ++ // Purpur start - Option to toggle milk curing bad omen ++ net.minecraft.world.effect.MobEffectInstance badOmen = entity.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN); ++ if (!level.purpurConfig.milkCuresBadOmen && stack.is(net.minecraft.world.item.Items.MILK_BUCKET) && badOmen != null) { ++ return entity.removeAllEffects(cause) && entity.addEffect(badOmen); + } -+ // Purpur end - return entityliving.removeAllEffects(cause); ++ // Purpur end - Option to toggle milk curing bad omen + return entity.removeAllEffects(cause); // CraftBukkit end } -diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -index 812f919a7a7e309c8513f44104f092496037608f..10730b307971915f52b3e41068a864b8ee1352b4 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -+++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -@@ -45,6 +45,7 @@ public final class Ingredient implements Predicate { +diff --git a/net/minecraft/world/item/crafting/Ingredient.java b/net/minecraft/world/item/crafting/Ingredient.java +index 879c8fe1f20decc793cfa39e686b61d521bd76ba..e9189347cc145bc6dcca2c66d1bd0f23ea71516d 100644 +--- a/net/minecraft/world/item/crafting/Ingredient.java ++++ b/net/minecraft/world/item/crafting/Ingredient.java +@@ -36,6 +36,7 @@ public final class Ingredient implements StackedContents.IngredientInfo itemStacks; -+ public Predicate predicate; // Purpur + @javax.annotation.Nullable + private java.util.Set itemStacks; // Paper - Improve exact choice recipe ingredients ++ public Predicate predicate; // Purpur - Add predicate to recipe's ExactChoice ingredient public boolean isExact() { return this.itemStacks != null; -@@ -100,6 +101,11 @@ public final class Ingredient implements Predicate { - - return false; +@@ -87,6 +88,11 @@ public final class Ingredient implements StackedContents.IngredientInfo> list = this.items(); - -diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -index f99b87cf70df7eaac13d46f4e0234b1e6483d342..73241113e50dc8be89ef8850d49d95ec31fb194f 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; - import java.util.ArrayList; - import java.util.Collection; - import java.util.List; -+import java.util.Map; - import java.util.Optional; - import java.util.function.BiConsumer; - import java.util.function.Consumer; -@@ -578,4 +579,58 @@ public class EnchantmentHelper { + return stack.is(this.values); + } +diff --git a/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/net/minecraft/world/item/enchantment/EnchantmentHelper.java +index 2847bf6c00366c6c2ffb1fc4d3030c1c3e4502eb..848d50f1bafdd885d58b50d9314944c372617db2 100644 +--- a/net/minecraft/world/item/enchantment/EnchantmentHelper.java ++++ b/net/minecraft/world/item/enchantment/EnchantmentHelper.java +@@ -602,4 +602,58 @@ public class EnchantmentHelper { interface EnchantmentVisitor { void accept(Holder enchantment, int level); } @@ -14310,104 +14427,104 @@ index f99b87cf70df7eaac13d46f4e0234b1e6483d342..73241113e50dc8be89ef8850d49d95ec + } + // Purpur end - Add option to mend the most damaged equipment first } -diff --git a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -index cfc6a657cae92c68868a76c1b7b1febe2a16e9f4..a12c08da793139e39dc11c213c94796b83bd8240 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -@@ -35,7 +35,7 @@ public class ItemEnchantments implements TooltipProvider { +diff --git a/net/minecraft/world/item/enchantment/ItemEnchantments.java b/net/minecraft/world/item/enchantment/ItemEnchantments.java +index cc87a5026a65192ae1f97b2bd03a918b75df0698..e10f1ffd7b3f17b5c0b6655c0b3edf21e938952a 100644 +--- a/net/minecraft/world/item/enchantment/ItemEnchantments.java ++++ b/net/minecraft/world/item/enchantment/ItemEnchantments.java +@@ -32,7 +32,7 @@ public class ItemEnchantments implements TooltipProvider { private static final java.util.Comparator> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName); - public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true); + public static final ItemEnchantments EMPTY = new ItemEnchantments(new it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true); // Paper end - private static final Codec LEVEL_CODEC = Codec.intRange(1, 255); -+ private static final Codec LEVEL_CODEC = Codec.intRange(1, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)); // Purpur - private static final Codec>> LEVELS_CODEC = Codec.unboundedMap( - Enchantment.CODEC, LEVEL_CODEC - )// Paper start - sort enchantments -@@ -69,7 +69,7 @@ public class ItemEnchantments implements TooltipProvider { ++ private static final Codec LEVEL_CODEC = Codec.intRange(1, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)); // Purpur - Add toggle for enchant level clamping + // Paper start - sort enchantments + private static final Codec>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC) + .xmap(m -> { +@@ -65,7 +65,7 @@ public class ItemEnchantments implements TooltipProvider { for (Entry> entry : enchantments.object2IntEntrySet()) { - int i = entry.getIntValue(); -- if (i < 0 || i > 255) { -+ if (i < 0 || i > (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)) { // Purpur - throw new IllegalArgumentException("Enchantment " + entry.getKey() + " has invalid level " + i); + int intValue = entry.getIntValue(); +- if (intValue < 0 || intValue > 255) { ++ if (intValue < 0 || intValue > (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)) { // Purpur - Add toggle for enchant level clamping + throw new IllegalArgumentException("Enchantment " + entry.getKey() + " has invalid level " + intValue); } } -@@ -164,13 +164,13 @@ public class ItemEnchantments implements TooltipProvider { +@@ -160,13 +160,13 @@ public class ItemEnchantments implements TooltipProvider { if (level <= 0) { this.enchantments.removeInt(enchantment); } else { - this.enchantments.put(enchantment, Math.min(level, 255)); -+ this.enchantments.put(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767))); // Purpur ++ this.enchantments.put(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767))); // Purpur - Add toggle for enchant level clamping } } public void upgrade(Holder enchantment, int level) { if (level > 0) { - this.enchantments.merge(enchantment, Math.min(level, 255), Integer::max); -+ this.enchantments.merge(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)), Integer::max); // Purpur ++ this.enchantments.merge(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)), Integer::max); // Purpur - Add toggle for enchant level clamping } } -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 0efc8d997b34302c3e0a5d7ec73a11a940dbeefe..af157881d440b34cfe79fbc9b03cc9ef28515eb8 100644 ---- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -@@ -131,7 +131,12 @@ public class MerchantOffer { +diff --git a/net/minecraft/world/item/trading/MerchantOffer.java b/net/minecraft/world/item/trading/MerchantOffer.java +index 6c06350751db7543d5bde7723121d9d9dbb79071..455ceb46f1ad99f20bda2c31bc252426dc95719a 100644 +--- a/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/net/minecraft/world/item/trading/MerchantOffer.java +@@ -143,7 +143,12 @@ public class MerchantOffer { } public void updateDemand() { - this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 -+ // Purpur start ++ // Purpur start - Configurable minimum demand for trades + this.updateDemand(0); + } + public void updateDemand(int minimumDemand) { + this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 -+ // Purpur end ++ // Purpur end - Configurable minimum demand for trades } public ItemStack assemble() { -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 7de66aa435dd36899b80f4ecc64480680e474d94..bb4411cfdf1bc7adc12c2f918d2eec830299f38b 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -59,6 +59,7 @@ public abstract class BaseSpawner { +diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java +index 425fcc7872c6ebd2156be8bea1d516ce7aeeb280..8de482367f3d9d91048b7c85cbaefcda9f9fbcdc 100644 +--- a/net/minecraft/world/level/BaseSpawner.java ++++ b/net/minecraft/world/level/BaseSpawner.java +@@ -52,6 +52,7 @@ public abstract class BaseSpawner { } - public boolean isNearPlayer(Level world, BlockPos pos) { -+ if (world.purpurConfig.spawnerDeactivateByRedstone && world.hasNeighborSignal(pos)) return false; // Purpur - return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API + public boolean isNearPlayer(Level level, BlockPos pos) { ++ if (level.purpurConfig.spawnerDeactivateByRedstone && level.hasNeighborSignal(pos)) return false; // Purpur - Redstone deactivates spawners + return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API } -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 5d7a6e4b73f032db356e7ec369b150013e940ee6..6b2cda6d578a0983b2401ea20629275431018433 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -184,7 +184,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst +diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java +index e81195df621159da67136f020fa7a6d39d1ee5ed..f41e41d01aa42f3578ffb3bc888416e74d17cd1d 100644 +--- a/net/minecraft/world/level/EntityGetter.java ++++ b/net/minecraft/world/level/EntityGetter.java +@@ -185,7 +185,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst - default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { + default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) { for (Player player : this.players()) { - if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { -+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur ++ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur - AFK API double d = player.distanceToSqr(x, y, z); - if (range < 0.0 || d < range * range) { + if (distance < 0.0 || d < distance * distance) { return true; -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index dd4d40a14cef268fe992f2c5ba523748fb619bd9..c3b9a9904f7b34df3dbd0a25e52946ab45f0f4d6 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -178,6 +178,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index d732a021351d80210e6411565c7a5e9d1b277f0f..78654e7ae26c0b2b2512f4e29a331e2ead2d6916 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -174,6 +174,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl // Gale end - Gale configuration - public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray -+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur + public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray ++ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; private org.spigotmc.TickLimiter tileLimiter; -@@ -187,6 +188,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -183,6 +184,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Gale - Pufferfish - move random tick random -+ // Purpur start ++ // Purpur start - Add adjustable breeding cooldown to config + private com.google.common.cache.Cache playerBreedingCooldowns; + + private com.google.common.cache.Cache getNewBreedingCooldownCache() { @@ -14448,26 +14565,26 @@ index dd4d40a14cef268fe992f2c5ba523748fb619bd9..c3b9a9904f7b34df3dbd0a25e52946ab + return java.util.Objects.hash(playerUUID, animalType); + } + } -+ // Purpur end ++ // Purpur end - Add adjustable breeding cooldown to config + public CraftWorld getWorld() { return this.world; } -@@ -847,6 +891,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot +@@ -860,6 +904,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration -+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur -+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur ++ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // Purpur - Purpur config files ++ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); -@@ -2040,4 +2086,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -2131,4 +2177,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl return this.id; } } + -+ // Purpur start ++ // Purpur start - Add allow water in end world option + public boolean isNether() { + return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER; + } @@ -14475,40 +14592,40 @@ index dd4d40a14cef268fe992f2c5ba523748fb619bd9..c3b9a9904f7b34df3dbd0a25e52946ab + public boolean isTheEnd() { + return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END; + } -+ // Purpur end ++ // Purpur end - Add allow water in end world option } -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 2febef53fc1c2bbacab94feb5de4e6934e47038e..88b3715df673c6d12aea69fde075ad3caa8c51e8 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -273,7 +273,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 = world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur - - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); -diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java -index 4de71492339c3d31a34f1fa2aa75e8b216485ef0..7b0a7bc89ae5a459a7db8d9ff728ddf5eb7e1024 100644 ---- a/src/main/java/net/minecraft/world/level/ServerExplosion.java -+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java -@@ -309,7 +309,7 @@ public class ServerExplosion implements Explosion { - public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) { - this.level = world; - this.source = entity; -- this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values -+ this.radius = (float) (world == null || world.purpurConfig.explosionClampRadius ? Math.max(power, 0.0) : power); // CraftBukkit - clamp bad values // Purpur - this.center = pos; - this.fire = createFire; - this.blockInteraction = destructionType; -@@ -664,10 +664,27 @@ public class ServerExplosion implements Explosion { +diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java +index 4bee1ba137d078563cedfdd184a8b4603df17487..d3f5242fc66529bf3137da4d505a6cf55e749e43 100644 +--- a/net/minecraft/world/level/NaturalSpawner.java ++++ b/net/minecraft/world/level/NaturalSpawner.java +@@ -260,7 +260,7 @@ public final class NaturalSpawner { + mutableBlockPos.set(x, y, z); + double d = x + 0.5; + double d1 = z + 0.5; +- Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, false); ++ Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, level.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur - mob spawning option to ignore creative players + if (nearestPlayer != null) { + double d2 = nearestPlayer.distanceToSqr(d, y, d1); + if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index ea22342896a608036042b5f5800302eae29f6c40..f2ef7af916318d9b83395455b9f25b61401e3017 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -317,7 +317,7 @@ public class ServerExplosion implements Explosion { + ) { + this.level = level; + this.source = source; +- this.radius = (float) Math.max(radius, 0.0); // CraftBukkit - clamp bad values ++ this.radius = (float) (level == null || level.purpurConfig.explosionClampRadius ? Math.max(radius, 0.0) : radius); // CraftBukkit - clamp bad values // Purpur - Config to remove explosion radius clamp + this.center = center; + this.fire = fire; + this.blockInteraction = blockInteraction; +@@ -647,10 +647,27 @@ public class ServerExplosion implements Explosion { public void explode() { // CraftBukkit start - if (this.radius < 0.1F) { -+ if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur ++ if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur - Config to remove explosion radius clamp return; } // CraftBukkit end @@ -14528,19 +14645,19 @@ index 4de71492339c3d31a34f1fa2aa75e8b216485ef0..7b0a7bc89ae5a459a7db8d9ff728ddf5 + return; + } + } -+ // Purpur end ++ // Purpur end - Add PreExplodeEvents // Paper start - collision optimisations this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; -diff --git a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -index 50c907c962f936d2035bb7550750cdbd220b29c2..f9a2d2d4f798efa0d691996ec5ff7fe00260b36c 100644 ---- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java +diff --git a/net/minecraft/world/level/block/AnvilBlock.java b/net/minecraft/world/level/block/AnvilBlock.java +index a788ebfac915a87df49b31467844fcb087a9b89b..c2eaacb4657d7329cc16e4f3d36fa545c7e4c2b7 100644 +--- a/net/minecraft/world/level/block/AnvilBlock.java ++++ b/net/minecraft/world/level/block/AnvilBlock.java @@ -59,6 +59,53 @@ public class AnvilBlock extends FallingBlock { - return this.defaultBlockState().setValue(FACING, ctx.getHorizontalDirection().getClockWise()); + return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getClockWise()); } -+ // Purpur start - repairable/damageable anvils ++ // Purpur start - Anvil repair/damage options + @Override + protected net.minecraft.world.InteractionResult useItemOn(final net.minecraft.world.item.ItemStack stack, final BlockState state, final Level world, final BlockPos pos, final Player player, final net.minecraft.world.InteractionHand hand, final BlockHitResult hit) { + if (world.purpurConfig.anvilRepairIngotsAmount > 0 && stack.is(net.minecraft.world.item.Items.IRON_INGOT)) { @@ -14585,21 +14702,21 @@ index 50c907c962f936d2035bb7550750cdbd220b29c2..f9a2d2d4f798efa0d691996ec5ff7fe0 + } + return net.minecraft.world.InteractionResult.TRY_WITH_EMPTY_HAND; + } -+ // Purpur end ++ // Purpur end - Anvil repair/damage options + @Override - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - if (!world.isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -index affbbf6abc6bc09ecb652c1dee92aa297458bc39..febc05dc39741807127cba4a2a55aaad62b0800c 100644 ---- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { + if (!level.isClientSide) { +diff --git a/net/minecraft/world/level/block/AzaleaBlock.java b/net/minecraft/world/level/block/AzaleaBlock.java +index 4555eb8e1be743f83b3f5b57b6e5f3301f9b4ee9..e9d92d29c5b403dd33463e968822e3414e7bf278 100644 +--- a/net/minecraft/world/level/block/AzaleaBlock.java ++++ b/net/minecraft/world/level/block/AzaleaBlock.java @@ -50,6 +50,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { @Override - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { -+ // Purpur start -+ growTree(world, random, pos, state); + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ // Purpur start - Chance for azalea blocks to grow into trees naturally ++ growTree(level, random, pos, state); + } + + @Override @@ -14610,113 +14727,111 @@ index affbbf6abc6bc09ecb652c1dee92aa297458bc39..febc05dc39741807127cba4a2a55aaad + } + } + -+ private void growTree(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { -+ // Purpur end - TreeGrower.AZALEA.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); ++ private void growTree(ServerLevel level, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { ++ // Purpur end - Chance for azalea blocks to grow into trees naturally + TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); } -diff --git a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -index d7ca7a43d2d5f8cad416156fd40588cdd6634f52..231338adda19f57bf1a95379cc2e14341d4068d0 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java +diff --git a/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java +index 045d85b1267e20ca66ef08eb981f167c64d8c780..e804ca8d6c9c9b8d9f982a970cc3edddf5c03aa1 100644 +--- a/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java ++++ b/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java @@ -39,6 +39,7 @@ public abstract class BaseCoralPlantTypeBlock extends Block implements SimpleWat } - protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) { -+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur + protected static boolean scanForWater(BlockState state, BlockGetter level, BlockPos pos) { ++ if (!((net.minecraft.world.level.LevelAccessor) level).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die if (state.getValue(WATERLOGGED)) { return true; } else { -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 c02c4834ace843633b77fb43eeadd3ddc7b1f743..c130d316e87f1f896d33ab43831063a89e3bef2b 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -106,7 +106,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock +diff --git a/net/minecraft/world/level/block/BedBlock.java b/net/minecraft/world/level/block/BedBlock.java +index c23c255cefe7c5be618bbe97a99ae3215d8e48c0..60c68936cd80e0137a1c92d6d1687321855c040f 100644 +--- a/net/minecraft/world/level/block/BedBlock.java ++++ b/net/minecraft/world/level/block/BedBlock.java +@@ -100,7 +100,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + } - Vec3 vec3d = pos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur + Vec3 center = pos.getCenter(); +- level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); ++ if (level.purpurConfig.bedExplode) level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect); // Purpur - Implement bed explosion options return InteractionResult.SUCCESS_SERVER; - } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { - if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first -@@ -159,7 +159,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = blockposition.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state -+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // CraftBukkit - add state // Purpur - return InteractionResult.SUCCESS; - } + } else if (state.getValue(OCCUPIED)) { + if (!BedBlock.canSetSpawn(level)) return this.explodeBed(state, level, pos); // Paper - check explode first +@@ -148,7 +148,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock } -@@ -183,7 +183,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + + Vec3 center = pos.getCenter(); +- level.explode(null, level.damageSources().badRespawnPointExplosion(center, blockState), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state ++ if (level.purpurConfig.bedExplode) level.explode(null, level.damageSources().badRespawnPointExplosion(center, blockState), null, center, (float) level.purpurConfig.bedExplosionPower, level.purpurConfig.bedExplosionFire, level.purpurConfig.bedExplosionEffect); // CraftBukkit - add state // Purpur - Implement bed explosion options + return InteractionResult.SUCCESS_SERVER; + } + // CraftBukkit end +@@ -169,7 +169,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock @Override - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { -- super.fallOn(world, state, pos, entity, fallDistance * 0.5F); -+ super.fallOn(world, state, pos, entity, fallDistance); // Purpur + public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { +- super.fallOn(level, state, pos, entity, fallDistance * 0.5F); ++ super.fallOn(level, state, pos, entity, fallDistance); // Purpur - Configurable block fall damage modifiers } @Override -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 9e3f1441d62128535112621bf259c24f1a90595b..2535e6d71b690f8dfde41a7d9cb76b6f010f5aa7 100644 ---- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -@@ -246,7 +246,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone - BigDripleafBlock.playTiltSound(world, blockposition, soundeffect); +diff --git a/net/minecraft/world/level/block/BigDripleafBlock.java b/net/minecraft/world/level/block/BigDripleafBlock.java +index 1cc8fd0a273c3a7a75bf1ea522d109bd94718f43..3c7544569edb83e03fc0b9e01fbff9a368110427 100644 +--- a/net/minecraft/world/level/block/BigDripleafBlock.java ++++ b/net/minecraft/world/level/block/BigDripleafBlock.java +@@ -261,7 +261,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone + playTiltSound(level, pos, sound); } -- int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt); -+ int i = world.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur - - if (i != -1) { - world.scheduleTick(blockposition, (Block) this, i); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index ef8d8990a8a98c0cbc3e7aa45d22c6008f0ff2bb..070a3dbd9b92053bfed11e1f8be6f9153dc47c29 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -88,6 +88,10 @@ public class Block extends BlockBehaviour implements ItemLike { +- int _int = DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt); ++ int _int = level.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur - Big dripleaf tilt delay + if (_int != -1) { + level.scheduleTick(pos, this, _int); + } +diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java +index 79de4c558f7cbeff7e55b6d9ad2644be46d72cd9..706a25c42580c0b7317bc85be5c8f2e66a1ef1e0 100644 +--- a/net/minecraft/world/level/block/Block.java ++++ b/net/minecraft/world/level/block/Block.java +@@ -90,6 +90,10 @@ public class Block extends BlockBehaviour implements ItemLike { public static final int UPDATE_LIMIT = 512; protected final StateDefinition stateDefinition; private BlockState defaultBlockState; -+ // Purpur start ++ // Purpur start - Configurable block fall damage modifiers + public float fallDamageMultiplier = 1.0F; + public float fallDistanceMultiplier = 1.0F; -+ // Purpur end ++ // Purpur end - Configurable block fall damage modifiers // Paper start - Protect Bedrock and End Portal/Frames from being destroyed public final boolean isDestroyable() { return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || -@@ -303,7 +307,7 @@ public class Block extends BlockBehaviour implements ItemLike { - public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) { - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> { -- Block.popResource((ServerLevel) world, pos, itemstack); -+ Block.popResource((ServerLevel) world, pos, applyLoreFromTile(itemstack, blockEntity)); // Purpur - }); - state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); - } -@@ -322,7 +326,7 @@ public class Block extends BlockBehaviour implements ItemLike { +@@ -299,7 +303,7 @@ public class Block extends BlockBehaviour implements ItemLike { event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping event.callEvent(); for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { - popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); -+ popResource(serverLevel, pos, applyLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur ++ popResource(serverLevel, pos, applyLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur - Persistent BlockEntity Lore and DisplayName } state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping -@@ -339,13 +343,32 @@ public class Block extends BlockBehaviour implements ItemLike { - // Paper end - Properly handle xp dropping - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { -- Block.popResource(world, pos, itemstack1); -+ Block.popResource(world, pos, applyLoreFromTile(itemstack1, blockEntity)); // Purpur - }); - state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping - } +@@ -317,7 +321,7 @@ public class Block extends BlockBehaviour implements ItemLike { + public static void dropResources(BlockState state, LevelAccessor level, BlockPos pos, @Nullable BlockEntity blockEntity) { + if (level instanceof ServerLevel) { +- getDrops(state, (ServerLevel)level, pos, blockEntity).forEach(itemStack -> popResource((ServerLevel)level, pos, itemStack)); ++ getDrops(state, (ServerLevel)level, pos, blockEntity).forEach(itemStack -> popResource((ServerLevel)level, pos, applyLoreFromTile(itemStack, blockEntity))); // Purpur - Persistent BlockEntity Lore and DisplayName + state.spawnAfterBreak((ServerLevel)level, pos, ItemStack.EMPTY, true); + } + } +@@ -329,11 +333,30 @@ public class Block extends BlockBehaviour implements ItemLike { + public static void dropResources(BlockState state, Level level, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) { + // Paper end - Properly handle xp dropping + if (level instanceof ServerLevel) { +- getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(itemStack -> popResource(level, pos, itemStack)); ++ getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(itemStack -> popResource(level, pos, applyLoreFromTile(itemStack, blockEntity))); // Purpur - Persistent BlockEntity Lore and DisplayName + state.spawnAfterBreak((ServerLevel) level, pos, tool, dropExperience); // Paper - Properly handle xp dropping + } } -+ // Purpur start ++ // Purpur start - Persistent BlockEntity Lore and DisplayName + private static ItemStack applyLoreFromTile(ItemStack stack, @Nullable BlockEntity blockEntity) { + if (stack.getItem() instanceof BlockItem) { + if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel) { @@ -14733,86 +14848,83 @@ index ef8d8990a8a98c0cbc3e7aa45d22c6008f0ff2bb..070a3dbd9b92053bfed11e1f8be6f915 + } + return stack; + } -+ // Purpur end ++ // Purpur end - Persistent BlockEntity Lore and DisplayName + - public static void popResource(Level world, BlockPos pos, ItemStack stack) { - double d0 = (double) EntityType.ITEM.getHeight() / 2.0D; - double d1 = (double) pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); -@@ -433,7 +456,17 @@ public class Block extends BlockBehaviour implements ItemLike { - } // Paper - fix drops not preventing stats/food exhaustion + public static void popResource(Level level, BlockPos pos, ItemStack stack) { + double d = EntityType.ITEM.getHeight() / 2.0; + double d1 = pos.getX() + 0.5 + Mth.nextDouble(level.random, -0.25, 0.25); +@@ -412,7 +435,15 @@ public class Block extends BlockBehaviour implements ItemLike { } -- public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} -+ // Purpur start -+ @Nullable protected LivingEntity placer = null; -+ -+ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { -+ this.placer = placer; + public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ this.placer = placer; // Purpur - Store placer on Block when placed + } + ++ // Purpur start - Store placer on Block when placed ++ @Nullable protected LivingEntity placer = null; + public void forgetPlacer() { + this.placer = null; -+ } -+ // Purpur end + } ++ // Purpur end - Store placer on Block when placed public boolean isPossibleToRespawnInThis(BlockState state) { return !state.isSolid() && !state.liquid(); -@@ -444,7 +477,7 @@ public class Block extends BlockBehaviour implements ItemLike { +@@ -423,7 +454,7 @@ public class Block extends BlockBehaviour implements ItemLike { } - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { + public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall()); -+ entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur ++ entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur - Configurable block fall damage modifiers } - public void updateEntityMovementAfterFallOn(BlockGetter world, Entity entity) { -diff --git a/src/main/java/net/minecraft/world/level/block/Blocks.java b/src/main/java/net/minecraft/world/level/block/Blocks.java -index 66a07f7cbf1c1d6ecbe055cbf4f63eb07d93e90c..63d67d46d30ed8ed57cdc0e59b6cb6b75ab22c1f 100644 ---- a/src/main/java/net/minecraft/world/level/block/Blocks.java -+++ b/src/main/java/net/minecraft/world/level/block/Blocks.java -@@ -6465,6 +6465,7 @@ public class Blocks { + public void updateEntityMovementAfterFallOn(BlockGetter level, Entity entity) { +diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java +index 0401a5e88fe7840ae667748409411ab73888799c..bf047be5b577b0d1bf70458df14618bcfe2d1de2 100644 +--- a/net/minecraft/world/level/block/Blocks.java ++++ b/net/minecraft/world/level/block/Blocks.java +@@ -6486,6 +6486,7 @@ public class Blocks { BlockBehaviour.Properties.of() .mapColor(MapColor.PLANT) .forceSolidOff() -+ .randomTicks() // Purpur ++ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally .instabreak() .sound(SoundType.AZALEA) .noOcclusion() -@@ -6476,6 +6477,7 @@ public class Blocks { +@@ -6497,6 +6498,7 @@ public class Blocks { BlockBehaviour.Properties.of() .mapColor(MapColor.PLANT) .forceSolidOff() -+ .randomTicks() // Purpur ++ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally .instabreak() .sound(SoundType.FLOWERING_AZALEA) .noOcclusion() -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 385da0585f409ee453f10d45f5837cdc09adc21b..c65016cba376a41c267fb4b6499ec0a263851558 100644 ---- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -@@ -123,10 +123,10 @@ public class BubbleColumnBlock extends Block implements BucketPickup { - if (state.is(Blocks.BUBBLE_COLUMN)) { - return state; - } else if (state.is(Blocks.SOUL_SAND)) { +diff --git a/net/minecraft/world/level/block/BubbleColumnBlock.java b/net/minecraft/world/level/block/BubbleColumnBlock.java +index 400e213f4a2f8f09198257ce64d528932180edb5..1d907cfa53808464f6cfe631435a3b55030b6f29 100644 +--- a/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -124,10 +124,10 @@ public class BubbleColumnBlock extends Block implements BucketPickup { + if (blockState.is(Blocks.BUBBLE_COLUMN)) { + return blockState; + } else if (blockState.is(Blocks.SOUL_SAND)) { - return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(false)); -+ return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(org.purpurmc.purpur.PurpurConfig.soulSandBlockReverseBubbleColumnFlow)); // Purpur ++ return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(org.purpurmc.purpur.PurpurConfig.soulSandBlockReverseBubbleColumnFlow)); // Purpur - Config to reverse bubble column flow } else { - return state.is(Blocks.MAGMA_BLOCK) + return blockState.is(Blocks.MAGMA_BLOCK) - ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(true)) -+ ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(!org.purpurmc.purpur.PurpurConfig.magmaBlockReverseBubbleColumnFlow)) // Purpur ++ ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(!org.purpurmc.purpur.PurpurConfig.magmaBlockReverseBubbleColumnFlow)) // Purpur - Config to reverse bubble column flow : Blocks.WATER.defaultBlockState(); } } -diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java -index eb324fda54ada3ed7941713a784ed2d686ec8c4b..09cc76f3fee4a767c9ec3fa592f2c3c6146344ec 100644 ---- a/src/main/java/net/minecraft/world/level/block/BushBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java -@@ -55,4 +55,24 @@ public abstract class BushBlock extends Block { - protected boolean isPathfindable(BlockState state, PathComputationType type) { - return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, type); +diff --git a/net/minecraft/world/level/block/BushBlock.java b/net/minecraft/world/level/block/BushBlock.java +index bc52568bfa56635300266424488e524d77d95e09..8f2eebc60d655d5a2c233e2b931cdca2c6a5e768 100644 +--- a/net/minecraft/world/level/block/BushBlock.java ++++ b/net/minecraft/world/level/block/BushBlock.java +@@ -61,4 +61,24 @@ public abstract class BushBlock extends Block { + protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { + return pathComputationType == PathComputationType.AIR && !this.hasCollision || super.isPathfindable(state, pathComputationType); } + -+ // Purpur start ++ // Purpur start - Ability for hoe to replant crops + public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) { + player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this)); + player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); @@ -14830,36 +14942,36 @@ index eb324fda54ada3ed7941713a784ed2d686ec8c4b..09cc76f3fee4a767c9ec3fa592f2c3c6 + + state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true); + } -+ // Purpur end ++ // Purpur end - Ability for hoe to replant crops } -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 c045b1cccf0047dbef8c04d5a28d31d53389054f..9f163ed07f8e6a5370c4c355b4e910f7a49b6bcd 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -24,7 +24,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; +diff --git a/net/minecraft/world/level/block/CactusBlock.java b/net/minecraft/world/level/block/CactusBlock.java +index 913779c68b178dbbfac3b1dcee0c6342028e5570..dfc55eec85f35534dfa36c007d3b3d015ec809a3 100644 +--- a/net/minecraft/world/level/block/CactusBlock.java ++++ b/net/minecraft/world/level/block/CactusBlock.java +@@ -21,7 +21,7 @@ import net.minecraft.world.level.pathfinder.PathComputationType; + import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; - import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit -public class CactusBlock extends Block { -+public class CactusBlock extends Block implements BonemealableBlock { // Purpur - ++public class CactusBlock extends Block implements BonemealableBlock { // Purpur - bonemealable cactus public static final MapCodec CODEC = simpleCodec(CactusBlock::new); public static final IntegerProperty AGE = BlockStateProperties.AGE_15; -@@ -115,7 +115,7 @@ public class CactusBlock extends Block { - - enumdirection = (Direction) iterator.next(); - iblockdata1 = world.getBlockState(pos.relative(enumdirection)); -- } while (!iblockdata1.isSolid() && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); -+ } while ((!world.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors || !iblockdata1.isSolid()) && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); // Purpur - - return false; - } -@@ -135,4 +135,34 @@ public class CactusBlock extends Block { - protected boolean isPathfindable(BlockState state, PathComputationType type) { + public static final int MAX_AGE = 15; +@@ -104,7 +104,7 @@ public class CactusBlock extends Block { + protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + for (Direction direction : Direction.Plane.HORIZONTAL) { + BlockState blockState = level.getBlockState(pos.relative(direction)); +- if (blockState.isSolid() || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) { ++ if ((level.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors && blockState.isSolid()) || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) { // Purpur - Cactus breaks from solid neighbors config + return false; + } + } +@@ -128,4 +128,34 @@ public class CactusBlock extends Block { + protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return false; } + -+ // Purpur start ++ // Purpur start - bonemealable cactus + @Override + public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { + if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; @@ -14887,226 +14999,226 @@ index c045b1cccf0047dbef8c04d5a28d31d53389054f..9f163ed07f8e6a5370c4c355b4e910f7 + world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0)); + } + } -+ // Purpur end ++ // Purpur end - bonemealable cactus } -diff --git a/src/main/java/net/minecraft/world/level/block/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -index 648c2510beb162e73aed236a3169d0bbb8fc5050..3563a241c0b697dc0167cf7b1aa73fef7d1e7934 100644 ---- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java +diff --git a/net/minecraft/world/level/block/CakeBlock.java b/net/minecraft/world/level/block/CakeBlock.java +index f15b56cbfb6540ea26c6a0abdd701b7d7f1a974e..cf6cf43fc3b234cecccf5c7c0cd4571d8cf43099 100644 +--- a/net/minecraft/world/level/block/CakeBlock.java ++++ b/net/minecraft/world/level/block/CakeBlock.java @@ -119,6 +119,7 @@ public class CakeBlock extends Block { org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel); if (!event.isCancelled()) { -+ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur ++ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - Burp after eating food fills hunger bar completely player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F); } -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 18d4020017d76303d3179fad8974574777ea6305..2ee2b1485f848ac5270bc3f7e1e5b1bc3029b0bb 100644 ---- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -@@ -140,7 +140,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB - BlockPos blockposition = ctx.getClickedPos(); - boolean flag = world.getFluidState(blockposition).getType() == Fluids.WATER; - -- return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, !flag)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); -+ return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, world.purpurConfig.campFireLitWhenPlaced ? !flag : world.purpurConfig.campFireLitWhenPlaced)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); // Purpur +diff --git a/net/minecraft/world/level/block/CampfireBlock.java b/net/minecraft/world/level/block/CampfireBlock.java +index ce0267f3651dcdc59a1b1ecffe14560a8107353d..d735b6e3ade144a4645345fb7b0f2013fa9002e5 100644 +--- a/net/minecraft/world/level/block/CampfireBlock.java ++++ b/net/minecraft/world/level/block/CampfireBlock.java +@@ -141,7 +141,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB + return this.defaultBlockState() + .setValue(WATERLOGGED, Boolean.valueOf(flag)) + .setValue(SIGNAL_FIRE, Boolean.valueOf(this.isSmokeSource(level.getBlockState(clickedPos.below())))) +- .setValue(LIT, Boolean.valueOf(!flag)) ++ .setValue(LIT, Boolean.valueOf(level.getMinecraftWorld().purpurConfig.campFireLitWhenPlaced && !flag)) // Purpur - Campfire option for lit when placed + .setValue(FACING, context.getHorizontalDirection()); } - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -index 22242d11e009acab4c9738a1c6ada8b9ba678a0c..828fa78a89f13ef81c53b4d6ea6ef86c7b53024e 100644 ---- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -@@ -72,7 +72,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { - SnowGolem entitysnowman = (SnowGolem) EntityType.SNOW_GOLEM.create(world, EntitySpawnReason.TRIGGERED); - - if (entitysnowman != null) { -- CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos()); -+ CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos(), this.placer); // Purpur +diff --git a/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/net/minecraft/world/level/block/CarvedPumpkinBlock.java +index 291107e21a858215821b82d184fcfb54abbc0d97..b05a96f66724cdfb2daf625f943d7e81377cb93f 100644 +--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java ++++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java +@@ -64,7 +64,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { + if (blockPatternMatch != null) { + SnowGolem snowGolem = EntityType.SNOW_GOLEM.create(level, EntitySpawnReason.TRIGGERED); + if (snowGolem != null) { +- spawnGolemInWorld(level, blockPatternMatch, snowGolem, blockPatternMatch.getBlock(0, 2, 0).getPos()); ++ spawnGolemInWorld(level, blockPatternMatch, snowGolem, blockPatternMatch.getBlock(0, 2, 0).getPos(), this.placer); // Purpur - Summoner API } } else { - BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.getOrCreateIronGolemFull().find(world, pos); -@@ -82,7 +82,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { - - if (entityirongolem != null) { - entityirongolem.setPlayerCreated(true); -- CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos()); -+ CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur + BlockPattern.BlockPatternMatch blockPatternMatch1 = this.getOrCreateIronGolemFull().find(level, pos); +@@ -72,13 +72,23 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { + IronGolem ironGolem = EntityType.IRON_GOLEM.create(level, EntitySpawnReason.TRIGGERED); + if (ironGolem != null) { + ironGolem.setPlayerCreated(true); +- spawnGolemInWorld(level, blockPatternMatch1, ironGolem, blockPatternMatch1.getBlock(1, 2, 0).getPos()); ++ spawnGolemInWorld(level, blockPatternMatch1, ironGolem, blockPatternMatch1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur - Summoner API } } } -@@ -90,6 +90,16 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { } - private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) { -+ // Purpur start -+ spawnGolemInWorld(world, patternResult, entity, pos, null); + private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos) { ++ // Purpur start - Summoner API ++ spawnGolemInWorld(level, patternMatch, golem, pos, null); + } -+ private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) { -+ if (entity instanceof SnowGolem snowGolem) { ++ private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) { ++ if (golem instanceof SnowGolem snowGolem) { + snowGolem.setSummoner(placer == null ? null : placer.getUUID()); -+ } else if (entity instanceof IronGolem ironGolem) { ++ } else if (golem instanceof IronGolem ironGolem) { + ironGolem.setSummoner(placer == null ? null : placer.getUUID()); + } -+ // Purpur end - // clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down - entity.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F); - // CraftBukkit start -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 c9968934f4ecaa8d81e545f279b3001c7b1ce545..03e4fce6f8226451365fc2831b5bf1e5e6091730 100644 ---- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -@@ -37,7 +37,7 @@ public class CauldronBlock extends AbstractCauldronBlock { - } ++ // Purpur end - Summoner API + // clearPatternBlocks(level, patternMatch); // CraftBukkit - moved down + golem.moveTo(pos.getX() + 0.5, pos.getY() + 0.05, pos.getZ() + 0.5, 0.0F, 0.0F); + if (!level.addFreshEntity(golem, (golem.getType() == EntityType.SNOW_GOLEM) ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_SNOWMAN : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_IRONGOLEM)) { +diff --git a/net/minecraft/world/level/block/CauldronBlock.java b/net/minecraft/world/level/block/CauldronBlock.java +index d58b49e550cfa683f753db2a913fddf307a1bba2..62ee64b97dfb2f1426d43cf1f8b0b0b6ec63b5b1 100644 +--- a/net/minecraft/world/level/block/CauldronBlock.java ++++ b/net/minecraft/world/level/block/CauldronBlock.java +@@ -32,8 +32,8 @@ public class CauldronBlock extends AbstractCauldronBlock { - protected static boolean shouldHandlePrecipitation(Level world, Biome.Precipitation precipitation) { -- return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < 0.05F : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < 0.1F : false); -+ return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < world.purpurConfig.cauldronRainChance : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < world.purpurConfig.cauldronPowderSnowChance : false); // Purpur + protected static boolean shouldHandlePrecipitation(Level level, Biome.Precipitation precipitation) { + return precipitation == Biome.Precipitation.RAIN +- ? level.getRandom().nextFloat() < 0.05F +- : precipitation == Biome.Precipitation.SNOW && level.getRandom().nextFloat() < 0.1F; ++ ? level.getRandom().nextFloat() < level.purpurConfig.cauldronRainChance // Purpur - Cauldron fill chances ++ : precipitation == Biome.Precipitation.SNOW && level.getRandom().nextFloat() < level.purpurConfig.cauldronPowderSnowChance; // Purpur - Cauldron fill chances } @Override -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 c4473c2a778116d48bc3e4796afd901f455070e6..e62217c0bfa6cc426c7d41154f3ccc34d8f242ca 100644 ---- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -@@ -94,4 +94,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { - world.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2); +diff --git a/net/minecraft/world/level/block/CaveVinesBlock.java b/net/minecraft/world/level/block/CaveVinesBlock.java +index ff445bb33a97e3fd2ef0f8759e59ef762d0a578f..b7a7a8d6587cc9b32ee23797411b25d7eccd92d4 100644 +--- a/net/minecraft/world/level/block/CaveVinesBlock.java ++++ b/net/minecraft/world/level/block/CaveVinesBlock.java +@@ -92,4 +92,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + level.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2); } + -+ // Purpur start ++ // Purpur start - cave vines configurable max growth age + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge; + } -+ // Purpur end ++ // Purpur end - cave vines configurable max growth age } -diff --git a/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java -index daae7fd6e0148cfba8e359d990748a0c83a3376e..0e06b1bcd906e92c083dc74d56d6d0a2a36f62a7 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java -@@ -67,7 +67,7 @@ public interface ChangeOverTimeBlock> { +diff --git a/net/minecraft/world/level/block/ChangeOverTimeBlock.java b/net/minecraft/world/level/block/ChangeOverTimeBlock.java +index 4f98ae8bd4bfb681883132eddb57cbc5703d7d9e..c84e1c124b733fb918de17340f7e0f57162b8051 100644 +--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java ++++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java +@@ -51,7 +51,7 @@ public interface ChangeOverTimeBlock> { } - float f = (float) (k + 1) / (float) (k + j + 1); + float f = (float)(i1 + 1) / (i1 + i + 1); - float f1 = f * f * this.getChanceModifier(); -+ float f1 = world.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() : f * f * this.getChanceModifier(); // Purpur - ++ float f1 = level.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() :f * f * this.getChanceModifier();// Purpur - option to disable the copper oxidation proximity penalty return random.nextFloat() < f1 ? this.getNext(state) : Optional.empty(); } -diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -index edef8fc62f8dba1b57214d8d7d805ff0d83f4114..663eb96b8227f000448957b5d8ea13ca78cee329 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -@@ -341,6 +341,7 @@ public class ChestBlock extends AbstractChestBlock implements + } +diff --git a/net/minecraft/world/level/block/ChestBlock.java b/net/minecraft/world/level/block/ChestBlock.java +index 9ed50f09147984ba8864edabb89669e2f4fc7bea..af4abe255da35e9b7ecfd29914d5e54e26f0c3cd 100644 +--- a/net/minecraft/world/level/block/ChestBlock.java ++++ b/net/minecraft/world/level/block/ChestBlock.java +@@ -357,6 +357,7 @@ public class ChestBlock extends AbstractChestBlock implements } - public static boolean isBlockedChestByBlock(BlockGetter world, BlockPos pos) { -+ if (world instanceof Level && ((Level) world).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur - BlockPos blockposition1 = pos.above(); - - return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1); -diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -index ceab17cdf519278393e41311488e6d7f99133ebe..c9c575c48965a31830cf97578911a69e889ed349 100644 ---- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -241,18 +241,27 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - int i = (Integer) state.getValue(ComposterBlock.LEVEL); - - if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { -- if (i < 7 && !world.isClientSide) { -- BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); + public static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) { ++ if (level instanceof Level level1 && level1.purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur - Option for chests to open even with a solid block on top + BlockPos blockPos = pos.above(); + return level.getBlockState(blockPos).isRedstoneConductor(level, blockPos); + } +diff --git a/net/minecraft/world/level/block/ComposterBlock.java b/net/minecraft/world/level/block/ComposterBlock.java +index 0d34839618648bb8c606dd2d9b1f9db93641c742..d71ac1d023cc5a5d00b20c0001a49f52591a3a9b 100644 +--- a/net/minecraft/world/level/block/ComposterBlock.java ++++ b/net/minecraft/world/level/block/ComposterBlock.java +@@ -241,17 +241,27 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + ) { + int levelValue = state.getValue(LEVEL); + if (levelValue < 8 && COMPOSTABLES.containsKey(stack.getItem())) { +- if (levelValue < 7 && !level.isClientSide) { +- BlockState blockState = addItem(player, state, level, pos, stack); - // Paper start - handle cancelled events -- if (iblockdata1 == null) { +- if (blockState == null) { - return InteractionResult.PASS; - } - // Paper end -- -- world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); +- level.levelEvent(1500, pos, state != blockState ? 1 : 0); - player.awardStat(Stats.ITEM_USED.get(stack.getItem())); - stack.consume(1, player); + // Purpur start - sneak to bulk process composter -+ BlockState newState = process(i, player, state, world, pos, stack); ++ BlockState newState = process(levelValue, player, state, level, pos, stack); + if (newState == null) { + return InteractionResult.PASS; - } -+ if (world.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) { ++ } ++ if (level.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) { + BlockState oldState; + int oldCount, newCount, oldLevel, newLevel; + do { + oldState = newState; + oldCount = stack.getCount(); + oldLevel = oldState.getValue(ComposterBlock.LEVEL); -+ newState = process(oldLevel, player, oldState, world, pos, stack); ++ newState = process(oldLevel, player, oldState, level, pos, stack); + if (newState == null) { + return InteractionResult.PASS; + } + newCount = stack.getCount(); + newLevel = newState.getValue(ComposterBlock.LEVEL); + } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState)); -+ } -+ // Purpur end + } ++ // Purpur end - Sneak to bulk process composter return InteractionResult.SUCCESS; } else { -@@ -260,6 +269,25 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { +@@ -259,6 +269,25 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { } } + // Purpur start - sneak to bulk process composter -+ private static @Nullable BlockState process(int level, Player player, BlockState state, Level world, BlockPos pos, ItemStack stack) { -+ if (level < 7 && !world.isClientSide) { -+ BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); ++ private static @Nullable BlockState process(int levelValue, Player player, BlockState state, Level level, BlockPos pos, ItemStack stack) { ++ if (levelValue < 7 && !level.isClientSide) { ++ BlockState iblockdata1 = ComposterBlock.addItem(player, state, level, pos, stack); + // Paper start - handle cancelled events + if (iblockdata1 == null) { + return null; + } + // Paper end + -+ world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); ++ level.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); + player.awardStat(Stats.ITEM_USED.get(stack.getItem())); + stack.consume(1, player); + return iblockdata1; + } + return state; + } -+ // Purpur end ++ // Purpur end - Sneak to bulk process composter + @Override - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - int i = (Integer) state.getValue(ComposterBlock.LEVEL); -diff --git a/src/main/java/net/minecraft/world/level/block/CoralBlock.java b/src/main/java/net/minecraft/world/level/block/CoralBlock.java -index a59b23f4062fa896836dec72cbd5097411774ad1..c526ea13a726624adaa654f09ff84c899c13ab98 100644 ---- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java -@@ -60,6 +60,7 @@ public class CoralBlock extends Block { + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { + int levelValue = state.getValue(LEVEL); +diff --git a/net/minecraft/world/level/block/CoralBlock.java b/net/minecraft/world/level/block/CoralBlock.java +index e0be02eaa07d40d0738931383426517d20fe3b0b..b747af5f3c65f4b79a304b0e903f7b824fb03d8d 100644 +--- a/net/minecraft/world/level/block/CoralBlock.java ++++ b/net/minecraft/world/level/block/CoralBlock.java +@@ -65,6 +65,7 @@ public class CoralBlock extends Block { } - protected boolean scanForWater(BlockGetter world, BlockPos pos) { -+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Direction[] aenumdirection = Direction.values(); - int i = aenumdirection.length; - -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 1ada5ed825501666addacf527a513ab7bd4a3a58..33c27909290ff3ab483226cf65b1a1bc2e983cbc 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -180,7 +180,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (world instanceof ServerLevel worldserver) { -- if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit -+ if (entity instanceof Ravager && world.purpurConfig.ravagerGriefableBlocks.contains(world.getBlockState(pos).getBlock()) && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.purpurConfig.ravagerBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Purpur - worldserver.destroyBlock(pos, true, entity); - } + protected boolean scanForWater(BlockGetter level, BlockPos pos) { ++ if (!((net.minecraft.world.level.LevelAccessor) level).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die + for (Direction direction : Direction.values()) { + FluidState fluidState = level.getFluidState(pos.relative(direction)); + if (fluidState.is(FluidTags.WATER)) { +diff --git a/net/minecraft/world/level/block/CropBlock.java b/net/minecraft/world/level/block/CropBlock.java +index bc0969f40814094e42a860a72314fccd1a66fabe..27f0c5c886a3f8b14ef9a00e2aaaabf4bf09c7db 100644 +--- a/net/minecraft/world/level/block/CropBlock.java ++++ b/net/minecraft/world/level/block/CropBlock.java +@@ -182,7 +182,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + @Override + protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent +- if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit ++ if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.purpurConfig.ravagerGriefableBlocks.contains(serverLevel.getBlockState(pos).getBlock()) && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.purpurConfig.ravagerBypassMobGriefing == !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Purpur - Configurable ravager griefable blocks list // Purpur - Add mobGriefing bypass to everything affected + serverLevel.destroyBlock(pos, true, entity); } -@@ -216,4 +216,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + +@@ -217,4 +217,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(CropBlock.AGE); + builder.add(AGE); } + -+ // Purpur start ++ // Purpur start - Ability for hoe to replant crops + @Override + public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) { + if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { @@ -15115,26 +15227,26 @@ index 1ada5ed825501666addacf527a513ab7bd4a3a58..33c27909290ff3ab483226cf65b1a1bc + super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp); + } + } -+ // Purpur end ++ // Purpur end - Ability for hoe to replant crops } -diff --git a/src/main/java/net/minecraft/world/level/block/DoorBlock.java b/src/main/java/net/minecraft/world/level/block/DoorBlock.java -index 077b99caf0ec0ee098786d23194d88e1dc4481ce..daf865c20cc193a12db0d98e3c0472eefdf635c2 100644 ---- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java -@@ -200,6 +200,7 @@ public class DoorBlock extends Block { - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { +diff --git a/net/minecraft/world/level/block/DoorBlock.java b/net/minecraft/world/level/block/DoorBlock.java +index eaa735478d808f7279b6ca62c546cc3d357c11d0..46948eefac791f267695f5e8abb45ad6d61beaac 100644 +--- a/net/minecraft/world/level/block/DoorBlock.java ++++ b/net/minecraft/world/level/block/DoorBlock.java +@@ -206,6 +206,7 @@ public class DoorBlock extends Block { + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (!this.type.canOpenByHand()) { return InteractionResult.PASS; -+ } else if (requiresRedstone(world, state, pos)) { return InteractionResult.CONSUME; // Purpur ++ } else if (requiresRedstone(level, state, pos)) { return InteractionResult.CONSUME; // Purpur - Option to make doors require redstone } else { - state = (BlockState) state.cycle(DoorBlock.OPEN); - world.setBlock(pos, state, 10); -@@ -301,4 +302,18 @@ public class DoorBlock extends Block { - flag = false; - return flag; + state = state.cycle(OPEN); + level.setBlock(pos, state, 10); +@@ -294,4 +295,18 @@ public class DoorBlock extends Block { + public static boolean isWoodenDoor(BlockState state) { + return state.getBlock() instanceof DoorBlock doorBlock && doorBlock.type().canOpenByHand(); } + -+ // Purpur start ++ // Purpur start - Option to make doors require redstone + public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) { + if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) { + // force update client @@ -15146,32 +15258,30 @@ index 077b99caf0ec0ee098786d23194d88e1dc4481ce..daf865c20cc193a12db0d98e3c0472ee + } + return false; + } -+ // Purpur end ++ // Purpur end - Option to make doors require redstone } -diff --git a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -index 30d15686b1a81de7ac28feb0c6188eb007c6f2fd..b6799db00e157892dd4339a01d2ca36092c8e491 100644 ---- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -@@ -48,8 +48,8 @@ public class DragonEggBlock extends FallingBlock { +diff --git a/net/minecraft/world/level/block/DragonEggBlock.java b/net/minecraft/world/level/block/DragonEggBlock.java +index 38f9003f8260eb2f0606cbd53aa30604cdeb48c0..e72c0f252138858f44e423b28d6e26fcab53a17e 100644 +--- a/net/minecraft/world/level/block/DragonEggBlock.java ++++ b/net/minecraft/world/level/block/DragonEggBlock.java +@@ -46,6 +46,7 @@ public class DragonEggBlock extends FallingBlock { } - private void teleport(BlockState state, Level world, BlockPos pos) { -+ if (!world.purpurConfig.dragonEggTeleport) return; // Purpur - WorldBorder worldborder = world.getWorldBorder(); -- - for (int i = 0; i < 1000; ++i) { - BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16)); + private void teleport(BlockState state, Level level, BlockPos pos) { ++ if (!level.purpurConfig.dragonEggTeleport) return; // Purpur - Option to disable dragon egg teleporting + WorldBorder worldBorder = level.getWorldBorder(); -diff --git a/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java b/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java -index a7fb500d950687743d1fc0b3ad3e6d10dcc6e31a..ce6a9e15ae0114623e79b5d8c244c2c490a3f74e 100644 ---- a/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java -@@ -123,4 +123,18 @@ public class EnchantingTableBlock extends BaseEntityBlock { - protected boolean isPathfindable(BlockState state, PathComputationType type) { + for (int i = 0; i < 1000; i++) { +diff --git a/net/minecraft/world/level/block/EnchantingTableBlock.java b/net/minecraft/world/level/block/EnchantingTableBlock.java +index b2d7a8f45dc7bb447b7c1af3d4411bf8214aca05..92c75217860f1fca706f4e7105589f0f67ba81f4 100644 +--- a/net/minecraft/world/level/block/EnchantingTableBlock.java ++++ b/net/minecraft/world/level/block/EnchantingTableBlock.java +@@ -119,4 +119,18 @@ public class EnchantingTableBlock extends BaseEntityBlock { + protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return false; } + -+ // Purpur start ++ // Purpur start - Enchantment Table Persists Lapis + @Override + public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) { + BlockEntity blockEntity = level.getBlockEntity(pos); @@ -15183,62 +15293,64 @@ index a7fb500d950687743d1fc0b3ad3e6d10dcc6e31a..ce6a9e15ae0114623e79b5d8c244c2c4 + + super.onRemove(state, level, pos, newState, moved); + } -+ // Purpur end ++ // Purpur end - Enchantment Table Persists Lapis } -diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -index a7a21f071161fb7e73a046717d2462f871ab653c..abb75f9389167a1f51a2c50831664d50181749de 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -@@ -104,6 +104,13 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { - TheEndGatewayBlockEntity tileentityendgateway = (TheEndGatewayBlockEntity) tileentity; - - if (!tileentityendgateway.isCoolingDown()) { -+ // Purpur start -+ if (world.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) { -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent()) { -+ return; -+ } -+ } -+ // Purpur end - entity.setAsInsidePortal(this, pos); - TheEndGatewayBlockEntity.triggerCooldown(world, pos, state, tileentityendgateway); - } -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 4aa14f975e1ceedf3d4a427e0daefb58b12fcafe..2dffc3990d9ae3d595d923239885e3a7d8ec04f3 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -70,6 +70,13 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { -+ // Purpur start -+ if (world.purpurConfig.imposeTeleportRestrictionsOnEndPortals && (entity.isVehicle() || entity.isPassenger())) { -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { +diff --git a/net/minecraft/world/level/block/EndGatewayBlock.java b/net/minecraft/world/level/block/EndGatewayBlock.java +index 84a1bd5e40e635962d795506861447851e443eee..54abeb142e119edd1c1d1c263821b95b1f05c388 100644 +--- a/net/minecraft/world/level/block/EndGatewayBlock.java ++++ b/net/minecraft/world/level/block/EndGatewayBlock.java +@@ -98,6 +98,13 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { + org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type + if (!event.callEvent()) return; + // Paper end - call EntityPortalEnterEvent ++ // Purpur start - Add EntityTeleportHinderedEvent ++ if (level.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) { ++ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent()) { + return; + } + } -+ // Purpur end ++ // Purpur end - Add EntityTeleportHinderedEvent + entity.setAsInsidePortal(this, pos); + TheEndGatewayBlockEntity.triggerCooldown(level, pos, state, theEndGatewayBlockEntity); + } +diff --git a/net/minecraft/world/level/block/EndPortalBlock.java b/net/minecraft/world/level/block/EndPortalBlock.java +index 01cddd7001b4a7f99c1b1d147fac904d3064d733..7e60bcadd2d343e00fc554dba0b85c9191f7efb6 100644 +--- a/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/net/minecraft/world/level/block/EndPortalBlock.java +@@ -58,6 +58,13 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { + protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { ++ // Purpur start - Add EntityTeleportHinderedEvent ++ if (level.purpurConfig.imposeTeleportRestrictionsOnEndPortals && (entity.isVehicle() || entity.isPassenger())) { ++ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { ++ return; ++ } ++ } ++ // Purpur end - Add EntityTeleportHinderedEvent // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type - world.getCraftServer().getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -index 2a207fb2e1c26b562de42240e11c856bd2a23458..6ad4aa371607ab92616626285a7e71757c76a3db 100644 ---- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -@@ -89,7 +89,7 @@ public class EnderChestBlock extends AbstractChestBlock i - // Paper start - Fix InventoryOpenEvent cancellation - moved up; - playerEnderChestContainer.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations - if (world instanceof ServerLevel serverLevel && player.openMenu( -- new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) -+ new SimpleMenuProvider((i, inventory, playerx) -> org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? getEnderChestSixRows(i, inventory, player, playerEnderChestContainer) : ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) // Purpur + org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type + level.getCraftServer().getPluginManager().callEvent(event); +diff --git a/net/minecraft/world/level/block/EnderChestBlock.java b/net/minecraft/world/level/block/EnderChestBlock.java +index f5533960708bdbaf2eacefbc7c7c3123b7d26502..17aa27885b4431bf7b98799e02d080b5a0ecbbf1 100644 +--- a/net/minecraft/world/level/block/EnderChestBlock.java ++++ b/net/minecraft/world/level/block/EnderChestBlock.java +@@ -85,8 +85,8 @@ public class EnderChestBlock extends AbstractChestBlock i + enderChestInventory.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations + if (level instanceof ServerLevel serverLevel && player.openMenu( + new SimpleMenuProvider( +- (containerId, playerInventory, player1) -> ChestMenu.threeRows(containerId, playerInventory, enderChestInventory), CONTAINER_TITLE +- ) ++ (containerId, playerInventory, player1) -> org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? getEnderChestSixRows(containerId, playerInventory, player, enderChestInventory) : ChestMenu.threeRows(containerId, playerInventory, enderChestInventory), CONTAINER_TITLE ++ ) // Purpur - Barrels and enderchests 6 rows ).isPresent()) { // Paper end - Fix InventoryOpenEvent cancellation - moved up; - // Paper - Fix InventoryOpenEvent cancellation - moved up; -@@ -104,6 +104,35 @@ public class EnderChestBlock extends AbstractChestBlock i + player.awardStat(Stats.OPEN_ENDERCHEST); +@@ -100,6 +100,35 @@ public class EnderChestBlock extends AbstractChestBlock i } } -+ // Purpur start ++ // Purpur start - Barrels and enderchests 6 rows + private ChestMenu getEnderChestSixRows(int syncId, net.minecraft.world.entity.player.Inventory inventory, Player player, PlayerEnderChestContainer playerEnderChestContainer) { + if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity(); @@ -15265,270 +15377,278 @@ index 2a207fb2e1c26b562de42240e11c856bd2a23458..6ad4aa371607ab92616626285a7e7175 + player.sixRowEnderchestSlotCount = -1; + return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); + } -+ // Purpur end ++ // Purpur end - Barrels and enderchests 6 rows + @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new EnderChestBlockEntity(pos, state); -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 c3dba0c2c94f3804338f86621dc42405e380a6b3..eaac00e2534aca4eab92c7b9f9248e04b35b47df 100644 ---- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -112,7 +112,7 @@ public class FarmBlock extends Block { - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. - if (world instanceof ServerLevel worldserver) { -- if (world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { -+ if ((worldserver.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= worldserver.purpurConfig.farmlandTrampleHeight : world.random.nextFloat() < fallDistance - 0.5F) && entity instanceof LivingEntity && (entity instanceof Player || worldserver.purpurConfig.farmlandBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // Purpur +diff --git a/net/minecraft/world/level/block/FarmBlock.java b/net/minecraft/world/level/block/FarmBlock.java +index 47c9b32c89e7e6f84a279c2f6098ada77dc58b6b..9a7ca836e54cc3f58001c85f0079747f4d4941ad 100644 +--- a/net/minecraft/world/level/block/FarmBlock.java ++++ b/net/minecraft/world/level/block/FarmBlock.java +@@ -112,9 +112,9 @@ public class FarmBlock extends Block { + public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { + super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. + if (level instanceof ServerLevel serverLevel +- && level.random.nextFloat() < fallDistance - 0.5F ++ && (serverLevel.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= serverLevel.purpurConfig.farmlandTrampleHeight : level.random.nextFloat() < fallDistance - 0.5F) // // Purpur - Configurable farmland trample height + && entity instanceof LivingEntity +- && (entity instanceof Player || serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ++ && (entity instanceof Player || serverLevel.purpurConfig.farmlandBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) + && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // CraftBukkit start - Interact soil org.bukkit.event.Cancellable cancellable; - if (entity instanceof Player) { -@@ -126,6 +126,22 @@ public class FarmBlock extends Block { +@@ -129,6 +129,27 @@ public class FarmBlock extends Block { return; } -+ // Purpur start -+ if (world.purpurConfig.farmlandTramplingDisabled) return; -+ if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; -+ if (world.purpurConfig.farmlandAlpha) { -+ Block block = world.getBlockState(pos.below()).getBlock(); -+ if (block instanceof FenceBlock || block instanceof WallBlock) { -+ return; ++ if (level.purpurConfig.farmlandTramplingDisabled) return; // Purpur - Farmland trampling changes ++ if (level.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; // Purpur - Farmland trampling changes ++ ++ // Purpur start - Ability to re-add farmland mechanics from Alpha ++ if (level.purpurConfig.farmlandAlpha) { ++ Block block = level.getBlockState(pos.below()).getBlock(); ++ if (block instanceof FenceBlock || block instanceof WallBlock) { ++ return; ++ } + } -+ } -+ if (world.purpurConfig.farmlandTramplingFeatherFalling) { -+ Iterator armor = ((LivingEntity) entity).getArmorSlots().iterator(); -+ if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) >= (int) entity.fallDistance) { -+ return; ++ // Purpur end - Ability to re-add farmland mechanics from Alpha ++ ++ // Purpur start - Farmland trampling changes ++ if (level.purpurConfig.farmlandTramplingFeatherFalling) { ++ java.util.Iterator armor = ((LivingEntity) entity).getArmorSlots().iterator(); ++ if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) >= (int) entity.fallDistance) { ++ return; ++ } + } -+ } -+ // Purpur end - if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { ++ // Purpur end - Farmland trampling changes ++ + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { return; } -@@ -174,7 +190,7 @@ public class FarmBlock extends Block { +@@ -174,7 +195,7 @@ public class FarmBlock extends Block { } } - return false; -+ return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur; ++ return ((ServerLevel) level).purpurConfig.farmlandGetsMoistFromBelow && level.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur - Allow soil to moisten from water directly under it // Paper end - Perf: remove abstract block iteration } -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 9b424d7661fedf8ee1eb9f3167c62e563f04d4d1..2af311847a085a8073e9bcb26c762d1bbe1eae2c 100644 ---- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +diff --git a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +index 0994f7265322d1f33365a1df0faaffd9df05fcc0..5dc1883ecd6e52666f553f30c150843c99fbef08 100644 +--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java ++++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -34,12 +34,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public BlockState getStateForPlacement(RandomSource random) { -- return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, random.nextInt(25)); -+ return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, random.nextInt(getMaxGrowthAge())); // Purpur +- return this.defaultBlockState().setValue(AGE, Integer.valueOf(random.nextInt(25))); ++ return this.defaultBlockState().setValue(AGE, Integer.valueOf(random.nextInt(getMaxGrowthAge()))); // Purpur - kelp, cave, weeping, and twisting configurable max growth age } @Override protected boolean isRandomlyTicking(BlockState state) { -- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25; -+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge(); // Purpur +- return state.getValue(AGE) < 25; ++ return state.getValue(AGE) < getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age } @Override @@ -55,7 +55,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - } else { - modifier = world.spigotConfig.caveVinesModifier; + } else if (this == Blocks.CAVE_VINES) { + modifier = level.spigotConfig.caveVinesModifier; } -- if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution -+ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur +- if (state.getValue(AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution ++ if (state.getValue(AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur - kelp, cave, weeping, and twisting configurable max growth age // Spigot end - BlockPos blockposition1 = pos.relative(this.growthDirection); - -@@ -77,11 +77,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements + BlockPos blockPos = pos.relative(this.growthDirection); + if (this.canGrowInto(level.getBlockState(blockPos))) { +@@ -75,11 +75,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } public BlockState getMaxAgeState(BlockState state) { -- return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, 25); -+ return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, getMaxGrowthAge()); // Purpur +- return state.setValue(AGE, Integer.valueOf(25)); ++ return state.setValue(AGE, Integer.valueOf(getMaxGrowthAge())); // Purpur - kelp, cave, weeping, and twisting configurable max growth age } public boolean isMaxAge(BlockState state) { -- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) == 25; -+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) >= getMaxGrowthAge(); // Purpur +- return state.getValue(AGE) == 25; ++ return state.getValue(AGE) >= getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age } - protected BlockState updateBodyAfterConvertedFromHead(BlockState from, BlockState to) { -@@ -123,13 +123,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements + protected BlockState updateBodyAfterConvertedFromHead(BlockState head, BlockState body) { +@@ -130,13 +130,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { - BlockPos blockposition1 = pos.relative(this.growthDirection); -- int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, 25); -+ int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, getMaxGrowthAge()); // Purpur - int j = this.getBlocksToGrowWhenBonemealed(random); + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + BlockPos blockPos = pos.relative(this.growthDirection); +- int min = Math.min(state.getValue(AGE) + 1, 25); ++ int min = Math.min(state.getValue(AGE) + 1, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age + int blocksToGrowWhenBonemealed = this.getBlocksToGrowWhenBonemealed(random); - for (int k = 0; k < j && this.canGrowInto(world.getBlockState(blockposition1)); ++k) { - world.setBlockAndUpdate(blockposition1, (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, i)); - blockposition1 = blockposition1.relative(this.growthDirection); -- i = Math.min(i + 1, 25); -+ i = Math.min(i + 1, getMaxGrowthAge()); // Purpur + for (int i = 0; i < blocksToGrowWhenBonemealed && this.canGrowInto(level.getBlockState(blockPos)); i++) { + level.setBlockAndUpdate(blockPos, state.setValue(AGE, Integer.valueOf(min))); + blockPos = blockPos.relative(this.growthDirection); +- min = Math.min(min + 1, 25); ++ min = Math.min(min + 1, getMaxGrowthAge()); // Purpur - kelp, cave, weeping, and twisting configurable max growth age } - } -@@ -142,4 +142,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements + +@@ -148,4 +148,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements protected GrowingPlantHeadBlock getHeadBlock() { return this; } + -+ public abstract int getMaxGrowthAge(); // Purpur ++ public abstract int getMaxGrowthAge(); // Purpur - kelp, cave, weeping, and twisting configurable max growth age } -diff --git a/src/main/java/net/minecraft/world/level/block/HayBlock.java b/src/main/java/net/minecraft/world/level/block/HayBlock.java -index ef364aa171a48482a45bc18cfe730ec20c3f7be6..74971d90506aa253d5ee821b5390fb2551a3a393 100644 ---- a/src/main/java/net/minecraft/world/level/block/HayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java +diff --git a/net/minecraft/world/level/block/HayBlock.java b/net/minecraft/world/level/block/HayBlock.java +index 3d2ced08c1d40a558e82346672eaee9bf1315971..406e94e4a88921900f59a3a1b65e74a6482c3b8f 100644 +--- a/net/minecraft/world/level/block/HayBlock.java ++++ b/net/minecraft/world/level/block/HayBlock.java @@ -23,6 +23,6 @@ public class HayBlock extends RotatedPillarBlock { @Override - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { -- entity.causeFallDamage(fallDistance, 0.2F, world.damageSources().fall()); -+ super.fallOn(world, state, pos, entity, fallDistance); // Purpur + public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { +- entity.causeFallDamage(fallDistance, 0.2F, level.damageSources().fall()); ++ super.fallOn(level, state, pos, entity, fallDistance); // Purpur - Configurable block fall damage modifiers } } -diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java -index a94762e65853ccad38cf90b0049ca256106c0c9f..38633e168a9b36e37feea00964d53e657926639e 100644 ---- a/src/main/java/net/minecraft/world/level/block/IceBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java -@@ -42,7 +42,7 @@ public class IceBlock extends HalfTransparentBlock { - public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { +diff --git a/net/minecraft/world/level/block/IceBlock.java b/net/minecraft/world/level/block/IceBlock.java +index be7141a4009036bcf3f92bba5d0ad74459e99bfa..a4d735a4365fdaf9e602315aa1176dfd5db77ff5 100644 +--- a/net/minecraft/world/level/block/IceBlock.java ++++ b/net/minecraft/world/level/block/IceBlock.java +@@ -40,7 +40,7 @@ public class IceBlock extends HalfTransparentBlock { + public void afterDestroy(Level level, BlockPos pos, ItemStack stack) { // Paper end - Improve Block#breakNaturally API - if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) { -- if (world.dimensionType().ultraWarm()) { -+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - world.removeBlock(pos, false); + if (!EnchantmentHelper.hasTag(stack, EnchantmentTags.PREVENTS_ICE_MELTING)) { +- if (level.dimensionType().ultraWarm()) { ++ if (level.isNether() || (level.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - Add allow water in end world option + level.removeBlock(pos, false); return; } -@@ -70,7 +70,7 @@ public class IceBlock extends HalfTransparentBlock { +@@ -65,7 +65,7 @@ public class IceBlock extends HalfTransparentBlock { return; } // CraftBukkit end -- if (world.dimensionType().ultraWarm()) { -+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - world.removeBlock(pos, false); +- if (level.dimensionType().ultraWarm()) { ++ if (level.isNether() || (level.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - Add allow water in end world option + level.removeBlock(pos, false); } else { - world.setBlockAndUpdate(pos, IceBlock.meltsInto()); -diff --git a/src/main/java/net/minecraft/world/level/block/KelpBlock.java b/src/main/java/net/minecraft/world/level/block/KelpBlock.java -index 784b19bc78c8ad9476b6dac37b6778a409a7c675..d49dd8b20d3785cc9482ed2a34fbd7aed4c9e537 100644 ---- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java + level.setBlockAndUpdate(pos, meltsInto()); +diff --git a/net/minecraft/world/level/block/KelpBlock.java b/net/minecraft/world/level/block/KelpBlock.java +index 72fc688eba837246ae9eb89ce3fa8e621774c37c..af4532f50eacb32a3ae1fc62ea6d564c127d8691 100644 +--- a/net/minecraft/world/level/block/KelpBlock.java ++++ b/net/minecraft/world/level/block/KelpBlock.java @@ -72,4 +72,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta protected FluidState getFluidState(BlockState state) { return Fluids.WATER.getSource(false); } + -+ // Purpur start ++ // Purpur start - kelp vines configurable max growth age + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge; + } -+ // Purpur end ++ // Purpur end - kelp vines configurable max growth age } -diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -index a2d023ff011f71f80032f02430a53d6a08a23623..399441dd8388dcdec08768665d3cfa189aca0a95 100644 ---- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -@@ -140,7 +140,7 @@ public class LiquidBlock extends Block implements BucketPickup { +diff --git a/net/minecraft/world/level/block/LiquidBlock.java b/net/minecraft/world/level/block/LiquidBlock.java +index 19d1906e9d4e92ff49a833bca03a7308ee8059e3..47a7ce88bf4d26408545dcc061aa763311af0dc9 100644 +--- a/net/minecraft/world/level/block/LiquidBlock.java ++++ b/net/minecraft/world/level/block/LiquidBlock.java +@@ -134,7 +134,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override - protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -- if (this.shouldSpreadLiquid(world, pos, state)) { -+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur - world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava + protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) { +- if (this.shouldSpreadLiquid(level, pos, state)) { ++ if (level.purpurConfig.tickFluids && this.shouldSpreadLiquid(level, pos, state)) { // Purpur - Tick fluids config + level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava } - -@@ -168,7 +168,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { + } +@@ -169,7 +169,7 @@ public class LiquidBlock extends Block implements BucketPickup { + BlockState neighborState, + RandomSource random + ) { - if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { -+ if (world.getWorldBorder().world.purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur - tickView.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); ++ if (level.getWorldBorder().world.purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur - Tick fluids config + scheduledTickAccess.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(level)); } -@@ -177,7 +177,7 @@ public class LiquidBlock extends Block implements BucketPickup { +@@ -178,7 +178,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override - protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { -- if (this.shouldSpreadLiquid(world, pos, state)) { -+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur - world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava + protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) { +- if (this.shouldSpreadLiquid(level, pos, state)) { ++ if (level.purpurConfig.tickFluids && this.shouldSpreadLiquid(level, pos, state)) { // Purpur - Tick fluids config + level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava } - -diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -index 7ffdcf18bf4bd8b5325c76945b2d80ca3fe52958..dfa931316fde0b2e80068a0edd1427ffd096b15b 100644 ---- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -@@ -29,7 +29,7 @@ public class MagmaBlock extends Block { + } +diff --git a/net/minecraft/world/level/block/MagmaBlock.java b/net/minecraft/world/level/block/MagmaBlock.java +index 6245b268d1361973a9dee2b80454779b7d0922f0..3345a151bdb5262be6ce117947f9975b7fdcfdf0 100644 +--- a/net/minecraft/world/level/block/MagmaBlock.java ++++ b/net/minecraft/world/level/block/MagmaBlock.java +@@ -28,7 +28,7 @@ public class MagmaBlock extends Block { @Override - public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { + public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) { - if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) { -+ if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity) { // Purpur - entity.hurt(world.damageSources().hotFloor().directBlock(world, pos), 1.0F); // CraftBukkit ++ if ((!entity.isSteppingCarefully() || level.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity) { // Purpur - Configurable damage settings for magma blocks + entity.hurt(level.damageSources().hotFloor().directBlock(level, pos), 1.0F); // CraftBukkit } -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 2b31bf586c1c0bd393d2aa8d0b6635dd9f22f21c..02bcba52e28f757b59e2f384d5744834962870f0 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -78,7 +78,7 @@ public class NetherPortalBlock extends Block implements Portal { - - @Override - protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -- if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot -+ if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(world.purpurConfig.piglinPortalSpawnModifier) < world.getDifficulty().getId()) { // Spigot // Purpur - while (world.getBlockState(pos).is((Block) this)) { +diff --git a/net/minecraft/world/level/block/NetherPortalBlock.java b/net/minecraft/world/level/block/NetherPortalBlock.java +index e2eb693b0130513115392cb0cb5a829ede5be8c5..eb659209008209c0930770e5f9671a3d7a4abae6 100644 +--- a/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -72,7 +72,7 @@ public class NetherPortalBlock extends Block implements Portal { + protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + if (level.spigotConfig.enableZombiePigmenPortalSpawns && level.dimensionType().natural() // Spigot + && level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) +- && random.nextInt(2000) < level.getDifficulty().getId()) { ++ && random.nextInt(level.purpurConfig.piglinPortalSpawnModifier) < level.getDifficulty().getId()) { // Purpur - Piglin portal spawn modifier + while (level.getBlockState(pos).is(this)) { pos = pos.below(); } @@ -117,6 +117,13 @@ public class NetherPortalBlock extends Block implements Portal { - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent if (entity.canUsePortal(false)) { -+ // Purpur start -+ if (world.purpurConfig.imposeTeleportRestrictionsOnNetherPortals && (entity.isVehicle() || entity.isPassenger())) { ++ // Purpur start - Add EntityTeleportHinderedEvent ++ if (level.purpurConfig.imposeTeleportRestrictionsOnNetherPortals && (entity.isVehicle() || entity.isPassenger())) { + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) { + return; + } + } -+ // Purpur end ++ // Purpur end - Add EntityTeleportHinderedEvent // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type - world.getCraftServer().getPluginManager().callEvent(event); -@@ -130,7 +137,7 @@ public class NetherPortalBlock extends Block implements Portal { + org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type + level.getCraftServer().getPluginManager().callEvent(event); +@@ -129,7 +136,7 @@ public class NetherPortalBlock extends Block implements Portal { @Override - public int getPortalTransitionTime(ServerLevel world, Entity entity) { - if (entity instanceof Player entityhuman) { -- return Math.max(0, world.getGameRules().getInt(entityhuman.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); -+ return Math.max(0, entityhuman.canPortalInstant ? 1 : world.getGameRules().getInt(entityhuman.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); // Purpur - } else { - return 0; - } -diff --git a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -index acbd60a2f162fe0e254e36d0e8e7face3fc8a7b3..b8355ea1de26c4b6905f477fb4e110f1762447b4 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java + public int getPortalTransitionTime(ServerLevel level, Entity entity) { + return entity instanceof Player player +- ? Math.max( ++ ? player.canPortalInstant ? 1 : Math.max( // Purpur - Add portal permission bypass + 0, + level.getGameRules() + .getInt( +diff --git a/net/minecraft/world/level/block/NetherWartBlock.java b/net/minecraft/world/level/block/NetherWartBlock.java +index 52891ddc14ad0e6f46a2f13d044723de9960b42d..4232fda95390be9faa9af6fa21e94deefe05ee69 100644 +--- a/net/minecraft/world/level/block/NetherWartBlock.java ++++ b/net/minecraft/world/level/block/NetherWartBlock.java @@ -16,7 +16,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class NetherWartBlock extends BushBlock { -+public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur - ++public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur - bonemealable netherwart public static final MapCodec CODEC = simpleCodec(NetherWartBlock::new); public static final int MAX_AGE = 3; -@@ -68,4 +68,32 @@ public class NetherWartBlock extends BushBlock { + public static final IntegerProperty AGE = BlockStateProperties.AGE_3; +@@ -70,4 +70,34 @@ public class NetherWartBlock extends BushBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(NetherWartBlock.AGE); + builder.add(AGE); } + -+ // Purpur start ++ // Purpur start - Ability for hoe to replant nether warts + @Override + public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) { + if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { @@ -15537,7 +15657,9 @@ index acbd60a2f162fe0e254e36d0e8e7face3fc8a7b3..b8355ea1de26c4b6905f477fb4e110f1 + super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp); + } + } ++ // Purpur end - Ability for hoe to replant nether warts + ++ // Purpur start - bonemealable netherwart + @Override + public boolean isValidBonemealTarget(final net.minecraft.world.level.LevelReader world, final BlockPos pos, final BlockState state) { + return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3; @@ -15554,124 +15676,123 @@ index acbd60a2f162fe0e254e36d0e8e7face3fc8a7b3..b8355ea1de26c4b6905f477fb4e110f1 + state = state.setValue(NetherWartBlock.AGE, i); + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit + } -+ // Purpur end ++ // Purpur end - bonemealable netherwart } -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 6582db84c5307257f16c321453491cf24e40c9c7..f9015d4e478efeec8a796b7a897638f76064db20 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -97,7 +97,7 @@ public class NoteBlock extends Block { +diff --git a/net/minecraft/world/level/block/NoteBlock.java b/net/minecraft/world/level/block/NoteBlock.java +index 32d83e3ceceba37a8f76b1cd3591e8afed685141..62db23309d722868e406a52b8e67c23273e36c96 100644 +--- a/net/minecraft/world/level/block/NoteBlock.java ++++ b/net/minecraft/world/level/block/NoteBlock.java +@@ -107,7 +107,7 @@ public class NoteBlock extends Block { } - private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { -- if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { -+ if (world.purpurConfig.noteBlockIgnoreAbove || ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { // Purpur - // CraftBukkit start - // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); - // if (event.isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -index 93ed9406c34804831b86d006dbd6087db9948f08..26cb9990b91991e0a2eadc2dcbbf229e2e88fb2d 100644 ---- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -@@ -75,6 +75,7 @@ public class ObserverBlock extends DirectionalBlock { - @Override - protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { - if (state.getValue(ObserverBlock.FACING) == direction && !(Boolean) state.getValue(ObserverBlock.POWERED)) { -+ if (!world.getWorldBorder().world.purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur - this.startSignal(world, tickView, pos); + private void playNote(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) { +- if (state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) { ++ if (level.purpurConfig.noteBlockIgnoreAbove || state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) { // Purpur - Config to allow Note Block sounds when blocked + level.blockEvent(pos, this, 0, 0); + level.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, pos); + } +diff --git a/net/minecraft/world/level/block/ObserverBlock.java b/net/minecraft/world/level/block/ObserverBlock.java +index 9ca4ae8cdf9d88b8b6a6ed5d4c0b306bd0539ffe..42c696aa307516642029646305a4f71a93a56628 100644 +--- a/net/minecraft/world/level/block/ObserverBlock.java ++++ b/net/minecraft/world/level/block/ObserverBlock.java +@@ -81,6 +81,7 @@ public class ObserverBlock extends DirectionalBlock { + RandomSource random + ) { + if (state.getValue(FACING) == direction && !state.getValue(POWERED)) { ++ if (!level.getWorldBorder().world.purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur - Add Option for disable observer clocks + this.startSignal(level, scheduledTickAccess, pos); } -diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index 53cea36ec931de89e0060613acf87beb51dc16ec..fd5489993dca0f940da69e9163f78e5c2e6ee063 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -194,7 +194,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate +diff --git a/net/minecraft/world/level/block/PointedDripstoneBlock.java b/net/minecraft/world/level/block/PointedDripstoneBlock.java +index 8580d44daeb0dacd96714537c2abafd9700cd16b..b70735764bd81c3570845c45aa79782b9867c30b 100644 +--- a/net/minecraft/world/level/block/PointedDripstoneBlock.java ++++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java +@@ -197,20 +197,20 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate @VisibleForTesting - public static void maybeTransferFluid(BlockState state, ServerLevel world, BlockPos pos, float dripChance) { -- if (dripChance <= 0.17578125F || dripChance <= 0.05859375F) { -+ if (dripChance <= world.purpurConfig.cauldronDripstoneWaterFillChance || dripChance <= world.purpurConfig.cauldronDripstoneLavaFillChance) { // Purpur - if (PointedDripstoneBlock.isStalactiteStartPos(state, world, pos)) { - Optional optional = PointedDripstoneBlock.getFluidAboveStalactite(world, pos, state); - -@@ -203,13 +203,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - float f1; - - if (fluidtype == Fluids.WATER) { -- f1 = 0.17578125F; -+ f1 = world.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur + public static void maybeTransferFluid(BlockState state, ServerLevel level, BlockPos pos, float randChance) { +- if (!(randChance > 0.17578125F) || !(randChance > 0.05859375F)) { ++ if (!(randChance > level.purpurConfig.cauldronDripstoneWaterFillChance) || !(randChance > level.purpurConfig.cauldronDripstoneLavaFillChance)) { // Purpur - Cauldron fill chances + if (isStalactiteStartPos(state, level, pos)) { + Optional fluidAboveStalactite = getFluidAboveStalactite(level, pos, state); + if (!fluidAboveStalactite.isEmpty()) { + Fluid fluid = fluidAboveStalactite.get().fluid; + float f; + if (fluid == Fluids.WATER) { +- f = 0.17578125F; ++ f = level.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur - Cauldron fill chances } else { - if (fluidtype != Fluids.LAVA) { + if (fluid != Fluids.LAVA) { return; } -- f1 = 0.05859375F; -+ f1 = world.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur +- f = 0.05859375F; ++ f = level.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur - Cauldron fill chances } - if (dripChance < f1) { -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 53f1a7ed6b4bd6e2d8460531226aabf249994c02..3760c3c9ab45d7152661edd5a48893e1b583fb95 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -@@ -76,7 +76,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { - if (world instanceof ServerLevel worldserver) { - // CraftBukkit start - if (entity.isOnFire() && entity.mayInteract(worldserver, pos)) { -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !((worldserver.purpurConfig.powderSnowBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || entity instanceof Player))) { // Purpur + if (!(randChance >= f)) { +diff --git a/net/minecraft/world/level/block/PowderSnowBlock.java b/net/minecraft/world/level/block/PowderSnowBlock.java +index 9c0ded7ae7e3a520704033a866f80743ae85d772..4f3646961beb877520e257e11224c3045467d351 100644 +--- a/net/minecraft/world/level/block/PowderSnowBlock.java ++++ b/net/minecraft/world/level/block/PowderSnowBlock.java +@@ -84,7 +84,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { + // CraftBukkit - move down + && entity.mayInteract(serverLevel, pos)) { + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(serverLevel.purpurConfig.powderSnowBypassMobGriefing ^ serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { return; } // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -index b763361a8f0f1b46093d5dd9afe8dba0cadf9c78..bd14c08defe8afc5ceca59d16a5b1dbad178f594 100644 ---- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -@@ -30,7 +30,7 @@ public class PoweredRailBlock extends BaseRailBlock { +diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java +index f33b53257a231dccf16740f1e78a3cdfa6e70726..4b3d00b085ea5d345b418815c9869f5f8c9fe558 100644 +--- a/net/minecraft/world/level/block/PoweredRailBlock.java ++++ b/net/minecraft/world/level/block/PoweredRailBlock.java +@@ -34,7 +34,7 @@ public class PoweredRailBlock extends BaseRailBlock { } - protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { -- if (distance >= 8) { -+ if (distance >= world.purpurConfig.railActivationRange) { // Purpur + protected boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) { +- if (recursionCount >= 8) { ++ if (recursionCount >= level.purpurConfig.railActivationRange) { // Purpur - Config for powered rail activation distance return false; } else { - int j = pos.getX(); -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 9117c035d5a6ff114b028fad3380ceb1fc2b9691..2c5e394156dbf76107adb4913a094dfd4a598dd7 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -149,7 +149,7 @@ public class RespawnAnchorBlock extends Block { + int x = pos.getX(); +diff --git a/net/minecraft/world/level/block/RespawnAnchorBlock.java b/net/minecraft/world/level/block/RespawnAnchorBlock.java +index e6e93814b6d22eb0ef4122c04ddce30c12c28d3f..e49fba46b006cb4b7568403d2d15777e253b0aab 100644 +--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java ++++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java +@@ -159,7 +159,7 @@ public class RespawnAnchorBlock extends Block { }; - Vec3 vec3d = explodedPos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state -+ if (world.purpurConfig.respawnAnchorExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // CraftBukkit - add state // Purpur + Vec3 center = pos2.getCenter(); + level.explode( +- null, level.damageSources().badRespawnPointExplosion(center, blockState), explosionDamageCalculator, center, 5.0F, true, Level.ExplosionInteraction.BLOCK // CraftBukkit - add state ++ null, level.damageSources().badRespawnPointExplosion(center, blockState), explosionDamageCalculator, center, (float) level.purpurConfig.respawnAnchorExplosionPower, level.purpurConfig.respawnAnchorExplosionFire, level.purpurConfig.respawnAnchorExplosionEffect // CraftBukkit - add state // Purpur - Implement respawn anchor explosion options + ); } - public static boolean canSetSpawn(Level world) { -diff --git a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java -index 0e05bfef55dcacb50766bba8328ffeb8a5394c31..e00fcea07e74de647c26ff9eb32bc682738c15b7 100644 ---- a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java -@@ -135,7 +135,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo - @Nullable +diff --git a/net/minecraft/world/level/block/SculkShriekerBlock.java b/net/minecraft/world/level/block/SculkShriekerBlock.java +index 3e7c0a4df225f727d927c7a0ddb47e3d898858c3..63ccc1719a89ee43fb53c712bcbaf5947b136621 100644 +--- a/net/minecraft/world/level/block/SculkShriekerBlock.java ++++ b/net/minecraft/world/level/block/SculkShriekerBlock.java +@@ -134,7 +134,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -- return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER); -+ return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, ctx.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState() +- .setValue(WATERLOGGED, Boolean.valueOf(context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER)); ++ .setValue(WATERLOGGED, Boolean.valueOf(context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER)).setValue(SculkShriekerBlock.CAN_SUMMON, context.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur - Config for sculk shrieker can_summon state } @Override -diff --git a/src/main/java/net/minecraft/world/level/block/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java -index 9274fd639c22e305dda567b303f9b01068adb52c..4433e432ea0ee8d11045b87e68dac3ed43e8cf82 100644 ---- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java +diff --git a/net/minecraft/world/level/block/SlabBlock.java b/net/minecraft/world/level/block/SlabBlock.java +index f6e39b69c1cafd9a429386cfd374ee643d258e97..ad57109f2766f44e386cb77fdce53580505efe33 100644 +--- a/net/minecraft/world/level/block/SlabBlock.java ++++ b/net/minecraft/world/level/block/SlabBlock.java @@ -150,4 +150,25 @@ public class SlabBlock extends Block implements SimpleWaterloggedBlock { return false; } } + -+ // Purpur start ++ // Purpur start - Break individual slabs when sneaking + public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) { + if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) { + return false; @@ -15690,34 +15811,29 @@ index 9274fd639c22e305dda567b303f9b01068adb52c..4433e432ea0ee8d11045b87e68dac3ed + } + return true; + } -+ // Purpur end ++ // Purpur end - Break individual slabs when sneaking } -diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -index 9908a0b5b1fec5f9de518a733f7abbbff7e1a9f9..0ad444cf7f798f63e9140a42c5d5d8cab0fcf14f 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -@@ -88,6 +88,12 @@ public class SnowLayerBlock extends Block { - protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState iblockdata1 = world.getBlockState(pos.below()); - -+ // Purpur start -+ if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) { -+ return false; -+ } -+ // Purpur end -+ - return iblockdata1.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) ? false : (iblockdata1.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) ? true : Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8); +diff --git a/net/minecraft/world/level/block/SnowLayerBlock.java b/net/minecraft/world/level/block/SnowLayerBlock.java +index dd2bd6afd5e97a9ff3d102546361fc2332525611..b53b210707e32509ee4b3bc8c026608f1beceb06 100644 +--- a/net/minecraft/world/level/block/SnowLayerBlock.java ++++ b/net/minecraft/world/level/block/SnowLayerBlock.java +@@ -96,6 +96,7 @@ public class SnowLayerBlock extends Block { + @Override + protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + BlockState blockState = level.getBlockState(pos.below()); ++ if (blockState.is(Blocks.BLUE_ICE) && !level.getWorldBorder().world.purpurConfig.snowOnBlueIce) return false; // Purpur - Add config for snow on blue ice + return !blockState.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) + && ( + blockState.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) +diff --git a/net/minecraft/world/level/block/SpawnerBlock.java b/net/minecraft/world/level/block/SpawnerBlock.java +index 635e2608f9148c27d5d632c1618826226eb1d879..1121843e9db120ef02818369a1fe5f487342a49b 100644 +--- a/net/minecraft/world/level/block/SpawnerBlock.java ++++ b/net/minecraft/world/level/block/SpawnerBlock.java +@@ -43,6 +43,57 @@ public class SpawnerBlock extends BaseEntityBlock { + ); } -diff --git a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -index 4f190a40b8474aa06a92c8afcc06d0044120ff7b..80ee7a6f010cc838625674007a3ea908f2f9dadd 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -@@ -42,6 +42,57 @@ public class SpawnerBlock extends BaseEntityBlock { - return createTickerHelper(type, BlockEntityType.MOB_SPAWNER, world.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick); - } - -+ // Purpur start ++ // Purpur start - Silk touch spawners + @Override + public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack, boolean includeDrops, boolean dropExp) { + if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) { @@ -15766,90 +15882,92 @@ index 4f190a40b8474aa06a92c8afcc06d0044120ff7b..80ee7a6f010cc838625674007a3ea908 + private boolean isSilkTouch(Level level, ItemStack stack) { + return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire; + } -+ // Purpur end ++ // Purpur end - Silk touch spawners + @Override - protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { - super.spawnAfterBreak(state, world, pos, tool, dropExperience); -@@ -50,6 +101,7 @@ public class SpawnerBlock extends BaseEntityBlock { + protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) { + super.spawnAfterBreak(state, level, pos, stack, dropExperience); +@@ -51,6 +102,7 @@ public class SpawnerBlock extends BaseEntityBlock { @Override - public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { -+ if (worldserver.purpurConfig.silkTouchEnabled && isSilkTouch(worldserver, itemstack)) return 0; // Purpur - if (flag) { - int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15); - -diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -index 59cf905b1b5686f6f4f2bad94730ffa69d3a2834..4c5bc71fef307d13b640e534ace0b4411f6e97e2 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -@@ -61,7 +61,7 @@ public class SpongeBlock extends Block { - - private boolean removeWaterBreadthFirstSearch(Level world, BlockPos pos) { - BlockStateListPopulator blockList = new BlockStateListPopulator(world); // CraftBukkit - Use BlockStateListPopulator -- BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> { -+ BlockPos.breadthFirstTraversal(pos, world.purpurConfig.spongeAbsorptionRadius, world.purpurConfig.spongeAbsorptionArea, (blockposition1, consumer) -> { // Purpur - Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS; - int i = aenumdirection.length; - -@@ -80,7 +80,7 @@ public class SpongeBlock extends Block { - FluidState fluid = blockList.getFluidState(blockposition1); - // CraftBukkit end - -- if (!fluid.is(FluidTags.WATER)) { -+ if (!fluid.is(FluidTags.WATER) && (!world.purpurConfig.spongeAbsorbsLava || !fluid.is(FluidTags.LAVA)) && (!world.purpurConfig.spongeAbsorbsWaterFromMud || !iblockdata.is(Blocks.MUD))) { // Purpur - return false; - } else { - Block block = iblockdata.getBlock(); -@@ -95,6 +95,10 @@ public class SpongeBlock extends Block { - - if (iblockdata.getBlock() instanceof LiquidBlock) { - blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit -+ // Purpur start -+ } else if (iblockdata.is(Blocks.MUD)) { -+ blockList.setBlock(blockposition1, Blocks.CLAY.defaultBlockState(), 3); -+ // Purpur end - } else { - if (!iblockdata.is(Blocks.KELP) && !iblockdata.is(Blocks.KELP_PLANT) && !iblockdata.is(Blocks.SEAGRASS) && !iblockdata.is(Blocks.TALL_SEAGRASS)) { - return false; -diff --git a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -index e61644241f24b42bb4f702d3eef5b590b4d107c8..0bf6503819b02e5ba2c346d9d563a93f2946d89b 100644 ---- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -@@ -98,4 +98,14 @@ public class StonecutterBlock extends Block { - protected boolean isPathfindable(BlockState state, PathComputationType type) { + public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) { ++ if (level.purpurConfig.silkTouchEnabled && isSilkTouch(level, stack)) return 0; // Purpur - Silk touch spawners + if (dropExperience) { + int i = 15 + level.random.nextInt(15) + level.random.nextInt(15); + // this.popExperience(level, pos, i); +diff --git a/net/minecraft/world/level/block/SpongeBlock.java b/net/minecraft/world/level/block/SpongeBlock.java +index 88e7320a48b08f052ae1cac914ebbcb905baf693..a20bf795d56a8b8aa1536eed887b57957952c714 100644 +--- a/net/minecraft/world/level/block/SpongeBlock.java ++++ b/net/minecraft/world/level/block/SpongeBlock.java +@@ -53,8 +53,8 @@ public class SpongeBlock extends Block { + org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level); // CraftBukkit - Use BlockStateListPopulator + BlockPos.breadthFirstTraversal( + pos, +- 6, +- 65, ++ level.purpurConfig.spongeAbsorptionRadius, // Purpur - Configurable sponge absorption ++ level.purpurConfig.spongeAbsorptionArea, // Purpur - Configurable sponge absorption + (validPos, queueAdder) -> { + for (Direction direction : ALL_DIRECTIONS) { + queueAdder.accept(validPos.relative(direction)); +@@ -68,7 +68,7 @@ public class SpongeBlock extends Block { + BlockState blockState = blockList.getBlockState(blockPos); + FluidState fluidState = blockList.getFluidState(blockPos); + // CraftBukkit end +- if (!fluidState.is(FluidTags.WATER)) { ++ if (!fluidState.is(FluidTags.WATER) && (!level.purpurConfig.spongeAbsorbsLava || !fluidState.is(FluidTags.LAVA)) && (!level.purpurConfig.spongeAbsorbsWaterFromMud || !blockState.is(Blocks.MUD))) { // Purpur - Option for sponges to work on lava and mud + return BlockPos.TraversalNodeStatus.SKIP; + } else if (blockState.getBlock() instanceof BucketPickup bucketPickup + && !bucketPickup.pickupBlock(null, blockList, blockPos, blockState).isEmpty()) { // CraftBukkit +@@ -76,6 +76,10 @@ public class SpongeBlock extends Block { + } else { + if (blockState.getBlock() instanceof LiquidBlock) { + blockList.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit ++ // Purpur start - Option for sponges to work on lava and mud ++ } else if (blockState.is(Blocks.MUD)) { ++ blockList.setBlock(blockPos, Blocks.CLAY.defaultBlockState(), 3); ++ // Purpur end - Option for sponges to work on lava and mud + } else { + if (!blockState.is(Blocks.KELP) + && !blockState.is(Blocks.KELP_PLANT) +diff --git a/net/minecraft/world/level/block/StonecutterBlock.java b/net/minecraft/world/level/block/StonecutterBlock.java +index 04706b35549d7d9c1684a106ab6bff6de8199bc9..3e1780bf622dde39ca2717e30ec5d79fa0fa872e 100644 +--- a/net/minecraft/world/level/block/StonecutterBlock.java ++++ b/net/minecraft/world/level/block/StonecutterBlock.java +@@ -93,4 +93,14 @@ public class StonecutterBlock extends Block { + protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return false; } + -+ // Purpur start ++ // Purpur start - Stonecutter damage + @Override + public void stepOn(Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.Entity entity) { + if (level.purpurConfig.stonecutterDamage > 0.0F && entity instanceof net.minecraft.world.entity.LivingEntity) { -+ entity.hurt(entity.damageSources().stonecutter().directBlock(level, pos), level.purpurConfig.stonecutterDamage); ++ entity.hurtServer((net.minecraft.server.level.ServerLevel) level, entity.damageSources().stonecutter().directBlock(level, pos), level.purpurConfig.stonecutterDamage); + } + super.stepOn(level, pos, state, entity); + } -+ // Purpur end ++ // Purpur end - Stonecutter damage } -diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -index 547ea09ed84595286c97c128b3b96f6d387ae25f..d0f8a13f27132257ece6dadf736c2dc6b1e5720e 100644 ---- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -@@ -20,7 +20,7 @@ import net.minecraft.world.level.material.FluidState; +diff --git a/net/minecraft/world/level/block/SugarCaneBlock.java b/net/minecraft/world/level/block/SugarCaneBlock.java +index 63d53f9090caca304c7f8c3f9910c57a6bdbb4d5..5a61216cea641f75fd1937d485651f6b6e4b6384 100644 +--- a/net/minecraft/world/level/block/SugarCaneBlock.java ++++ b/net/minecraft/world/level/block/SugarCaneBlock.java +@@ -19,7 +19,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class SugarCaneBlock extends Block { -+public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur - ++public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur - bonemealable sugarcane public static final MapCodec CODEC = simpleCodec(SugarCaneBlock::new); public static final IntegerProperty AGE = BlockStateProperties.AGE_15; + protected static final float AABB_OFFSET = 6.0F; @@ -113,4 +113,34 @@ public class SugarCaneBlock extends Block { protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(SugarCaneBlock.AGE); + builder.add(AGE); } + -+ // Purpur start ++ // Purpur start - bonemealable sugarcane + @Override + public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { + if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; @@ -15877,160 +15995,165 @@ index 547ea09ed84595286c97c128b3b96f6d387ae25f..d0f8a13f27132257ece6dadf736c2dc6 + world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0)); + } + } -+ // Purpur end ++ // Purpur end - bonemealable sugarcane } -diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -index 953ddb2ea6fd48e57712e30a6addf23e188e5312..f1dfb23160ff70e0da4dd2af2d83e879527c6651 100644 ---- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -@@ -171,7 +171,7 @@ public class TurtleEggBlock extends Block { - private boolean shouldUpdateHatchLevel(Level world) { - float f = world.getTimeOfDay(1.0F); +diff --git a/net/minecraft/world/level/block/TurtleEggBlock.java b/net/minecraft/world/level/block/TurtleEggBlock.java +index 8a09af26bbccdecd19607da84c11029c3cb9a11e..a3a093d95306baac22e5cf720f5b14f733b548d4 100644 +--- a/net/minecraft/world/level/block/TurtleEggBlock.java ++++ b/net/minecraft/world/level/block/TurtleEggBlock.java +@@ -157,7 +157,7 @@ public class TurtleEggBlock extends Block { -- return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(500) == 0; -+ return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(world.purpurConfig.turtleEggsRandomTickCrackChance) == 0; + private boolean shouldUpdateHatchLevel(Level level) { + float timeOfDay = level.getTimeOfDay(1.0F); +- return timeOfDay < 0.69 && timeOfDay > 0.65 || level.random.nextInt(500) == 0; ++ return timeOfDay < 0.69 && timeOfDay > 0.65 || level.random.nextInt(level.purpurConfig.turtleEggsRandomTickCrackChance) == 0; // Purpur - Turtle eggs random tick crack chance } @Override -@@ -204,6 +204,31 @@ public class TurtleEggBlock extends Block { +@@ -192,9 +192,31 @@ public class TurtleEggBlock extends Block { } - private boolean canDestroyEgg(ServerLevel world, Entity entity) { -- return !(entity instanceof Turtle) && !(entity instanceof Bat) ? (!(entity instanceof LivingEntity) ? false : entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) : false; -+ // Purpur start + private boolean canDestroyEgg(ServerLevel level, Entity entity) { +- return !(entity instanceof Turtle) +- && !(entity instanceof Bat) +- && entity instanceof LivingEntity +- && (entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); ++ // Purpur start - Add turtle egg block options + if (entity instanceof Turtle || entity instanceof Bat) { + return false; + } -+ if (world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) { ++ if (level.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) { + return true; + } -+ if (world.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ if (level.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) { + return true; + } -+ if (world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) { ++ if (level.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) { + return true; + } + if (!(entity instanceof LivingEntity)) { + return false; + } -+ if (world.purpurConfig.turtleEggsTramplingFeatherFalling) { ++ // Purpur start - Option to disable turtle egg trampling with feather falling ++ if (level.purpurConfig.turtleEggsTramplingFeatherFalling) { + java.util.Iterator armor = ((LivingEntity) entity).getArmorSlots().iterator(); + return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) < (int) entity.fallDistance; + } -+ if (entity instanceof Player) { -+ return true; -+ } ++ // Purpur end - Option to disable turtle egg trampling with feather falling ++ if (entity instanceof Player) return true; + -+ return world.purpurConfig.turtleEggsBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ // Purpur end ++ return level.purpurConfig.turtleEggsBypassMobGriefing ^ level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected ++ // Purpur end - Add turtle egg block options } } -diff --git a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -index 6342bb11a162b9e6d9475c5989b1670d77e8f0fb..f8be92512446d3f0e5f0f21222bbefd04ab2838a 100644 ---- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java +diff --git a/net/minecraft/world/level/block/TwistingVinesBlock.java b/net/minecraft/world/level/block/TwistingVinesBlock.java +index 138832bb247f263aa393e9c3d9b5e9c79743c24c..3f7b0e488fd259badced8a2b4b09b4930b3d9573 100644 +--- a/net/minecraft/world/level/block/TwistingVinesBlock.java ++++ b/net/minecraft/world/level/block/TwistingVinesBlock.java @@ -34,4 +34,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + -+ // Purpur start ++ // Purpur start - twisting vines configurable max growth age + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge; + } -+ // Purpur end ++ // Purpur end - twisting vines configurable max growth age } -diff --git a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -index 3dec5a082606ee35a8c8d7f746480262d6a189c5..b2f6ccae9576c176263e51a232e17a08d54543b3 100644 ---- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java +diff --git a/net/minecraft/world/level/block/WeepingVinesBlock.java b/net/minecraft/world/level/block/WeepingVinesBlock.java +index 9d7fa63a699e7042118b29b154c62f406b51861a..92763699d12118ab0a304afbd564fd1c954ee749 100644 +--- a/net/minecraft/world/level/block/WeepingVinesBlock.java ++++ b/net/minecraft/world/level/block/WeepingVinesBlock.java @@ -34,4 +34,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + -+ // Purpur start ++ // Purpur start - weeping vines configurable max growth age + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge; + } -+ // Purpur end ++ // Purpur end - weeping vines configurable max growth age } -diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -index 0fbe66cc02bd3d95c0a5dcd55380a1b4a2f17ca6..3a7126883f11ac5a647947eaf060df15536a6cb2 100644 ---- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -@@ -80,6 +80,7 @@ public class WitherSkullBlock extends SkullBlock { - entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F); - entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; - entitywither.makeInvulnerable(); -+ entitywither.setSummoner(iblockdata.getBlock().placer == null ? null : iblockdata.getBlock().placer.getUUID()); // Purpur +diff --git a/net/minecraft/world/level/block/WitherSkullBlock.java b/net/minecraft/world/level/block/WitherSkullBlock.java +index dc70aaa8d929c40c5f34c8facc1ad2bff4e98768..31d776ce04a8035977ad82527e90ab3b215940c1 100644 +--- a/net/minecraft/world/level/block/WitherSkullBlock.java ++++ b/net/minecraft/world/level/block/WitherSkullBlock.java +@@ -71,6 +71,7 @@ public class WitherSkullBlock extends SkullBlock { + ); + witherBoss.yBodyRot = blockPatternMatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; + witherBoss.makeInvulnerable(); ++ witherBoss.setSummoner(blockState.getBlock().placer == null ? null : blockState.getBlock().placer.getUUID()); // Purpur - Summoner API // CraftBukkit start - if (!world.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) { + if (!level.addFreshEntity(witherBoss, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) { return; -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 43899f544cc22666f9ca496128650ce7751ec913..9cba9ebcaa3935778865e3af5211e0b0796cbe16 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 -@@ -219,6 +219,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit +diff --git a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index ad9f009dd6c86e08c193839479b4b3285afd7dfb..901201c36c6dcd35e32e1da524a06d41e34da11f 100644 +--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -191,6 +191,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit } - ItemStack itemstack = (ItemStack) blockEntity.items.get(1); -+ // Purpur start + ItemStack itemStack = furnace.items.get(1); ++ // Purpur start - Furnace uses lava from underneath + boolean usedLavaFromUnderneath = false; -+ if (world.purpurConfig.furnaceUseLavaFromUnderneath && !blockEntity.isLit() && itemstack.isEmpty() && !blockEntity.items.get(0).isEmpty() && world.getGameTime() % 20 == 0) { -+ BlockPos below = blockEntity.getBlockPos().below(); -+ BlockState belowState = world.getBlockStateIfLoaded(below); ++ if (level.purpurConfig.furnaceUseLavaFromUnderneath && !furnace.isLit() && itemStack.isEmpty() && !furnace.items.get(0).isEmpty() && level.getGameTime() % 20 == 0) { ++ BlockPos below = furnace.getBlockPos().below(); ++ BlockState belowState = level.getBlockStateIfLoaded(below); + if (belowState != null && belowState.is(Blocks.LAVA)) { + net.minecraft.world.level.material.FluidState fluidState = belowState.getFluidState(); + if (fluidState != null && fluidState.isSource()) { -+ world.setBlock(below, Blocks.AIR.defaultBlockState(), 3); -+ itemstack = Items.LAVA_BUCKET.getDefaultInstance(); ++ level.setBlock(below, Blocks.AIR.defaultBlockState(), 3); ++ itemStack = Items.LAVA_BUCKET.getDefaultInstance(); + usedLavaFromUnderneath = true; + } + } + } -+ // Purpur end - ItemStack itemstack1 = (ItemStack) blockEntity.items.get(0); - boolean flag2 = !itemstack1.isEmpty(); - boolean flag3 = !itemstack.isEmpty(); -@@ -308,6 +323,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - setChanged(world, pos, state); ++ // Purpur end - Furnace uses lava from underneath + ItemStack itemStack1 = furnace.items.get(0); + boolean flag1 = !itemStack1.isEmpty(); + boolean flag2 = !itemStack.isEmpty(); +@@ -274,6 +289,8 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + if (flag) { + setChanged(level, pos, state); } - -+ if (usedLavaFromUnderneath) blockEntity.items.set(1, ItemStack.EMPTY); // Purpur ++ ++ if (usedLavaFromUnderneath) furnace.items.set(1, ItemStack.EMPTY); // Purpur - Furnace uses lava from underneath } - private static boolean canBurn(RegistryAccess dynamicRegistryManager, @Nullable RecipeHolder recipe, SingleRecipeInput input, NonNullList inventory, int maxCount) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -index 618552afbdacc919c33b30a6bf4834fb71ab3d5b..7a059d20abdcc0073a314311d78f63ae4bd0365e 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -@@ -68,7 +68,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - public BarrelBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.BARREL, pos, state); -- this.items = NonNullList.withSize(27, ItemStack.EMPTY); -+ // Purpur start -+ this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }, ItemStack.EMPTY); -+ // Purpur end - this.openersCounter = new ContainerOpenersCounter() { - @Override - protected void onOpen(Level world, BlockPos pos, BlockState state) { -@@ -119,7 +128,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { + private static boolean canBurn( +diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +index 0f808855f58281578c2758513787f0f7330c9291..9f6063089f0aa3a68d26ae7cfe39379123ab2f47 100644 +--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +@@ -55,7 +55,17 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { + this.maxStack = i; + } + // CraftBukkit end +- private NonNullList items = NonNullList.withSize(27, ItemStack.EMPTY); ++ // Purpur start - Barrels and enderchests 6 rows ++ private NonNullList items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { ++ case 6 -> 54; ++ case 5 -> 45; ++ case 4 -> 36; ++ case 2 -> 18; ++ case 1 -> 9; ++ default -> 27; ++ }, ItemStack.EMPTY); ++ // Purpur end - Barrels and enderchests 6 rows ++ + public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() { + @Override + protected void onOpen(Level level, BlockPos pos, BlockState state) { +@@ -107,7 +117,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override public int getContainerSize() { - return 27; -+ // Purpur start ++ // Purpur start - Barrels and enderchests 6 rows + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; @@ -16039,37 +16162,37 @@ index 618552afbdacc919c33b30a6bf4834fb71ab3d5b..7a059d20abdcc0073a314311d78f63ae + case 1 -> 9; + default -> 27; + }; -+ // Purpur end ++ // Purpur end - Barrels and enderchests 6 rows } @Override -@@ -139,7 +157,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { +@@ -127,7 +146,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override - protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { -- return ChestMenu.threeRows(syncId, playerInventory, this); -+ // Purpur start + protected AbstractContainerMenu createMenu(int id, Inventory player) { +- return ChestMenu.threeRows(id, player, this); ++ // Purpur start - Barrels and enderchests 6 rows + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> ChestMenu.sixRows(syncId, playerInventory, this); -+ case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this); -+ case 4 -> ChestMenu.fourRows(syncId, playerInventory, this); -+ case 2 -> ChestMenu.twoRows(syncId, playerInventory, this); -+ case 1 -> ChestMenu.oneRow(syncId, playerInventory, this); -+ default -> ChestMenu.threeRows(syncId, playerInventory, this); ++ case 6 -> ChestMenu.sixRows(id, player, this); ++ case 5 -> ChestMenu.fiveRows(id, player, this); ++ case 4 -> ChestMenu.fourRows(id, player, this); ++ case 2 -> ChestMenu.twoRows(id, player, this); ++ case 1 -> ChestMenu.oneRow(id, player, this); ++ default -> ChestMenu.threeRows(id, player, this); + }; -+ // Purpur end ++ // Purpur end - Barrels and enderchests 6 rows } @Override -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 0e0d178f2793ab014358f534c8dc53218b89f083..2d190b3a6378b8cbadfa65510df1ccfbd5882ef8 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 -@@ -92,6 +92,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name +diff --git a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index deef33d96db188cb297f04b581ab29e77e3716a9..80b0feac68813f11dc5cadc5faf413a59ad73e5b 100644 +--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -139,6 +139,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name public double getEffectRange() { if (this.effectRange < 0) { -+ // Purpur Start ++ // Purpur start - Beacon Activation Range Configurable + if (this.level != null) { + switch (this.levels) { + case 1: return this.level.purpurConfig.beaconLevelOne; @@ -16078,55 +16201,53 @@ index 0e0d178f2793ab014358f534c8dc53218b89f083..2d190b3a6378b8cbadfa65510df1ccfb + case 4: return this.level.purpurConfig.beaconLevelFour; + } + } -+ // Purpur End ++ // Purpur end - Beacon Activation Range Configurable return this.levels * 10 + 10; } else { return effectRange; @@ -168,6 +178,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - int j = pos.getY(); - int k = pos.getZ(); - BlockPos blockposition1; -+ boolean isTintedGlass = false; - - if (blockEntity.lastCheckY < j) { - blockposition1 = pos; -@@ -201,6 +212,9 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + int y = pos.getY(); + int z = pos.getZ(); + BlockPos blockPos; ++ boolean isTintedGlass = false; // Purpur - allow beacon effects when covered by tinted glass + if (blockEntity.lastCheckY < y) { + blockPos = pos; + blockEntity.checkingBeamSections = Lists.newArrayList(); +@@ -197,6 +208,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name } } } else { -+ if (world.purpurConfig.beaconAllowEffectsWithTintedGlass && block.equals(Blocks.TINTED_GLASS)) { -+ isTintedGlass = true; -+ } - if (tileentitybeacon_beaconcolortracker == null || iblockdata1.getLightBlock() >= 15 && !iblockdata1.is(Blocks.BEDROCK)) { ++ if (level.purpurConfig.beaconAllowEffectsWithTintedGlass && blockState.getBlock().equals(Blocks.TINTED_GLASS)) {isTintedGlass = true;} // Purpur - allow beacon effects when covered by tinted glass + if (beaconBeamSection == null || blockState.getLightBlock() >= 15 && !blockState.is(Blocks.BEDROCK)) { blockEntity.checkingBeamSections.clear(); - blockEntity.lastCheckY = l; -@@ -220,7 +234,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - blockEntity.levels = BeaconBlockEntity.updateBase(world, i, j, k); + blockEntity.lastCheckY = height; +@@ -216,7 +228,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + blockEntity.levels = updateBase(level, x, y, z); } - if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { -+ if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (world.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { - BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); ++ if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (level.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { // Purpur - allow beacon effects when covered by tinted glass + applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges + playSound(level, pos, SoundEvents.BEACON_AMBIENT); } -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 83ad45aed0894e90825d22e078632352c3a06816..def408384cbd571b7bee23f5cecf430a5d690c4b 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 -@@ -60,7 +60,7 @@ public class BeehiveBlockEntity extends BlockEntity { - private List stored = Lists.newArrayList(); - @Nullable - public BlockPos savedFlowerPos; -- public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold -+ public int maxBees = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // CraftBukkit - allow setting max amount of bees a hive can hold // Purpur - - public BeehiveBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.BEEHIVE, pos, state); -@@ -147,11 +147,33 @@ public class BeehiveBlockEntity extends BlockEntity { +diff --git a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index 47582c2cbae227c47684b8451c7bac39bce7e0aa..cb9b08d8bd25f0483c28cdcfdf31dc56d122643e 100644 +--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -76,7 +76,7 @@ public class BeehiveBlockEntity extends BlockEntity { + "leash", + "UUID" + ); +- public static final int MAX_OCCUPANTS = 3; ++ public static final int MAX_OCCUPANTS = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // Purpur - Config to change max number of bees + private static final int MIN_TICKS_BEFORE_REENTERING_HIVE = 400; + private static final int MIN_OCCUPATION_TICKS_NECTAR = 2400; + public static final int MIN_OCCUPATION_TICKS_NECTARLESS = 600; +@@ -154,11 +154,33 @@ public class BeehiveBlockEntity extends BlockEntity { return list; } -+ // Purpur start ++ // Purpur start - Stored Bee API + public List releaseBee(BlockState iblockdata, BeehiveBlockEntity.BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { + List list = Lists.newArrayList(); + @@ -16140,72 +16261,73 @@ index 83ad45aed0894e90825d22e078632352c3a06816..def408384cbd571b7bee23f5cecf430a + + return list; + } -+ // Purpur end ++ // Purpur end - Stored Bee API + @VisibleForDebug public int getOccupantCount() { return this.stored.size(); } -+ // Purpur start ++ // Purpur start - Stored Bee API + public List getStored() { + return stored; + } -+ // Purpur end ++ // Purpur end - Stored Bee API + // Paper start - Add EntityBlockStorage clearEntities public void clearBees() { this.stored.clear(); -@@ -472,9 +494,9 @@ public class BeehiveBlockEntity extends BlockEntity { - } +@@ -408,8 +430,8 @@ public class BeehiveBlockEntity extends BlockEntity { + return this.stored.stream().map(BeehiveBlockEntity.BeeData::toOccupant).toList(); } -- private static class BeeData { -+ public static class BeeData { // Purpur - change from private to public - +- static class BeeData { - private final BeehiveBlockEntity.Occupant occupant; -+ public final BeehiveBlockEntity.Occupant occupant; // Purpur - make public ++ public static class BeeData { // Purpur - make public - Stored Bee API ++ public final BeehiveBlockEntity.Occupant occupant; // Purpur - make public - Stored Bee API private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts private int ticksInHive; -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 1f929b467a0ece3143af58a657cf5983c07a8d51..eaa6ece956f90632831f0558924eaf18680a252b 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 -@@ -95,6 +95,12 @@ public abstract class BlockEntity { - if (persistentDataTag instanceof CompoundTag) { +diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java +index 77618757c0e678532dbab814aceed83f7f1cd892..3fd0f42618e5c2c683335d1d3e0bb74c6d32ef66 100644 +--- a/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -84,6 +84,14 @@ public abstract class BlockEntity { this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); } -+ // Purpur start -+ if (nbt.contains("Purpur.persistentLore")) { -+ net.minecraft.world.item.component.ItemLore.CODEC.decode(net.minecraft.nbt.NbtOps.INSTANCE, nbt.getCompound("Purpur.persistentLore")).result() -+ .ifPresent(tag -> this.persistentLore = tag.getFirst()); + // Paper end - read persistent data container ++ ++ // Purpur start - Persistent BlockEntity Lore and DisplayName ++ if (tag.contains("Purpur.persistentLore")) { ++ net.minecraft.world.item.component.ItemLore.CODEC.decode(net.minecraft.nbt.NbtOps.INSTANCE, tag.getCompound("Purpur.persistentLore")).result() ++ .ifPresent(tag1 -> this.persistentLore = tag1.getFirst()); + } -+ // Purpur end - } - // CraftBukkit end - -@@ -111,6 +117,15 @@ public abstract class BlockEntity { - this.loadAdditional(nbt, registries); ++ // Purpur end - Persistent BlockEntity Lore and DisplayName ++ } -+ // Purpur start + public final void loadWithComponents(CompoundTag tag, HolderLookup.Provider registries) { +@@ -98,6 +106,15 @@ public abstract class BlockEntity { + this.loadAdditional(tag, registries); + } + ++ // Purpur start - Persistent BlockEntity Lore and DisplayName + protected void saveAdditional(CompoundTag nbt) { + if (this.persistentLore != null) { + net.minecraft.world.item.component.ItemLore.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, this.persistentLore).result() + .ifPresent(tag -> nbt.put("Purpur.persistentLore", tag)); + } + } -+ // Purpur end ++ // Purpur end - Persistent BlockEntity Lore and DisplayName + - protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {} - - public final CompoundTag saveWithFullMetadata(HolderLookup.Provider registries) { -@@ -419,4 +434,16 @@ public abstract class BlockEntity { - - T getOrDefault(DataComponentType type, T fallback); + protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { } -+ // Purpur start + +@@ -378,4 +395,16 @@ public abstract class BlockEntity { + + T getOrDefault(DataComponentType component, T defaultValue); + } ++ // Purpur start - Persistent BlockEntity Lore and DisplayName + @Nullable + private net.minecraft.world.item.component.ItemLore persistentLore = null; + @@ -16216,121 +16338,118 @@ index 1f929b467a0ece3143af58a657cf5983c07a8d51..eaa6ece956f90632831f0558924eaf18 + public @org.jetbrains.annotations.Nullable net.minecraft.world.item.component.ItemLore getPersistentLore() { + return this.persistentLore; + } -+ // Purpur end ++ // Purpur end - Persistent BlockEntity Lore and DisplayName } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -index d354c2ad41533ec8d52f7c5f63f8f74a0b1ce235..a43c41a26c1e34a2f4eda1c498a562664a783c77 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -@@ -169,7 +169,7 @@ public class ConduitBlockEntity extends BlockEntity { - if ((l > 1 || i1 > 1 || j1 > 1) && (i == 0 && (i1 == 2 || j1 == 2) || j == 0 && (l == 2 || j1 == 2) || k == 0 && (l == 2 || i1 == 2))) { - BlockPos blockposition2 = pos.offset(i, j, k); - BlockState iblockdata = world.getBlockState(blockposition2); -- Block[] ablock = ConduitBlockEntity.VALID_BLOCKS; -+ Block[] ablock = world.purpurConfig.conduitBlocks; // Purpur - int k1 = ablock.length; +diff --git a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +index 269994df977801c67b8c576bde2478624b2631a1..b1689c67458f77f19c86d5d1d23f30e10a27d7a8 100644 +--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +@@ -155,7 +155,7 @@ public class ConduitBlockEntity extends BlockEntity { + BlockPos blockPos1 = pos.offset(i, i1, i2x); + BlockState blockState = level.getBlockState(blockPos1); - for (int l1 = 0; l1 < k1; ++l1) { -@@ -189,13 +189,13 @@ public class ConduitBlockEntity extends BlockEntity { +- for (Block block : VALID_BLOCKS) { ++ for (Block block : level.purpurConfig.conduitBlocks) { // Purpur - Conduit behavior configuration + if (blockState.is(block)) { + positions.add(blockPos1); + } +@@ -170,13 +170,13 @@ public class ConduitBlockEntity extends BlockEntity { - private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { + private static void applyEffects(Level level, BlockPos pos, List positions) { // CraftBukkit start -- ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks)); -+ ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks, world)); // Purpur +- ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions)); ++ ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions, level)); // Purpur - Conduit behavior configuration } -- public static int getRange(List list) { -+ public static int getRange(List list, Level world) { // Purpur +- public static int getRange(List positions) { ++ public static int getRange(List positions, Level level) { // Purpur - Conduit behavior configuration // CraftBukkit end - int i = list.size(); -- int j = i / 7 * 16; -+ int j = i / 7 * world.purpurConfig.conduitDistance; // Purpur + int size = positions.size(); +- int i = size / 7 * 16; ++ int i = size / 7 * level.purpurConfig.conduitDistance; // Purpur - Conduit behavior configuration // CraftBukkit start - return j; + return i; } -@@ -238,20 +238,20 @@ public class ConduitBlockEntity extends BlockEntity { - tileentityconduit.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, blockposition, tileentityconduit.destroyTargetUUID); - tileentityconduit.destroyTargetUUID = null; - } else if (tileentityconduit.destroyTarget == null) { -- List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition), (entityliving1) -> { -+ List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition, world), (entityliving1) -> { // Purpur - return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); - }); - - if (!list1.isEmpty()) { - tileentityconduit.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); +@@ -213,17 +213,17 @@ public class ConduitBlockEntity extends BlockEntity { + blockEntity.destroyTargetUUID = null; + } else if (blockEntity.destroyTarget == null) { + List entitiesOfClass = level.getEntitiesOfClass( +- LivingEntity.class, getDestroyRangeAABB(pos), collidedEntity -> collidedEntity instanceof Enemy && collidedEntity.isInWaterOrRain() ++ LivingEntity.class, getDestroyRangeAABB(pos, level), collidedEntity -> collidedEntity instanceof Enemy && collidedEntity.isInWaterOrRain() // Purpur - Conduit behavior configuration + ); + if (!entitiesOfClass.isEmpty()) { + blockEntity.destroyTarget = entitiesOfClass.get(level.random.nextInt(entitiesOfClass.size())); } -- } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), 8.0D)) { -+ } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur - tileentityconduit.destroyTarget = null; +- } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0)) { ++ } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), level.purpurConfig.conduitDamageDistance)) { // Purpur - Conduit behavior configuration + blockEntity.destroyTarget = null; } - // CraftBukkit start - if (damageTarget && tileentityconduit.destroyTarget != null) { -- if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), 4.0F)) { -+ if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), world.purpurConfig.conduitDamageAmount)) { // Purpur - world.playSound(null, tileentityconduit.destroyTarget.getX(), tileentityconduit.destroyTarget.getY(), tileentityconduit.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); - } - // CraftBukkit end -@@ -276,16 +276,22 @@ public class ConduitBlockEntity extends BlockEntity { + if (damageTarget && blockEntity.destroyTarget != null) { // CraftBukkit +- if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic().directBlock(level, pos), 4.0F)) // CraftBukkit ++ if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic().directBlock(level, pos), level.purpurConfig.conduitDamageAmount)) // CraftBukkit // Purpur - Conduit behavior configuration + level.playSound( + null, + blockEntity.destroyTarget.getX(), +@@ -253,16 +253,22 @@ public class ConduitBlockEntity extends BlockEntity { } public static AABB getDestroyRangeAABB(BlockPos pos) { -+ // Purpur start ++ // Purpur start - Conduit behavior configuration + return getDestroyRangeAABB(pos, null); + } + + private static AABB getDestroyRangeAABB(BlockPos pos, Level level) { -+ // Purpur end - int i = pos.getX(); - int j = pos.getY(); - int k = pos.getZ(); - -- return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(8.0D); -+ return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(level == null ? 8.0D : level.purpurConfig.conduitDamageDistance); // Purpur ++ // Purpur end - Conduit behavior configuration + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); +- return new AABB(x, y, z, x + 1, y + 1, z + 1).inflate(8.0); ++ return new AABB(x, y, z, x + 1, y + 1, z + 1).inflate(level == null ? 8.0 : level.purpurConfig.conduitDamageDistance); // Purpur - Conduit behavior configuration } @Nullable - private static LivingEntity findDestroyTarget(Level world, BlockPos pos, UUID uuid) { -- List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving) -> { -+ List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving) -> { // Purpur - return entityliving.getUUID().equals(uuid); - }); - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java -index 39aac959775afeaeea211f21d498cb0ddf0a3fcb..6349a342c023f378af431a73a62fb017882e257d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java + private static LivingEntity findDestroyTarget(Level level, BlockPos pos, UUID targetId) { + List entitiesOfClass = level.getEntitiesOfClass( +- LivingEntity.class, getDestroyRangeAABB(pos), collidedEntity -> collidedEntity.getUUID().equals(targetId) ++ LivingEntity.class, getDestroyRangeAABB(pos, level), collidedEntity -> collidedEntity.getUUID().equals(targetId) // Purpur - Conduit behavior configuration + ); + return entitiesOfClass.size() == 1 ? entitiesOfClass.get(0) : null; + } +diff --git a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java +index 337bbef7e89a46bc4e7d6763db6d1e9ffe5a8c2e..5ced128ad3b9ce5e32a15fd996ef4bd07c72da97 100644 +--- a/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java @@ -28,6 +28,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable private static final RandomSource RANDOM = RandomSource.create(); @Nullable private Component name; -+ private int lapis = 0; // Purpur ++ private int lapis = 0; // Purpur - Enchantment Table Persists Lapis public EnchantingTableBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.ENCHANTING_TABLE, pos, state); @@ -39,6 +40,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable if (this.hasCustomName()) { - nbt.putString("CustomName", Component.Serializer.toJson(this.name, registries)); + tag.putString("CustomName", Component.Serializer.toJson(this.name, registries)); } -+ nbt.putInt("Purpur.Lapis", this.lapis); // Purpur ++ tag.putInt("Purpur.Lapis", this.lapis); // Purpur - Enchantment Table Persists Lapis } @Override @@ -47,6 +49,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable - if (nbt.contains("CustomName", 8)) { - this.name = parseCustomNameSafe(nbt.getString("CustomName"), registries); + if (tag.contains("CustomName", 8)) { + this.name = parseCustomNameSafe(tag.getString("CustomName"), registries); } -+ this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur ++ this.lapis = tag.getInt("Purpur.Lapis"); // Purpur - Enchantment Table Persists Lapis } - public static void bookAnimationTick(Level world, BlockPos pos, BlockState state, EnchantingTableBlockEntity blockEntity) { + public static void bookAnimationTick(Level level, BlockPos pos, BlockState state, EnchantingTableBlockEntity enchantingTable) { @@ -138,4 +141,14 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable - public void removeComponentsFromTag(CompoundTag nbt) { - nbt.remove("CustomName"); + public void removeComponentsFromTag(CompoundTag tag) { + tag.remove("CustomName"); } + -+ // Purpur start ++ // Purpur start - Enchantment Table Persists Lapis + public int getLapis() { + return this.lapis; + } @@ -16338,30 +16457,17 @@ index 39aac959775afeaeea211f21d498cb0ddf0a3fcb..6349a342c023f378af431a73a62fb017 + public void setLapis(int lapis) { + this.lapis = lapis; + } -+ // Purpur ++ // Purpur end - Enchantment Table Persists Lapis } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java b/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java -index 61ef08ac941b1e8988d001241780d3a1582f7a2d..2eab5b43ab654966d26424597c1f3baa919e8434 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java -@@ -17,7 +17,7 @@ import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.block.Blocks; - - public class FuelValues { -- private final Object2IntSortedMap values; -+ public final Object2IntSortedMap values; // Purpur - private -> public - - FuelValues(Object2IntSortedMap fuelValues) { - this.values = fuelValues; -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 8ac19e1e052e73ff3fd09089bb8e3fd687390ee4..6da1eec98c08e4909ecbd48fe90b3fd62011e284 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 -@@ -201,16 +201,31 @@ public class SignBlockEntity extends BlockEntity { - return this.setText((SignText) textChanger.apply(signtext), front); +diff --git a/net/minecraft/world/level/block/entity/SignBlockEntity.java b/net/minecraft/world/level/block/entity/SignBlockEntity.java +index a1a537fc373b92b84f003844e3ea328bb91b9a4a..662f53ca5826fb5b68eb4d426f1d9c5d83906eaf 100644 +--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -163,16 +163,32 @@ public class SignBlockEntity extends BlockEntity { + return this.setText(updater.apply(text), isFrontText); } -+ // Purpur start ++ // Purpur start - Signs allow color codes + private Component translateColors(org.bukkit.entity.Player player, String line, Style style) { + if (level.purpurConfig.signAllowColors) { + if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); @@ -16373,29 +16479,30 @@ index 8ac19e1e052e73ff3fd09089bb8e3fd687390ee4..6da1eec98c08e4909ecbd48fe90b3fd6 + return Component.literal(line).setStyle(style); + } + } -+ // Purpur end ++ // Purpur end - Signs allow color codes + - private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List list, SignText signtext, boolean front) { // CraftBukkit - SignText originalText = signtext; // CraftBukkit - for (int i = 0; i < list.size(); ++i) { - FilteredText filteredtext = (FilteredText) list.get(i); - Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); - -+ org.bukkit.entity.Player player = (org.bukkit.craftbukkit.entity.CraftPlayer) entityhuman.getBukkitEntity(); // Purpur - if (entityhuman.isTextFilteringEnabled()) { -- signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only -+ signtext = signtext.setMessage(i, translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur + private SignText setMessages(Player player, List filteredText, SignText text, boolean front) { // CraftBukkit + SignText originalText = text; // CraftBukkit + for (int i = 0; i < filteredText.size(); i++) { + FilteredText filteredText1 = filteredText.get(i); + Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle(); ++ ++ org.bukkit.entity.Player craftPlayer = (org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity(); // Purpur - Signs allow color codes + if (player.isTextFilteringEnabled()) { +- text = text.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style)); // Paper - filter sign text to chat only ++ text = text.setMessage(i, translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty()), style)); // Paper - filter sign text to chat only // Purpur - Signs allow color codes } else { -- signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only -+ signtext = signtext.setMessage(i, translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.raw()), chatmodifier), translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur + text = text.setMessage( +- i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style) // Paper - filter sign text to chat only ++ i, translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.raw()), style), translateColors(craftPlayer, net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty()), style) // Paper - filter sign text to chat only // Purpur - Signs allow color codes + ); } } - -@@ -348,6 +363,28 @@ public class SignBlockEntity extends BlockEntity { - return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events +@@ -311,6 +327,28 @@ public class SignBlockEntity extends BlockEntity { + return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel)level, 2, string, component, level.getServer(), player); // Paper - Fix commands from signs not firing command events } -+ // Purpur start ++ // Purpur start - Signs allow color codes + public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) { + final CompoundTag nbt = new CompoundTag(); + this.saveAdditional(nbt, this.getLevel().registryAccess()); @@ -16415,21 +16522,21 @@ index 8ac19e1e052e73ff3fd09089bb8e3fd687390ee4..6da1eec98c08e4909ecbd48fe90b3fd6 + nbt.putString("PurpurEditor", "true"); + return ClientboundBlockEntityDataPacket.create(this, (blockEntity, registryAccess) -> nbt); + } -+ // Purpur end ++ // Purpur end - Signs allow color codes + @Override public ClientboundBlockEntityDataPacket getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java -index 205e223c356634bd6bc6bd58c6f0b7fda61a6f5f..bea05cb928d540a2f19b51bb7352d032b2dd69cd 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java +diff --git a/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/net/minecraft/world/level/block/piston/PistonStructureResolver.java +index ad143a92569f5b420ccaa2089758b2fb3b4ab7c5..5a3660e02bc805e9a35a81b8a61f07b3f20b5ba9 100644 +--- a/net/minecraft/world/level/block/piston/PistonStructureResolver.java ++++ b/net/minecraft/world/level/block/piston/PistonStructureResolver.java @@ -81,7 +81,7 @@ public class PistonStructureResolver { return true; } else { int i = 1; - if (i + this.toPush.size() > 12) { -+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur ++ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit return false; } else { while (isSticky(blockState)) { @@ -16438,7 +16545,7 @@ index 205e223c356634bd6bc6bd58c6f0b7fda61a6f5f..bea05cb928d540a2f19b51bb7352d032 } - if (++i + this.toPush.size() > 12) { -+ if (++i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur ++ if (++i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit return false; } } @@ -16447,1807 +16554,219 @@ index 205e223c356634bd6bc6bd58c6f0b7fda61a6f5f..bea05cb928d540a2f19b51bb7352d032 } - if (this.toPush.size() >= 12) { -+ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur ++ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit return false; } -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 5b6fbfd1a7a2d87fb2b87d5d1e674206cdf9b280..dcf2dcece3e995ce4646b931329246be19a4e1c2 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 -@@ -91,7 +91,7 @@ public abstract class BlockBehaviour implements FeatureElement { - - protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP}; - public final boolean hasCollision; -- protected final float explosionResistance; -+ public float explosionResistance; // Purpur - protected final -> public - protected final boolean isRandomlyTicking; - protected final SoundType soundType; - protected final float friction; -@@ -99,7 +99,7 @@ public abstract class BlockBehaviour implements FeatureElement { - protected final float jumpFactor; - protected final boolean dynamicShape; - protected final FeatureFlagSet requiredFeatures; -- protected final BlockBehaviour.Properties properties; -+ public final BlockBehaviour.Properties properties; // Purpur - protected -> public - protected final Optional> drops; - protected final String descriptionId; - -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 356d010506fd21f3c752e4aa86c46c1106fdde3b..86e16dd6b905af31795fda8002f2e1f857ddcb9f 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 +diff --git a/net/minecraft/world/level/chunk/storage/EntityStorage.java b/net/minecraft/world/level/chunk/storage/EntityStorage.java +index 2856206eafddfcbcc1b65408deda40357f43a6f8..dee0c4ac773c47fb87258b6126854d802ab26db8 100644 +--- a/net/minecraft/world/level/chunk/storage/EntityStorage.java ++++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java @@ -106,6 +106,7 @@ public class EntityStorage implements EntityPersistentStorage { } // Paper end - Entity load/save limit per chunk - CompoundTag compoundTagx = new CompoundTag(); -+ if (!entity.canSaveToDisk()) return; // Purpur - if (entity.save(compoundTagx)) { - listTag.add(compoundTagx); + CompoundTag compoundTag1 = new CompoundTag(); ++ if (!entity.canSaveToDisk()) return; // Purpur - Add canSaveToDisk to Entity + if (entity.save(compoundTag1)) { + listTag.add(compoundTag1); } -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 997f80d332b95bb011cbbc27c065b2811a2dddc7..d9a1ad945504d26a2c0717e40782f2859a4b2e9b 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -48,7 +48,7 @@ public class PhantomSpawner implements CustomSpawner { - int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; - this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; +diff --git a/net/minecraft/world/level/levelgen/PhantomSpawner.java b/net/minecraft/world/level/levelgen/PhantomSpawner.java +index c792483860d31ce663e7de34e9f79ff46de75b8c..b9fdd5518ee1fe743485abb3557a21101846041f 100644 +--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -43,7 +43,7 @@ public class PhantomSpawner implements CustomSpawner { + int spawnAttemptMaxSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; + this.nextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; // Paper end - Ability to control player's insomnia and phantoms -- if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { -+ if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur +- if (level.getSkyDarken() < 5 && level.dimensionType().hasSkyLight()) { ++ if (level.getSkyDarken() < level.purpurConfig.phantomSpawnMinSkyDarkness && level.dimensionType().hasSkyLight()) { // Purpur - Add phantom spawning options return 0; } else { int i = 0; -@@ -60,10 +60,10 @@ public class PhantomSpawner implements CustomSpawner { - if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls - BlockPos blockposition = entityplayer.blockPosition(); +@@ -51,9 +51,9 @@ public class PhantomSpawner implements CustomSpawner { + for (ServerPlayer serverPlayer : level.players()) { + if (!serverPlayer.isSpectator() && (!level.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !serverPlayer.isCreative())) { // Paper - Add phantom creative and insomniac controls + BlockPos blockPos = serverPlayer.blockPosition(); +- if (!level.dimensionType().hasSkyLight() || blockPos.getY() >= level.getSeaLevel() && level.canSeeSky(blockPos)) { ++ if (!level.dimensionType().hasSkyLight() || (!level.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockPos.getY() >= level.getSeaLevel()) && (!level.purpurConfig.phantomSpawnOnlyWithVisibleSky || level.canSeeSky(blockPos))) { // Purpur - Add phantom spawning options + DifficultyInstance currentDifficultyAt = level.getCurrentDifficultyAt(blockPos); +- if (currentDifficultyAt.isHarderThan(randomSource.nextFloat() * 3.0F)) { ++ if (currentDifficultyAt.isHarderThan(randomSource.nextFloat() * (float) level.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur - Add phantom spawning options + ServerStatsCounter stats = serverPlayer.getStats(); + int i1 = Mth.clamp(stats.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); + int i2 = 24000; +@@ -73,7 +73,7 @@ public class PhantomSpawner implements CustomSpawner { + FluidState fluidState = level.getFluidState(blockPos1); + if (NaturalSpawner.isValidEmptySpawnBlock(level, blockPos1, blockState, fluidState, EntityType.PHANTOM)) { + SpawnGroupData spawnGroupData = null; +- int i3 = 1 + randomSource.nextInt(currentDifficultyAt.getDifficulty().getId() + 1); ++ int i3 = level.purpurConfig.phantomSpawnMinPerAttempt + level.random.nextInt((level.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? currentDifficultyAt.getDifficulty().getId() : level.purpurConfig.phantomSpawnMaxPerAttempt - level.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur - Add phantom spawning options -- if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { -+ if (!world.dimensionType().hasSkyLight() || (!world.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockposition.getY() >= world.getSeaLevel()) && (!world.purpurConfig.phantomSpawnOnlyWithVisibleSky || world.canSeeSky(blockposition))) { // Purpur - DifficultyInstance difficultydamagescaler = world.getCurrentDifficultyAt(blockposition); - -- if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * 3.0F)) { -+ if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur - ServerStatsCounter serverstatisticmanager = entityplayer.getStats(); - int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); - boolean flag2 = true; -@@ -83,7 +83,7 @@ public class PhantomSpawner implements CustomSpawner { - - if (NaturalSpawner.isValidEmptySpawnBlock(world, blockposition1, iblockdata, fluid, EntityType.PHANTOM)) { - SpawnGroupData groupdataentity = null; -- int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); -+ int k = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficultydamagescaler.getDifficulty().getId() : world.purpurConfig.phantomSpawnMaxPerAttempt - world.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur - - for (int l = 0; l < k; ++l) { + for (int i4 = 0; i4 < i3; i4++) { // Paper start - PhantomPreSpawnEvent -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 221978c64cf6171db078c6cbfc850f6aeae73884..408e7c61d87a0e6d8502bf1f5ca76fd728c5d10c 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -266,7 +266,7 @@ public abstract class FlowingFluid extends Fluid { +diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java +index c47a33a989d7ffea4f0bbae39fd64869369e9bda..c535cd03c577d76c3b19da5a8426d0cbee3069f0 100644 +--- a/net/minecraft/world/level/material/FlowingFluid.java ++++ b/net/minecraft/world/level/material/FlowingFluid.java +@@ -232,7 +232,7 @@ public abstract class FlowingFluid extends Fluid { } } -- if (j >= 2 && this.canConvertToSource(world)) { -+ if (j >= getRequiredSources(world) && this.canConvertToSource(world)) { // Purpur - BlockState iblockdata2 = world.getBlockState(blockposition_mutableblockposition.setWithOffset(pos, Direction.DOWN)); - FluidState fluid1 = iblockdata2.getFluidState(); +- if (i1 >= 2 && this.canConvertToSource(level)) { ++ if (i1 >= this.getRequiredSources(level) && this.canConvertToSource(level)) { // Purpur - Implement infinite liquids + BlockState blockState1 = level.getBlockState(mutableBlockPos.setWithOffset(pos, Direction.DOWN)); + FluidState fluidState1 = blockState1.getFluidState(); + if (blockState1.isSolid() || this.isSourceBlockOfThisType(fluidState1)) { +@@ -320,6 +320,12 @@ public abstract class FlowingFluid extends Fluid { -@@ -356,6 +356,12 @@ public abstract class FlowingFluid extends Fluid { + protected abstract boolean canConvertToSource(ServerLevel level); - protected abstract boolean canConvertToSource(ServerLevel world); - -+ // Purpur start ++ // Purpur start - Implement infinite liquids + protected int getRequiredSources(Level level) { + return 2; + } -+ // Purpur end ++ // Purpur end - Implement infinite liquids + - protected void spreadTo(LevelAccessor world, BlockPos pos, BlockState state, Direction direction, FluidState fluidState) { - Block block = state.getBlock(); - -diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -index 884db3e64cb22ed765beec8f11ea309fcf810207..6e643c1a7f7e71cfd20603facaf224985ee81716 100644 ---- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -@@ -181,7 +181,7 @@ public abstract class LavaFluid extends FlowingFluid { + protected void spreadTo(LevelAccessor level, BlockPos pos, BlockState blockState, Direction direction, FluidState fluidState) { + if (blockState.getBlock() instanceof LiquidBlockContainer liquidBlockContainer) { + liquidBlockContainer.placeLiquid(level, pos, blockState, fluidState); +diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java +index 6f135caffb7638c6156f00341aeac12b50cea99d..85629a43f5469a89dd6078d879f475e8212438ec 100644 +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -177,7 +177,7 @@ public abstract class LavaFluid extends FlowingFluid { @Override - public int getTickDelay(LevelReader world) { -- return world.dimensionType().ultraWarm() ? 10 : 30; -+ return world.dimensionType().ultraWarm() ? world.getWorldBorder().world.purpurConfig.lavaSpeedNether : world.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur + public int getTickDelay(LevelReader level) { +- return level.dimensionType().ultraWarm() ? 10 : 30; ++ return level.dimensionType().ultraWarm() ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable } @Override @@ -199,6 +199,13 @@ public abstract class LavaFluid extends FlowingFluid { - world.levelEvent(1501, pos, 0); + level.levelEvent(1501, pos, 0); } -+ // Purpur start ++ // Purpur start - Implement infinite liquids + @Override + protected int getRequiredSources(Level level) { + return level.purpurConfig.lavaInfiniteRequiredSources; + } -+ // Purpur end ++ // Purpur end - Implement infinite liquids + @Override - protected boolean canConvertToSource(ServerLevel world) { - return world.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION); -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 552925ba47c7475e2e1ec2ded0966f28ed3e50a5..1e741f36b79585f33abe413beafe00cf5205d54f 100644 ---- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -@@ -81,6 +81,13 @@ public abstract class WaterFluid extends FlowingFluid { - return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); + protected boolean canConvertToSource(ServerLevel level) { + return level.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION); +diff --git a/net/minecraft/world/level/material/WaterFluid.java b/net/minecraft/world/level/material/WaterFluid.java +index 56781b47aeddf0c84d64ddf8b1aad7b26730b68c..2e4fed7c27910b6c886f710f33b0841c2a175837 100644 +--- a/net/minecraft/world/level/material/WaterFluid.java ++++ b/net/minecraft/world/level/material/WaterFluid.java +@@ -74,6 +74,12 @@ public abstract class WaterFluid extends FlowingFluid { + protected boolean canConvertToSource(ServerLevel level) { + return level.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); } - -+ // Purpur start ++ // Purpur start - Implement infinite liquids + @Override + protected int getRequiredSources(Level level) { + return level.purpurConfig.waterInfiniteRequiredSources; + } -+ // Purpur end -+ ++ // Purpur end - Implement infinite liquids // Paper start - Add BlockBreakBlockEvent - @Override - protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { -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 c84fd369d92932903c76bb2012602617d3e2d213..224896124706764412033c8726c822e116f2c0f1 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java + @Override + protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) { +diff --git a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +index 9e6b2bbc1f83d32d0332f036be4f1a0e18b826bf..db6baaa698fe93aba3fbd595158b568badd6cb8a 100644 +--- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java @@ -240,7 +240,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { if ((node == null || node.costMalus < 0.0F) - && maxYStep > 0 - && (pathType != PathType.FENCE || this.canWalkOverFences()) -- && pathType != PathType.UNPASSABLE_RAIL -+ && (this.mob.level().purpurConfig.mobsIgnoreRails || pathType != PathType.UNPASSABLE_RAIL) // Purpur - && pathType != PathType.TRAPDOOR - && pathType != PathType.POWDER_SNOW) { - node = this.tryJumpOn(x, y, z, maxYStep, prevFeetY, direction, nodeType, mutableBlockPos); -@@ -491,7 +491,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { + && verticalDeltaLimit > 0 + && (cachedPathType != PathType.FENCE || this.canWalkOverFences()) +- && cachedPathType != PathType.UNPASSABLE_RAIL ++ && (this.mob.level().purpurConfig.mobsIgnoreRails || cachedPathType != PathType.UNPASSABLE_RAIL) // Purpur - Config to allow mobs to pathfind over rails + && cachedPathType != PathType.TRAPDOOR + && cachedPathType != PathType.POWDER_SNOW) { + node = this.tryJumpOn(x, y, z, verticalDeltaLimit, nodeFloorLevel, direction, pathType, mutableBlockPos); +@@ -493,7 +493,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { return PathType.TRAPDOOR; } else if (blockState.is(Blocks.POWDER_SNOW)) { return PathType.POWDER_SNOW; - } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) { -+ } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur ++ } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur - Stonecutter damage return PathType.DAMAGE_OTHER; } else if (blockState.is(Blocks.HONEY_BLOCK)) { return PathType.STICKY_HONEY; -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 90056822cd17f3d33d14b3f94b34750ee522a0a9..acdff7b4a00d563739fd301c3633a266875296fa 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -@@ -35,7 +35,7 @@ public class PortalShape { +diff --git a/net/minecraft/world/level/portal/PortalShape.java b/net/minecraft/world/level/portal/PortalShape.java +index 710f4570bb45a25f20cf914c640539cfa9c9d31b..19ee66cc966cbd124d8c59bc55586237c2ca5deb 100644 +--- a/net/minecraft/world/level/portal/PortalShape.java ++++ b/net/minecraft/world/level/portal/PortalShape.java +@@ -28,7 +28,7 @@ public class PortalShape { + public static final int MAX_WIDTH = 21; private static final int MIN_HEIGHT = 3; public static final int MAX_HEIGHT = 21; - private static final BlockBehaviour.StatePredicate FRAME = (iblockdata, iblockaccess, blockposition) -> { -- return iblockdata.is(Blocks.OBSIDIAN); -+ return iblockdata.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && iblockdata.is(Blocks.CRYING_OBSIDIAN)); // Purpur - }; +- private static final BlockBehaviour.StatePredicate FRAME = (state, level, pos) -> state.is(Blocks.OBSIDIAN); ++ private static final BlockBehaviour.StatePredicate FRAME = (state, level, pos) -> state.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && state.is(Blocks.CRYING_OBSIDIAN)); // Purpur - Crying obsidian valid for portal frames private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F; - private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0D; -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 b15b0c8057e61c6aef05c0865e2c3e06adcf938b..aabc0b5a3e50aad8c4f902fa41e6bed319599ff3 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 -@@ -81,6 +81,7 @@ public class MapItemSavedData extends SavedData { + private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0; + private final Direction.Axis axis; +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index f5a131e870a4f1ad06ebfb1f360720cf19656fb5..3a8c5a252ea5986b2366fa774ea1754494af1d59 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -68,6 +68,7 @@ public class MapItemSavedData extends SavedData { + public final Map decorations = Maps.newLinkedHashMap(); private final Map frameMarkers = Maps.newHashMap(); private int trackedDecorationCount; - private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper -+ public boolean isExplorerMap; // Purpur ++ public boolean isExplorerMap; // Purpur - Explorer Map API // CraftBukkit start - public final CraftMapView mapView; -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java -index 5f27e1ce23f2ed68e4c8af1986fafce940dbf826..d8cf49cbd82ed12d23fa10a81a88cc4bcf1c0f10 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java + public final org.bukkit.craftbukkit.map.CraftMapView mapView; +diff --git a/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java b/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java +index c3d3dd9d3be4b824ccfc114243812ef31b46f4dc..b7636b7dcd2cbec3f08029733bb03e4bd0282b9e 100644 +--- a/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java ++++ b/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java @@ -66,6 +66,11 @@ public class EnchantedCountIncreaseFunction extends LootItemConditionalFunction Entity entity = context.getOptionalParameter(LootContextParams.ATTACKING_ENTITY); if (entity instanceof LivingEntity livingEntity) { - int i = EnchantmentHelper.getEnchantmentLevel(this.enchantment, livingEntity); + int enchantmentLevel = EnchantmentHelper.getEnchantmentLevel(this.enchantment, livingEntity); + // Purpur start - Add an option to fix MC-3304 projectile looting + if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && context.getOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY) instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { -+ i = arrow.actualEnchantments.getLevel(this.enchantment); ++ enchantmentLevel = arrow.actualEnchantments.getLevel(this.enchantment); + } + // Purpur end - Add an option to fix MC-3304 projectile looting - if (i == 0) { + if (enchantmentLevel == 0) { return stack; } -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index 6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130..e74866e5195a5eeae7666ad7be750edac5947094 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -551,4 +551,10 @@ public class AABB { - public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { - return new AABB(center.x - dx / 2.0, center.y - dy / 2.0, center.z - dz / 2.0, center.x + dx / 2.0, center.y + dy / 2.0, center.z + dz / 2.0); +diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java +index 85148858db1fd5e9da8bbdde4b0d84110d80e373..c9c6e4e460ad8435f12761704bb9b0284d6aa708 100644 +--- a/net/minecraft/world/phys/AABB.java ++++ b/net/minecraft/world/phys/AABB.java +@@ -442,4 +442,10 @@ public class AABB { + center.x - xSize / 2.0, center.y - ySize / 2.0, center.z - zSize / 2.0, center.x + xSize / 2.0, center.y + ySize / 2.0, center.z + zSize / 2.0 + ); } + -+ // Purpur - tuinity added method ++ // Purpur start - Stop squids floating on top of water - tuinity added method + public final AABB offsetY(double dy) { + return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ); + } -+ // Purpur ++ // Purpur end - Stop squids floating on top of water } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 94ca0407303c4493ab4928b12ec6ecc75aaca549..a138e1b6b66d99f2035de054137a607aa6b7f0b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -363,14 +363,26 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - - @Override - public Location getLocation() { -+ // Purpur start -+ if (this.isOnline()) { -+ return this.getPlayer().getLocation(); -+ } -+ // Purpur end -+ - CompoundTag data = this.getData(); - if (data == null) { - return null; - } - -- if (data.contains("Pos") && data.contains("Rotation")) { -- ListTag position = (ListTag) data.get("Pos"); -- ListTag rotation = (ListTag) data.get("Rotation"); -+ // Purpur start - OfflinePlayer API -+ //if (data.contains("Pos") && data.contains("Rotation")) { -+ ListTag position = data.getList("Pos", net.minecraft.nbt.Tag.TAG_DOUBLE); -+ ListTag rotation = data.getList("Rotation", net.minecraft.nbt.Tag.TAG_FLOAT); -+ -+ if (position.isEmpty() && rotation.isEmpty()) { -+ return null; -+ } -+ // Purpur end - OfflinePlayer API - - UUID uuid = new UUID(data.getLong("WorldUUIDMost"), data.getLong("WorldUUIDLeast")); - -@@ -381,9 +393,9 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - rotation.getFloat(0), - rotation.getFloat(1) - ); -- } -+ //} // Purpur - OfflinePlayer API - -- return null; -+ //return null; // Purpur - OfflinePlayer API - } - - @Override -@@ -626,4 +638,191 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - manager.save(); - } - } -+ -+ // Purpur start - OfflinePlayer API -+ @Override -+ public boolean getAllowFlight() { -+ if (this.isOnline()) { -+ return this.getPlayer().getAllowFlight(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return false; -+ if (!data.contains("abilities")) return false; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getByte("mayfly") == (byte) 1; -+ } -+ } -+ -+ @Override -+ public void setAllowFlight(boolean flight) { -+ if (this.isOnline()) { -+ this.getPlayer().setAllowFlight(flight); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putByte("mayfly", (byte) (flight ? 1 : 0)); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public boolean isFlying() { -+ if (this.isOnline()) { -+ return this.isFlying(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return false; -+ if (!data.contains("abilities")) return false; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getByte("flying") == (byte) 1; -+ } -+ } -+ -+ @Override -+ public void setFlying(boolean value) { -+ if (this.isOnline()) { -+ this.getPlayer().setFlying(value); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putByte("mayfly", (byte) (value ? 1 : 0)); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public void setFlySpeed(float value) throws IllegalArgumentException { -+ if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1"); -+ if (this.isOnline()) { -+ this.getPlayer().setFlySpeed(value); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putFloat("flySpeed", value); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public float getFlySpeed() { -+ if (this.isOnline()) { -+ return this.getPlayer().getFlySpeed(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return 0; -+ if (!data.contains("abilities")) return 0; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getFloat("flySpeed"); -+ } -+ } -+ -+ @Override -+ public void setWalkSpeed(float value) throws IllegalArgumentException { -+ if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1"); -+ if (this.isOnline()) { -+ this.getPlayer().setWalkSpeed(value); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putFloat("walkSpeed", value); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public float getWalkSpeed() { -+ if (this.isOnline()) { -+ return this.getPlayer().getWalkSpeed(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return 0; -+ if (!data.contains("abilities")) return 0; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getFloat("walkSpeed"); -+ } -+ } -+ -+ @Override -+ public boolean teleportOffline(Location destination) { -+ if (this.isOnline()) { -+ return this.getPlayer().teleport(destination); -+ } else { -+ return setLocation(destination); -+ } -+ } -+ -+ @Override -+ public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){ -+ if (this.isOnline()) { -+ return this.getPlayer().teleport(destination, cause); -+ } else { -+ return setLocation(destination); -+ } -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination) { -+ if (this.isOnline()) { -+ return this.getPlayer().teleportAsync(destination); -+ } else { -+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); -+ } -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { -+ if (this.isOnline()) { -+ return this.getPlayer().teleportAsync(destination, cause); -+ } else { -+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); -+ } -+ } -+ -+ private boolean setLocation(Location location) { -+ CompoundTag data = this.getData(); -+ if (data == null) return false; -+ data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits()); -+ data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits()); -+ net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag(); -+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX())); -+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY())); -+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ())); -+ data.put("Pos", position); -+ net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag(); -+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw())); -+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch())); -+ data.put("Rotation", rotation); -+ save(data); -+ return true; -+ } -+ -+ /** -+ * Safely replaces player's .dat file with provided CompoundTag -+ * @param compoundTag -+ */ -+ private void save(CompoundTag compoundTag) { -+ File playerDir = server.console.playerDataStorage.getPlayerDir(); -+ try { -+ File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); -+ net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.toPath()); -+ File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); -+ File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); -+ net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); -+ } catch (java.io.IOException e) { -+ e.printStackTrace(); -+ } -+ } -+ // Purpur end - OfflinePlayer API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index c11f638e120ae4989d110f55c4569dd67b6f3260..ef8041877456316fc32a382e28ee81161d962353 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -427,6 +427,20 @@ public final class CraftServer implements Server { - this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); - this.pluginManager.paperPluginManager = this.paperPluginManager; - // Paper end -+ // Purpur start -+ org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() { -+ private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance(); -+ @Override -+ public boolean has(@org.jetbrains.annotations.NotNull String key) { -+ return language.has(key); -+ } -+ -+ @Override -+ public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) { -+ return language.getOrDefault(key); -+ } -+ }); -+ // Purpur end - - CraftRegistry.setMinecraftRegistry(console.registryAccess()); - -@@ -1088,6 +1102,7 @@ public final class CraftServer implements Server { - org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot - this.console.paperConfigurations.reloadConfigs(this.console); - this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration -+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - for (ServerLevel world : this.console.getAllLevels()) { - // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty - world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) -@@ -1103,6 +1118,7 @@ public final class CraftServer implements Server { - } - } - world.spigotConfig.init(); // Spigot -+ world.purpurConfig.init(); // Purpur - } - - Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -1120,6 +1136,7 @@ public final class CraftServer implements Server { - org.spigotmc.SpigotConfig.registerCommands(); // Spigot - io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper - this.spark.registerCommandBeforePlugins(this); // Paper - spark -+ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); - this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); - -@@ -1632,6 +1649,58 @@ public final class CraftServer implements Server { - return true; - } - -+ // Purpur Start -+ @Override -+ public void addFuel(org.bukkit.Material material, int burnTime) { -+ Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0"); -+ -+ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); -+ MinecraftServer.getServer().fuelValues().values.put(itemStack.getItem(), burnTime); -+ } -+ -+ @Override -+ public void removeFuel(org.bukkit.Material material) { -+ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); -+ MinecraftServer.getServer().fuelValues().values.keySet().removeIf(itemStack::is); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration) { -+ sendBlockHighlight(location, duration, "", 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, int argb) { -+ sendBlockHighlight(location, duration, "", argb); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text) { -+ sendBlockHighlight(location, duration, text, 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, int argb) { -+ this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb)); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { -+ sendBlockHighlight(location, duration, "", color, transparency); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { -+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); -+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); -+ } -+ -+ @Override -+ public void clearBlockHighlights() { -+ this.worlds.forEach((name, world) -> clearBlockHighlights()); -+ } -+ // Purpur End -+ - @Override - public List getRecipesFor(ItemStack result) { - Preconditions.checkArgument(result != null, "ItemStack cannot be null"); -@@ -3045,6 +3114,18 @@ public final class CraftServer implements Server { - } - // Gale end - Gale configuration - API - -+ // Purpur start -+ @Override -+ public YamlConfiguration getPurpurConfig() { -+ return org.purpurmc.purpur.PurpurConfig.config; -+ } -+ -+ @Override -+ public java.util.Properties getServerProperties() { -+ return getProperties().properties; -+ } -+ // Purpur end -+ - @Override - public void restart() { - org.spigotmc.RestartCommand.restart(); -@@ -3338,4 +3419,16 @@ public final class CraftServer implements Server { - return MinecraftServer.lastTickOversleepTime; - } - // Gale end - YAPFA - last tick time - API -+ -+ // Purpur start -+ @Override -+ public String getServerName() { -+ return this.getProperties().serverName; -+ } -+ -+ @Override -+ public boolean isLagging() { -+ return getServer().lagging; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index d39a0785f42c72d7b32af5c1c8067b2857f22a48..768ad742ff654a1e80c0d3746bc0ba503f350cd8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2384,6 +2384,48 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight()); - } - -+ // Purpur start -+ public float getLocalDifficultyAt(Location location) { -+ return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty(); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration) { -+ sendBlockHighlight(location, duration, "", 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, int argb) { -+ sendBlockHighlight(location, duration, "", argb); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text) { -+ sendBlockHighlight(location, duration, text, 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, int argb) { -+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), io.papermc.paper.util.MCUtil.toBlockPosition(location), text, argb, duration); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { -+ sendBlockHighlight(location, duration, "", color, transparency); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { -+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); -+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); -+ } -+ -+ @Override -+ public void clearBlockHighlights() { -+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle()); -+ } -+ // Purpur end -+ - @Override - public Collection getStructures(int x, int z) { - return this.getStructures(x, z, struct -> true); -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 50a2b6e73ba4d8ad65582b2544fa409fd44f36d5..a7cc9af6979860d54c677a7d8d6c1589e97d4841 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -176,6 +176,14 @@ public class Main { - .describedAs("Jar file"); - // Paper end - -+ // Purpur Start -+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("purpur.yml")) -+ .describedAs("Yml file"); -+ // Purpur end -+ - // Paper start - acceptsAll(asList("server-name"), "Name of the server") - .withRequiredArg() -@@ -259,7 +267,7 @@ public class Main { - System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper - } - -- if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { -+ if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur - Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper - - Calendar deadline = Calendar.getInstance(); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -index 1a2a05160ba51d9c75f1ae6ae61d944d81428722..3beb26ad2ef0fded49a8da8c5dec64f9508c1995 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -@@ -16,8 +16,15 @@ import org.bukkit.entity.Bee; - - public class CraftBeehive extends CraftBlockEntityState implements Beehive { - -+ private final List> storage = new ArrayList<>(); // Purpur -+ - public CraftBeehive(World world, BeehiveBlockEntity tileEntity) { - super(world, tileEntity); -+ // Purpur start - load bees to be able to modify them individually -+ for(BeehiveBlockEntity.BeeData data : tileEntity.getStored()) { -+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this)); -+ } -+ // Purpur end - } - - protected CraftBeehive(CraftBeehive state, Location location) { -@@ -75,15 +82,54 @@ public class CraftBeehive extends CraftBlockEntityState impl - bees.add((Bee) bee.getBukkitEntity()); - } - } -- -+ storage.clear(); // Purpur - return bees; - } - -+ // Purpur start -+ @Override -+ public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity entity) { -+ ensureNoWorldGeneration(); -+ -+ if(!getEntities().contains(entity)) { -+ return null; -+ } -+ -+ if(isPlaced()) { -+ BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getTileEntityFromWorld()); -+ BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle(); -+ -+ List list = beehive.releaseBee(getHandle(), data, BeeReleaseStatus.BEE_RELEASED, true); -+ -+ if (list.size() == 1) { -+ storage.remove(entity); -+ -+ return (Bee) list.get(0).getBukkitEntity(); -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public List> getEntities() { -+ return new ArrayList<>(storage); -+ } -+ // Purpur end -+ - @Override - public void addEntity(Bee entity) { - Preconditions.checkArgument(entity != null, "Entity must not be null"); - -- this.getSnapshot().addOccupant(((CraftBee) entity).getHandle()); -+ int length = this.getSnapshot().getStored().size(); // Purpur -+ getSnapshot().addOccupant(((CraftBee) entity).getHandle()); -+ -+ // Purpur start - check if new bee was added, and if yes, add to stored bees -+ List storedBeeData = this.getSnapshot().getStored(); -+ if(length < storedBeeData.size()) { -+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(storedBeeData.getLast(), this)); -+ } -+ // Purpur end - } - - @Override -@@ -100,6 +146,7 @@ public class CraftBeehive extends CraftBlockEntityState impl - @Override - public void clearEntities() { - getSnapshot().clearBees(); -+ storage.clear(); // Purpur - } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -index c1759aeb3e6ad0e4eb66cba3da1b120dd1dce812..1a91bc2e422db0eba65694ac046f1b362c6b0cd6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -@@ -73,7 +73,7 @@ public class CraftConduit extends CraftBlockEntityState impl - public int getRange() { - this.ensureNoWorldGeneration(); - ConduitBlockEntity conduit = (ConduitBlockEntity) this.getTileEntityFromWorld(); -- return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks) : 0; -+ return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks, this.world.getHandle()) : 0; // Purpur - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -index 4e56018b64d11f76c8da43fd8f85c6de72204e36..aa8212432825db65cf485cd93f734ccd9eefcb5a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -@@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co - - @Override - public void sendMessage(String message) { -- this.sendRawMessage(message); -+ // Purpur start -+ String[] parts = message.split("\n"); -+ for (String part : parts) { -+ this.sendRawMessage(part); -+ } -+ // Purpur end - } - - @Override -@@ -91,7 +96,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co - // Paper start - @Override - public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { -- this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); -+ this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -index d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..985e9ec21c60a1f47973bd5fc53b96a6f9b7d04a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -@@ -21,12 +21,12 @@ public class CraftEndermite extends CraftMonster implements Endermite { - - @Override - public boolean isPlayerSpawned() { -- return false; -+ return getHandle().isPlayerSpawned(); // Purpur - } - - @Override - public void setPlayerSpawned(boolean playerSpawned) { -- // Nop -+ getHandle().setPlayerSpawned(playerSpawned); // Purpur - } - // Paper start - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index ddabaed899c755925ad8618b78c33dacaf2126ac..51aee9a468f4ebfa9672fd9ce84883cf080859e3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -87,6 +87,23 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); - } - -+ // Purpur start - API for any mob to burn daylight -+ @Override -+ public boolean isImmuneToFire() { -+ return getHandle().fireImmune(); -+ } -+ -+ @Override -+ public void setImmuneToFire(Boolean fireImmune) { -+ getHandle().immuneToFire = fireImmune; -+ } -+ -+ @Override -+ public boolean isInDaylight() { -+ return getHandle().isSunBurnTick(); -+ } -+ // Purpur end - API for any mob to burn daylight -+ - public static CraftEntity getEntity(CraftServer server, T entity) { - Preconditions.checkArgument(entity != null, "Unknown entity"); - -@@ -246,6 +263,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); - // Don't allow teleporting between worlds while keeping passengers - if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - return false; - } - -@@ -1306,4 +1324,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - } - // Paper end - broadcast hurt animation -+ -+ // Purpur start -+ @Override -+ public org.bukkit.entity.Player getRider() { -+ net.minecraft.world.entity.player.Player rider = getHandle().getRider(); -+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null; -+ } -+ -+ @Override -+ public boolean hasRider() { -+ return getHandle().getRider() != null; -+ } -+ -+ @Override -+ public boolean isRidable() { -+ return getHandle().isRidable(); -+ } -+ -+ @Override -+ public boolean isRidableInWater() { -+ return !getHandle().dismountsUnderwater(); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index e345cdbfab44a0f5da80d738798dbb4424b7ab5c..856f12eb276c214f2f57a58a89a4da9eea34db2d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -273,6 +273,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - @Override - public void recalculatePermissions() { - this.perm.recalculatePermissions(); -+ getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -index 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..966587c2788b5c93be83259ddc962a89cde7cbaa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -@@ -27,4 +27,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem { - public void setPlayerCreated(boolean playerCreated) { - this.getHandle().setPlayerCreated(playerCreated); - } -+ -+ // Purpur start -+ @Override -+ @org.jetbrains.annotations.Nullable -+ public java.util.UUID getSummoner() { -+ return getHandle().getSummoner(); -+ } -+ -+ @Override -+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { -+ getHandle().setSummoner(summoner); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index 30d62ee4d5cd2ddacb8783b5bbbf475d592b3e02..5c1cda88080850314dac196dbe71ff12e48a8aca 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -151,4 +151,51 @@ public class CraftItem extends CraftEntity implements Item { - public String toString() { - return "CraftItem"; - } -+ -+ // Purpur start -+ @Override -+ public void setImmuneToCactus(boolean immuneToCactus) { -+ this.getHandle().immuneToCactus = immuneToCactus; -+ } -+ -+ @Override -+ public boolean isImmuneToCactus() { -+ return this.getHandle().immuneToCactus; -+ } -+ -+ @Override -+ public void setImmuneToExplosion(boolean immuneToExplosion) { -+ this.getHandle().immuneToExplosion = immuneToExplosion; -+ } -+ -+ @Override -+ public boolean isImmuneToExplosion() { -+ return this.getHandle().immuneToExplosion; -+ } -+ -+ @Override -+ public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) { -+ this.getHandle().immuneToFire = (immuneToFire != null && immuneToFire); -+ } -+ -+ @Override -+ public void setImmuneToFire(boolean immuneToFire) { -+ this.setImmuneToFire((Boolean) immuneToFire); -+ } -+ -+ @Override -+ public boolean isImmuneToFire() { -+ return this.getHandle().immuneToFire; -+ } -+ -+ @Override -+ public void setImmuneToLightning(boolean immuneToLightning) { -+ this.getHandle().immuneToLightning = immuneToLightning; -+ } -+ -+ @Override -+ public boolean isImmuneToLightning() { -+ return this.getHandle().immuneToLightning; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index d0c409f4efad289e3e325f44b500fc72589d89d4..051ffc663317fe5a4fafe0750c89fafdece4d316 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -523,7 +523,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - 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 -+ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : getHandle().level().purpurConfig.mobLastHurtByPlayerTime; // 100 value taken from EntityLiving#damageEntity // Purpur - } - // Paper end - -@@ -1211,4 +1211,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); - } - // Paper end - Expose canUseSlot -+ -+ // Purpur start - API for any mob to burn daylight -+ @Override -+ public boolean shouldBurnInDay() { -+ return this.getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(final boolean shouldBurnInDay) { -+ this.getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } -+ // Purpur end - API for any mob to burn daylight - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -index 351f42842b780d053cd2e5bad9ae299449141b10..4860574e7fad7a9527dda599703c573c5b4b234b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -@@ -90,4 +90,16 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys - return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public boolean shouldJoinCaravan() { -+ return getHandle().shouldJoinCaravan; -+ } -+ -+ @Override -+ public void setShouldJoinCaravan(boolean shouldJoinCaravan) { -+ getHandle().shouldJoinCaravan = shouldJoinCaravan; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index d4e497961578bb693275cdf95915b60b2cc76eb7..63065a22ff359c142bab23fccacfd5ebd86f81a5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -583,10 +583,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setPlayerListName(String name) { -+ // Purpur start -+ setPlayerListName(name, false); -+ } -+ public void setPlayerListName(String name, boolean useMM) { -+ // Purpur end - if (name == null) { - name = this.getName(); - } -- this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name); -+ this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur - if (this.getHandle().connection == null) return; // Paper - Updates are possible before the player has fully joined - for (ServerPlayer player : (List) this.server.getHandle().players) { - if (player.getBukkitEntity().canSee(this)) { -@@ -1435,6 +1440,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper start - Teleport passenger API - // Don't allow teleporting between worlds while keeping passengers - if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur start - return false; - } - -@@ -1456,6 +1462,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur start - return false; - } - -@@ -2769,6 +2776,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.getHandle().getAbilities().walkingSpeed * 2f; - } - -+ // Purpur start - OfflinePlayer API -+ @Override -+ public boolean teleportOffline(@NotNull Location destination) { -+ return this.teleport(destination); -+ } -+ -+ @Override -+ public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) { -+ return this.teleport(destination, cause); -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination) { -+ return this.teleportAsync(destination); -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination, PlayerTeleportEvent.TeleportCause cause) { -+ return this.teleportAsync(destination, cause); -+ } -+ // Purpur end - OfflinePlayer API -+ - private void validateSpeed(float value) { - Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value); - } -@@ -3580,4 +3609,70 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData())); - } - // Paper end - entity effect API -+ -+ // Purpur start -+ @Override -+ public boolean usesPurpurClient() { -+ return getHandle().purpurClient; -+ } -+ -+ @Override -+ public boolean isAfk() { -+ return getHandle().isAfk(); -+ } -+ -+ @Override -+ public void setAfk(boolean setAfk) { -+ getHandle().setAfk(setAfk); -+ } -+ -+ @Override -+ public void resetIdleTimer() { -+ getHandle().resetLastActionTime(); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration) { -+ sendBlockHighlight(location, duration, "", 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, int argb) { -+ sendBlockHighlight(location, duration, "", argb); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text) { -+ sendBlockHighlight(location, duration, text, 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, int argb) { -+ if (this.getHandle().connection == null) return; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload(io.papermc.paper.util.MCUtil.toBlockPosition(location), argb, text, duration))); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { -+ sendBlockHighlight(location, duration, "", color, transparency); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { -+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); -+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); -+ } -+ -+ @Override -+ public void clearBlockHighlights() { -+ if (this.getHandle().connection == null) return; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload())); -+ } -+ -+ @Override -+ public void sendDeathScreen(net.kyori.adventure.text.Component message) { -+ if (this.getHandle().connection == null) return; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -index 4ce2373ff71c3c1b8951646e057587a3ab09e145..4f7f6cf6ca24406570d2d29dc63dc89401119961 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -@@ -28,4 +28,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok - public String toString() { - return "CraftSnowman"; - } -+ -+ // Purpur start -+ @Override -+ @org.jetbrains.annotations.Nullable -+ public java.util.UUID getSummoner() { -+ return getHandle().getSummoner(); -+ } -+ -+ @Override -+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { -+ getHandle().setSummoner(summoner); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 8e895d6f84f7d84b219f2424909dd42e5f08dec4..53dcce0701d713c5dd097340a91b8be4806de4b8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -375,4 +375,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - getHandle().getGossips().gossips.clear(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public boolean isLobotomized() { -+ return getHandle().isLobotomized(); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index 7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e..da6ccb2a38df76770821a1a2203e54e722f541ca 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -99,4 +99,17 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok - this.getHandle().makeInvulnerable(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ @org.jetbrains.annotations.Nullable -+ public java.util.UUID getSummoner() { -+ return getHandle().getSummoner(); -+ } -+ -+ @Override -+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { -+ getHandle().setSummoner(summoner); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -index ecd33b4add46acbe4e4f8879c0601220423d66ca..1506a8c0fa490726eb4a4ae14f3aa194dc81d9ab 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -@@ -146,4 +146,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { - return this.getKey().hashCode(); - } - } -+ -+ // Purpur start -+ @Override -+ public boolean isRabid() { -+ return getHandle().isRabid(); -+ } -+ -+ @Override -+ public void setRabid(boolean isRabid) { -+ getHandle().setRabid(isRabid); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index e37aaf77f94b97b736cc20ef070cefdff0400188..763d06265c7d0000e4c641c3aaba785bb0efb23e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -602,6 +602,15 @@ public class CraftEventFactory { - // Paper end - craftServer.getPluginManager().callEvent(event); - -+ // Purpur start -+ if (who != null) { -+ switch (action) { -+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND); -+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND); -+ } -+ } -+ // Purpur end -+ - return event; - } - -@@ -1131,7 +1140,7 @@ public class CraftEventFactory { - return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.LAVA, bukkitDamageSource, modifiers, modifierFunctions, cancelled); - } else if (source.getDirectBlock() != null) { - DamageCause cause; -- if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) { -+ if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL) || source.isStonecutter()) { // Purpur - cause = DamageCause.CONTACT; - } else if (source.is(DamageTypes.HOT_FLOOR)) { - cause = DamageCause.HOT_FLOOR; -@@ -1191,6 +1200,7 @@ public class CraftEventFactory { - EntityDamageEvent event; - if (damager != null) { - event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); -+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -index 6d3f9d5dab6c9a2860ae31cae24310aa2d62da7c..4f29c579f94efe59a8c78520d75676fc4875e2f0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -@@ -145,8 +145,19 @@ public class CraftContainer extends AbstractContainerMenu { - case PLAYER: - case CHEST: - case ENDER_CHEST: -+ // Purpur start -+ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? net.minecraft.world.inventory.MenuType.GENERIC_9x6 : net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); -+ break; - case BARREL: -- this.delegate = new ChestMenu(net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); -+ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> net.minecraft.world.inventory.MenuType.GENERIC_9x6; -+ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5; -+ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4; -+ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2; -+ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1; -+ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x3; -+ }, windowId, bottom, top, top.getContainerSize() / 9); -+ // Purpur end - break; - case DISPENSER: - case DROPPER: -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -index c6159c70f7a37b9bffe268b91905ce848d1d2927..d02adaaa6fbdc1c0eff44cb4a1f1642f9575a821 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -84,7 +84,7 @@ public class CraftInventory implements Inventory { - - @Override - public void setContents(ItemStack[] items) { -- Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); -+ // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur - - for (int i = 0; i < this.getSize(); i++) { - if (i >= items.length) { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -index 792cb6adf0c7a6335cc5985fce8bed2e0f1149af..5734c5caffda79383ae30df20c3defb51b87f39e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -@@ -19,6 +19,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - private int repairCost; - private int repairCostAmount; - private int maximumRepairCost; -+ // Purpur start - Anvil API -+ private boolean bypassCost; -+ private boolean canDoUnsafeEnchants; -+ // Purpur end - Anvil API - - public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory) { - super(inventory, resultInventory); -@@ -27,6 +31,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - this.repairCost = CraftInventoryAnvil.DEFAULT_REPAIR_COST; - this.repairCostAmount = CraftInventoryAnvil.DEFAULT_REPAIR_COST_AMOUNT; - this.maximumRepairCost = CraftInventoryAnvil.DEFAULT_MAXIMUM_REPAIR_COST; -+ // Purpur start - Anvil API -+ this.bypassCost = false; -+ this.canDoUnsafeEnchants = false; -+ // Purpur end - Anvil API - } - - @Override -@@ -113,4 +121,30 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - consumer.accept(cav); - } - } -+ -+ // Purpur start - Anvil API -+ @Override -+ public boolean canBypassCost() { -+ this.syncWithArbitraryViewValue((cav) -> this.bypassCost = cav.canBypassCost()); -+ return this.bypassCost; -+ } -+ -+ @Override -+ public void setBypassCost(boolean bypassCost) { -+ this.bypassCost = bypassCost; -+ this.syncViews((cav) -> cav.setBypassCost(bypassCost)); -+ } -+ -+ @Override -+ public boolean canDoUnsafeEnchants() { -+ this.syncWithArbitraryViewValue((cav) -> this.canDoUnsafeEnchants = cav.canDoUnsafeEnchants()); -+ return this.canDoUnsafeEnchants; -+ } -+ -+ @Override -+ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { -+ this.canDoUnsafeEnchants = canDoUnsafeEnchants; -+ this.syncViews((cav) -> cav.setDoUnsafeEnchants(canDoUnsafeEnchants)); -+ } -+ // Purpur end - Anvil API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 78975412da0f0c2b802bfce6d30d56b26d8023e2..4ec6a07796023aab2f8f84f131f48108c235c852 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -658,4 +658,285 @@ public final class CraftItemStack extends ItemStack { - } - - // Paper end - data component API -+ -+ // Purpur start -+ @Override -+ public String getDisplayName() { -+ return getItemMeta().getDisplayName(); -+ } -+ -+ @Override -+ public void setDisplayName(String name) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setDisplayName(name); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasDisplayName() { -+ return hasItemMeta() && getItemMeta().hasDisplayName(); -+ } -+ -+ @Override -+ public String getLocalizedName() { -+ return getItemMeta().getLocalizedName(); -+ } -+ -+ @Override -+ public void setLocalizedName(String name) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setLocalizedName(name); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasLocalizedName() { -+ return hasItemMeta() && getItemMeta().hasLocalizedName(); -+ } -+ -+ @Override -+ public boolean hasLore() { -+ return hasItemMeta() && getItemMeta().hasLore(); -+ } -+ -+ @Override -+ public boolean hasEnchant(Enchantment ench) { -+ return hasItemMeta() && getItemMeta().hasEnchant(ench); -+ } -+ -+ @Override -+ public int getEnchantLevel(Enchantment ench) { -+ return getItemMeta().getEnchantLevel(ench); -+ } -+ -+ @Override -+ public Map getEnchants() { -+ return getItemMeta().getEnchants(); -+ } -+ -+ @Override -+ public boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean removeEnchant(Enchantment ench) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeEnchant(ench); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean hasEnchants() { -+ return hasItemMeta() && getItemMeta().hasEnchants(); -+ } -+ -+ @Override -+ public boolean hasConflictingEnchant(Enchantment ench) { -+ return hasItemMeta() && getItemMeta().hasConflictingEnchant(ench); -+ } -+ -+ @Override -+ public void setCustomModelData(Integer data) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setCustomModelData(data); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public int getCustomModelData() { -+ return getItemMeta().getCustomModelData(); -+ } -+ -+ @Override -+ public boolean hasCustomModelData() { -+ return hasItemMeta() && getItemMeta().hasCustomModelData(); -+ } -+ -+ @Override -+ public boolean hasBlockData() { -+ return hasItemMeta() && ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).hasBlockData(); -+ } -+ -+ @Override -+ public org.bukkit.block.data.BlockData getBlockData(Material material) { -+ return ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).getBlockData(material); -+ } -+ -+ @Override -+ public void setBlockData(org.bukkit.block.data.BlockData blockData) { -+ ItemMeta itemMeta = getItemMeta(); -+ ((org.bukkit.inventory.meta.BlockDataMeta) itemMeta).setBlockData(blockData); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public int getRepairCost() { -+ return ((org.bukkit.inventory.meta.Repairable) getItemMeta()).getRepairCost(); -+ } -+ -+ @Override -+ public void setRepairCost(int cost) { -+ ItemMeta itemMeta = getItemMeta(); -+ ((org.bukkit.inventory.meta.Repairable) itemMeta).setRepairCost(cost); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasRepairCost() { -+ return hasItemMeta() && ((org.bukkit.inventory.meta.Repairable) getItemMeta()).hasRepairCost(); -+ } -+ -+ @Override -+ public boolean isUnbreakable() { -+ return hasItemMeta() && getItemMeta().isUnbreakable(); -+ } -+ -+ @Override -+ public void setUnbreakable(boolean unbreakable) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setUnbreakable(unbreakable); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasAttributeModifiers() { -+ return hasItemMeta() && getItemMeta().hasAttributeModifiers(); -+ } -+ -+ @Override -+ public com.google.common.collect.Multimap getAttributeModifiers() { -+ return getItemMeta().getAttributeModifiers(); -+ } -+ -+ @Override -+ public com.google.common.collect.Multimap getAttributeModifiers(org.bukkit.inventory.EquipmentSlot slot) { -+ return getItemMeta().getAttributeModifiers(slot); -+ } -+ -+ @Override -+ public java.util.Collection getAttributeModifiers(org.bukkit.attribute.Attribute attribute) { -+ return getItemMeta().getAttributeModifiers(attribute); -+ } -+ -+ @Override -+ public boolean addAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.addAttributeModifier(attribute, modifier); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public void setAttributeModifiers(com.google.common.collect.Multimap attributeModifiers) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setAttributeModifiers(attributeModifiers); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeAttributeModifier(attribute); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean removeAttributeModifier(org.bukkit.inventory.EquipmentSlot slot) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeAttributeModifier(slot); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeAttributeModifier(attribute, modifier); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean hasDamage() { -+ return hasItemMeta() && ((org.bukkit.inventory.meta.Damageable) getItemMeta()).hasDamage(); -+ } -+ -+ @Override -+ public int getDamage() { -+ return ((org.bukkit.inventory.meta.Damageable) getItemMeta()).getDamage(); -+ } -+ -+ @Override -+ public void setDamage(int damage) { -+ ItemMeta itemMeta = getItemMeta(); -+ ((org.bukkit.inventory.meta.Damageable) itemMeta).setDamage(damage); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public void repair() { -+ repair(1); -+ } -+ -+ @Override -+ public boolean damage() { -+ return damage(1); -+ } -+ -+ @Override -+ public void repair(int amount) { -+ damage(-amount); -+ } -+ -+ @Override -+ public boolean damage(int amount) { -+ return damage(amount, false); -+ } -+ -+ @Override -+ public boolean damage(int amount, boolean ignoreUnbreaking) { -+ org.bukkit.inventory.meta.Damageable damageable = (org.bukkit.inventory.meta.Damageable) getItemMeta(); -+ if (amount > 0) { -+ int unbreaking = getEnchantLevel(Enchantment.UNBREAKING); -+ int reduce = 0; -+ for (int i = 0; unbreaking > 0 && i < amount; ++i) { -+ if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) { -+ ++reduce; -+ } -+ } -+ amount -= reduce; -+ if (amount <= 0) { -+ return isBroke(damageable.getDamage()); -+ } -+ } -+ int damage = damageable.getDamage() + amount; -+ damageable.setDamage(damage); -+ setItemMeta((ItemMeta) damageable); -+ return isBroke(damage); -+ } -+ -+ private boolean isBroke(int damage) { -+ if (damage > getType().getMaxDurability()) { -+ if (getAmount() > 0) { -+ // ensure it "breaks" -+ setAmount(0); -+ } -+ return true; -+ } -+ return false; -+ } -+ -+ private boolean reduceDamage(java.util.Random random, int unbreaking) { -+ if (getType().isArmor()) { -+ return random.nextFloat() < 0.6F; -+ } -+ return random.nextInt(unbreaking + 1) > 0; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -index 3592091c6d1371224e82e1f95b003951ad2f8779..4fdc78a9c74b42a8894030221e0452493d68020e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -@@ -38,6 +38,7 @@ public interface CraftRecipe extends Recipe { - stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat))); - } else if (bukkit instanceof RecipeChoice.ExactChoice) { - stack = Ingredient.ofStacks(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> CraftItemStack.asNMSCopy(mat)).toList()); -+ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur - // Paper start - support "empty" choices - legacy method that spigot might incorrectly call - // Their impl of Ingredient.of() will error, ingredients need at least one entry. - // Callers running into this exception may have passed an incorrect empty() recipe choice to a non-empty slot or -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java -index f86c95a13dff012de5db3e41ac261e9e8d44d9f3..1db0b790d824e419bb5fb6ab1f3003e120f9763b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java -@@ -75,4 +75,26 @@ public class CraftAnvilView extends CraftInventoryView experience - 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); -diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java +diff --git a/org/purpurmc/purpur/PurpurConfig.java b/org/purpurmc/purpur/PurpurConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b0105dd852b7 +index 0000000000000000000000000000000000000000..aca85686fb0b9ec78d4e83574bbfe6313785f40f --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -@@ -0,0 +1,580 @@ ++++ b/org/purpurmc/purpur/PurpurConfig.java +@@ -0,0 +1,581 @@ +package org.purpurmc.purpur; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; -+import com.mojang.datafixers.util.Pair; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.regex.Pattern; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.core.Registry; @@ -18255,12 +16774,8 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.effect.MobEffect; -+import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.food.FoodProperties; -+import net.minecraft.world.food.Foods; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; @@ -18271,21 +16786,17 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.purpurmc.purpur.command.PurpurCommand; -+import org.purpurmc.purpur.task.TPSBarTask; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; -+import java.util.ArrayList; -+import java.util.Collections; +import java.util.HashMap; -+import java.util.HashSet; +import java.util.List; +import java.util.Map; -+import java.util.Set; +import java.util.logging.Level; ++import org.purpurmc.purpur.task.TPSBarTask; + +@SuppressWarnings("unused") +public class PurpurConfig { @@ -18323,8 +16834,8 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 + commands = new HashMap<>(); + commands.put("purpur", new PurpurCommand("purpur")); + -+ version = getInt("config-version", 37); -+ set("config-version", 37); ++ version = getInt("config-version", 38); ++ set("config-version", 38); + + readConfig(PurpurConfig.class, null); + @@ -18479,6 +16990,11 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 + deathMessageOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.death.only-broadcast-to-affected-player", deathMessageOnlyBroadcastToAffectedPlayer); + } + ++ public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); ++ private static void serverModName() { ++ serverModName = getString("settings.server-mod-name", serverModName); ++ } ++ + public static double laggingThreshold = 19.0D; + private static void tickLoopSettings() { + laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); @@ -18693,7 +17209,7 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 + public static boolean endermanShortHeight = false; + private static void entitySettings() { + endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight); -+ if (endermanShortHeight) EntityType.ENDERMAN.setDimensions(EntityDimensions.scalable(0.6F, 1.9F)); ++ if (endermanShortHeight) EntityType.ENDERMAN.dimensions = EntityDimensions.scalable(0.6F, 1.9F); + } + + public static boolean allowWaterPlacementInTheEnd = true; @@ -18718,11 +17234,11 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 + maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond); + } + -+ public static java.util.regex.Pattern usernameValidCharactersPattern; ++ public static Pattern usernameValidCharactersPattern; + private static void usernameValidationSettings() { + String defaultPattern = "^[a-zA-Z0-9_.]*$"; + String setPattern = getString("settings.username-valid-characters", defaultPattern); -+ usernameValidCharactersPattern = java.util.regex.Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern); ++ usernameValidCharactersPattern = Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern); + } + + public static boolean fixProjectileLootingTransfer = false; @@ -18823,19 +17339,22 @@ index 0000000000000000000000000000000000000000..4401cd919951e92f613e0899f243b010 + }); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java +diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..36b875a1cb10f1704e8530b6eb7b7e9dc51ef995 +index 0000000000000000000000000000000000000000..2aac26f50958d8653eb1472daa7761d36093cf4c --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -0,0 +1,3431 @@ ++++ b/org/purpurmc/purpur/PurpurWorldConfig.java +@@ -0,0 +1,3440 @@ +package org.purpurmc.purpur; + ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.function.Predicate; ++import java.util.logging.Level; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.monster.Shulker; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.Item; @@ -18843,21 +17362,18 @@ index 0000000000000000000000000000000000000000..36b875a1cb10f1704e8530b6eb7b7e9d +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.properties.Tilt; ++import org.apache.commons.lang.BooleanUtils; ++import org.bukkit.ChatColor; ++import org.bukkit.World; ++import org.bukkit.configuration.ConfigurationSection; ++import java.util.List; ++import java.util.Map; +import org.purpurmc.purpur.tool.Flattenable; +import org.purpurmc.purpur.tool.Strippable; +import org.purpurmc.purpur.tool.Tillable; +import org.purpurmc.purpur.tool.Waxable; +import org.purpurmc.purpur.tool.Weatherable; -+import org.apache.commons.lang.BooleanUtils; -+import org.bukkit.ChatColor; -+import org.bukkit.World; -+import org.bukkit.configuration.ConfigurationSection; -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.function.Predicate; -+import java.util.logging.Level; ++ +import static org.purpurmc.purpur.PurpurConfig.log; + +@SuppressWarnings("unused") @@ -19063,6 +17579,7 @@ index 0000000000000000000000000000000000000000..36b875a1cb10f1704e8530b6eb7b7e9d + public List itemImmuneToFire = new ArrayList<>(); + public List itemImmuneToLightning = new ArrayList<>(); + public boolean dontRunWithScissors = false; ++ public ResourceLocation dontRunWithScissorsItemModelReference = ResourceLocation.parse("purpurmc:scissors"); + public boolean ignoreScissorsInWater = false; + public boolean ignoreScissorsInLava = false; + public double scissorsRunningDamage = 1D; @@ -19114,6 +17631,7 @@ index 0000000000000000000000000000000000000000..36b875a1cb10f1704e8530b6eb7b7e9d + if (item != Items.AIR) itemImmuneToLightning.add(item); + }); + dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); ++ dontRunWithScissorsItemModelReference = ResourceLocation.parse(getString("gameplay-mechanics.item.shears.damage-if-sprinting-item-model", "purpurmc:scissors")); + ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater); + ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava); + scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage); @@ -19435,28 +17953,35 @@ index 0000000000000000000000000000000000000000..36b875a1cb10f1704e8530b6eb7b7e9d + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); + } ++ if (PurpurConfig.version < 38) { ++ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:pale_oak_wood", Map.of("into", "minecraft:stripped_pale_oak_wood", "drops", new HashMap())); ++ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:pale_oak_log", Map.of("into", "minecraft:stripped_pale_oak_log", "drops", new HashMap())); ++ } + getMap("tools.axe.strippables", Map.ofEntries( + Map.entry("minecraft:oak_wood", Map.of("into", "minecraft:stripped_oak_wood", "drops", new HashMap())), + Map.entry("minecraft:oak_log", Map.of("into", "minecraft:stripped_oak_log", "drops", new HashMap())), + Map.entry("minecraft:dark_oak_wood", Map.of("into", "minecraft:stripped_dark_oak_wood", "drops", new HashMap())), + Map.entry("minecraft:dark_oak_log", Map.of("into", "minecraft:stripped_dark_oak_log", "drops", new HashMap())), ++ Map.entry("minecraft:pale_oak_wood", Map.of("into", "minecraft:stripped_pale_oak_wood", "drops", new HashMap())), ++ Map.entry("minecraft:pale_oak_log", Map.of("into", "minecraft:stripped_pale_oak_log", "drops", new HashMap())), + Map.entry("minecraft:acacia_wood", Map.of("into", "minecraft:stripped_acacia_wood", "drops", new HashMap())), + Map.entry("minecraft:acacia_log", Map.of("into", "minecraft:stripped_acacia_log", "drops", new HashMap())), ++ Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())), ++ Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())), + Map.entry("minecraft:birch_wood", Map.of("into", "minecraft:stripped_birch_wood", "drops", new HashMap())), + Map.entry("minecraft:birch_log", Map.of("into", "minecraft:stripped_birch_log", "drops", new HashMap())), + Map.entry("minecraft:jungle_wood", Map.of("into", "minecraft:stripped_jungle_wood", "drops", new HashMap())), + Map.entry("minecraft:jungle_log", Map.of("into", "minecraft:stripped_jungle_log", "drops", new HashMap())), + Map.entry("minecraft:spruce_wood", Map.of("into", "minecraft:stripped_spruce_wood", "drops", new HashMap())), + Map.entry("minecraft:spruce_log", Map.of("into", "minecraft:stripped_spruce_log", "drops", new HashMap())), -+ Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())), -+ Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())), -+ Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())), -+ Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())), -+ Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())), + Map.entry("minecraft:warped_stem", Map.of("into", "minecraft:stripped_warped_stem", "drops", new HashMap())), + Map.entry("minecraft:warped_hyphae", Map.of("into", "minecraft:stripped_warped_hyphae", "drops", new HashMap())), + Map.entry("minecraft:crimson_stem", Map.of("into", "minecraft:stripped_crimson_stem", "drops", new HashMap())), -+ Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap()))) ++ Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap())), ++ Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())), ++ Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())), ++ Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())) ++ ) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; } @@ -22260,11 +20785,11 @@ index 0000000000000000000000000000000000000000..36b875a1cb10f1704e8530b6eb7b7e9d + shearsCanDefuseTnt = shearsCanDefuseTntChance > 0.00F; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/CompassCommand.java b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java +diff --git a/org/purpurmc/purpur/command/CompassCommand.java b/org/purpurmc/purpur/command/CompassCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bcac74ea45 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java ++++ b/org/purpurmc/purpur/command/CompassCommand.java @@ -0,0 +1,27 @@ +package org.purpurmc.purpur.command; + @@ -22293,11 +20818,11 @@ index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bc + ); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java +diff --git a/org/purpurmc/purpur/command/CreditsCommand.java b/org/purpurmc/purpur/command/CreditsCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..40d2fab4a9728ac90c36e30c130f3116b7025d11 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java ++++ b/org/purpurmc/purpur/command/CreditsCommand.java @@ -0,0 +1,35 @@ +package org.purpurmc.purpur.command; + @@ -22334,11 +20859,11 @@ index 0000000000000000000000000000000000000000..40d2fab4a9728ac90c36e30c130f3116 + return targets.size(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/DemoCommand.java b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java +diff --git a/org/purpurmc/purpur/command/DemoCommand.java b/org/purpurmc/purpur/command/DemoCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..235f3cd89f675b70a6152a00534608c0902f19fd --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java ++++ b/org/purpurmc/purpur/command/DemoCommand.java @@ -0,0 +1,35 @@ +package org.purpurmc.purpur.command; + @@ -22375,12 +20900,12 @@ index 0000000000000000000000000000000000000000..235f3cd89f675b70a6152a00534608c0 + return targets.size(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/PingCommand.java b/src/main/java/org/purpurmc/purpur/command/PingCommand.java +diff --git a/org/purpurmc/purpur/command/PingCommand.java b/org/purpurmc/purpur/command/PingCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..f202b98a194604e39798fdb8e417c6d2835f71c8 +index 0000000000000000000000000000000000000000..74a602c331e9581d7425a09e4094f1d646099676 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/PingCommand.java -@@ -0,0 +1,33 @@ ++++ b/org/purpurmc/purpur/command/PingCommand.java +@@ -0,0 +1,32 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; @@ -22389,7 +20914,6 @@ index 0000000000000000000000000000000000000000..f202b98a194604e39798fdb8e417c6d2 +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; -+import org.bukkit.craftbukkit.util.CraftChatMessage; + +import java.util.Collection; +import java.util.Collections; @@ -22414,11 +20938,11 @@ index 0000000000000000000000000000000000000000..f202b98a194604e39798fdb8e417c6d2 + return targets.size(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java +diff --git a/org/purpurmc/purpur/command/PurpurCommand.java b/org/purpurmc/purpur/command/PurpurCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67878b90d5 +index 0000000000000000000000000000000000000000..7163c8247c5f564c723409e4dc645ebee0a7d4d1 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java ++++ b/org/purpurmc/purpur/command/PurpurCommand.java @@ -0,0 +1,66 @@ +package org.purpurmc.purpur.command; + @@ -22471,7 +20995,7 @@ index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67 + PurpurConfig.init((File) console.options.valueOf("purpur-settings")); + for (ServerLevel level : console.getAllLevels()) { + level.purpurConfig.init(); -+ level.resetBreedingCooldowns(); ++ level.resetBreedingCooldowns(); // Purpur - Add adjustable breeding cooldown to config + } + console.server.reloadCount++; + @@ -22486,11 +21010,11 @@ index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67 + return true; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java +diff --git a/org/purpurmc/purpur/command/RamBarCommand.java b/org/purpurmc/purpur/command/RamBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8ea47eecc6 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java ++++ b/org/purpurmc/purpur/command/RamBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + @@ -22536,11 +21060,11 @@ index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8e + return targets.size(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/RamCommand.java b/src/main/java/org/purpurmc/purpur/command/RamCommand.java +diff --git a/org/purpurmc/purpur/command/RamCommand.java b/org/purpurmc/purpur/command/RamCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..992f8dfc628c7485e335191e1308cdfd4eedfbe8 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/RamCommand.java ++++ b/org/purpurmc/purpur/command/RamCommand.java @@ -0,0 +1,30 @@ +package org.purpurmc.purpur.command; + @@ -22572,11 +21096,11 @@ index 0000000000000000000000000000000000000000..992f8dfc628c7485e335191e1308cdfd + ); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java +diff --git a/org/purpurmc/purpur/command/TPSBarCommand.java b/org/purpurmc/purpur/command/TPSBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f9b044107ff7c29a83eb5378aa9f5465ba1995 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java ++++ b/org/purpurmc/purpur/command/TPSBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + @@ -22622,11 +21146,11 @@ index 0000000000000000000000000000000000000000..d8f9b044107ff7c29a83eb5378aa9f54 + return targets.size(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java +diff --git a/org/purpurmc/purpur/command/UptimeCommand.java b/org/purpurmc/purpur/command/UptimeCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..4bb475099bcf8f05d5f1474e7fbf29c57c2c40cd --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java ++++ b/org/purpurmc/purpur/command/UptimeCommand.java @@ -0,0 +1,55 @@ +package org.purpurmc.purpur.command; + @@ -22683,89 +21207,18 @@ index 0000000000000000000000000000000000000000..4bb475099bcf8f05d5f1474e7fbf29c5 + long millis; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java +diff --git a/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java new file mode 100644 -index 0000000000000000000000000000000000000000..a04d23bd98075cd65a24d4de8d18281d1668480f +index 0000000000000000000000000000000000000000..940bcc6f79b59cb3cce578912eb789efd394f456 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java -@@ -0,0 +1,67 @@ -+package org.purpurmc.purpur.configuration.transformation; -+ -+import io.papermc.paper.configuration.Configurations; -+import io.papermc.paper.configuration.PaperConfigurations; -+import io.papermc.paper.configuration.type.number.DoubleOr; -+import java.util.OptionalDouble; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.purpurmc.purpur.PurpurConfig; -+import org.spongepowered.configurate.ConfigurateException; -+import org.spongepowered.configurate.ConfigurationNode; -+import org.spongepowered.configurate.NodePath; -+import org.spongepowered.configurate.transformation.ConfigurationTransformation; -+import org.spongepowered.configurate.transformation.TransformAction; -+ -+import static org.spongepowered.configurate.NodePath.path; -+ -+public class VoidDamageHeightMigration implements TransformAction { -+ -+ public static boolean HAS_BEEN_REGISTERED = false; -+ -+ public static final String ENVIRONMENT_KEY = "environment"; -+ public static final String VOID_DAMAGE_KEY = "void-damage-amount"; -+ public static final String VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY = "void-damage-min-build-height-offset"; -+ public static final double DEFAULT_VOID_DAMAGE_HEIGHT = -64.0D; -+ public static final double DEFAULT_VOID_DAMAGE = 4.0D; -+ -+ private final String worldName; -+ -+ private VoidDamageHeightMigration(String worldName) { -+ this.worldName = PaperConfigurations.WORLD_DEFAULTS.equals(worldName) ? "default" : worldName; -+ } -+ -+ @Override -+ public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { -+ String purpurVoidDamageHeightPath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-height"; -+ ConfigurationNode voidDamageMinHeightOffsetNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY); -+ if (PurpurConfig.config.contains(purpurVoidDamageHeightPath)) { -+ double purpurVoidDamageHeight = PurpurConfig.config.getDouble(purpurVoidDamageHeightPath); -+ if (purpurVoidDamageHeight != DEFAULT_VOID_DAMAGE_HEIGHT && (voidDamageMinHeightOffsetNode.empty() || voidDamageMinHeightOffsetNode.getDouble() == DEFAULT_VOID_DAMAGE_HEIGHT)) { -+ voidDamageMinHeightOffsetNode.raw(null); -+ voidDamageMinHeightOffsetNode.set(purpurVoidDamageHeight); -+ } -+ PurpurConfig.config.set(purpurVoidDamageHeightPath, null); -+ } -+ -+ String purpurVoidDamagePath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-dealt"; -+ ConfigurationNode voidDamageNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_KEY); -+ if (PurpurConfig.config.contains(purpurVoidDamagePath)) { -+ double purpurVoidDamage = PurpurConfig.config.getDouble(purpurVoidDamagePath); -+ if (purpurVoidDamage != DEFAULT_VOID_DAMAGE && (voidDamageNode.empty() || voidDamageNode.getDouble() == DEFAULT_VOID_DAMAGE)) { -+ voidDamageNode.raw(null); -+ voidDamageNode.set(new DoubleOr.Disabled(OptionalDouble.of(purpurVoidDamage))); -+ } -+ PurpurConfig.config.set(purpurVoidDamagePath, null); -+ } -+ -+ return null; -+ } -+ -+ public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap) { -+ if (PurpurConfig.version < 36) { -+ HAS_BEEN_REGISTERED = true; -+ builder.addAction(path(), new VoidDamageHeightMigration(contextMap.require(Configurations.WORLD_NAME))); -+ } -+ } -+ -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ed494e0ad278813a0eb261101447b84cca3ad7aa ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -@@ -0,0 +1,71 @@ ++++ b/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java +@@ -0,0 +1,74 @@ +package org.purpurmc.purpur.controller; + ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.entity.player.Input; +import net.minecraft.world.entity.player.Player; + +public class FlyingMoveControllerWASD extends MoveControllerWASD { @@ -22795,11 +21248,12 @@ index 0000000000000000000000000000000000000000..ed494e0ad278813a0eb261101447b84c + + @Override + public void purpurTick(Player rider) { -+ float forward = Math.max(0.0F, rider.getForwardMot()); ++ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); ++ float forward = lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : 0.0F; + float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F); -+ float strafe = rider.getStrafeMot(); ++ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F); + -+ if (rider.jumping && spacebarEvent(entity)) { ++ if (lastClientInput.jump() && spacebarEvent(entity)) { + entity.onSpacebar(); + } + @@ -22833,16 +21287,18 @@ index 0000000000000000000000000000000000000000..ed494e0ad278813a0eb261101447b84c + setStrafe(entity.getStrafeMot()); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java +diff --git a/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java new file mode 100644 -index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a040960d52e +index 0000000000000000000000000000000000000000..e0bbaec05afa0ae67ed486b14ea1fbadbbe90d9b --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -@@ -0,0 +1,63 @@ ++++ b/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java +@@ -0,0 +1,66 @@ +package org.purpurmc.purpur.controller; + ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.entity.player.Input; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; + @@ -22857,8 +21313,9 @@ index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a04 + + @Override + public void purpurTick(Player rider) { -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot() * 0.5F; ++ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); ++ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F); ++ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F; + float vertical = 0; + + if (forward < 0.0F) { @@ -22872,7 +21329,7 @@ index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a04 + speed *= groundSpeedModifier; + } + -+ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar()) { ++ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar()) { + entity.setNoGravity(true); + vertical = 1.0F; + } else { @@ -22902,11 +21359,11 @@ index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a04 + } + } +} -diff --git a/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java +diff --git a/org/purpurmc/purpur/controller/LookControllerWASD.java b/org/purpurmc/purpur/controller/LookControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..dd219518150ca90f89ad238904fd4095efe032d8 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java ++++ b/org/purpurmc/purpur/controller/LookControllerWASD.java @@ -0,0 +1,79 @@ +package org.purpurmc.purpur.controller; + @@ -22987,11 +21444,11 @@ index 0000000000000000000000000000000000000000..dd219518150ca90f89ad238904fd4095 + return pitch; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java +diff --git a/org/purpurmc/purpur/controller/MoveControllerWASD.java b/org/purpurmc/purpur/controller/MoveControllerWASD.java new file mode 100644 -index 0000000000000000000000000000000000000000..ad85c1ff6cd5d5ce2262bdb367ce9c8a5b707170 +index 0000000000000000000000000000000000000000..34f3c43fa16e950326ac5e3d93faee0466ffedc6 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java ++++ b/org/purpurmc/purpur/controller/MoveControllerWASD.java @@ -0,0 +1,92 @@ +package org.purpurmc.purpur.controller; + @@ -23064,7 +21521,7 @@ index 0000000000000000000000000000000000000000..ad85c1ff6cd5d5ce2262bdb367ce9c8a + + ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0); + -+ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) { ++ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) { + entity.jumpFromGround(); + } + @@ -23085,16 +21542,18 @@ index 0000000000000000000000000000000000000000..ad85c1ff6cd5d5ce2262bdb367ce9c8a + } + } +} -diff --git a/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java +diff --git a/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java new file mode 100644 -index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6fafaa9e0 +index 0000000000000000000000000000000000000000..922e48799c43ca322a8f550c98a26e1e2959439c --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -@@ -0,0 +1,50 @@ ++++ b/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java +@@ -0,0 +1,53 @@ +package org.purpurmc.purpur.controller; + ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.entity.player.Input; +import net.minecraft.world.entity.player.Player; + +public class WaterMoveControllerWASD extends MoveControllerWASD { @@ -23111,8 +21570,9 @@ index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6 + + @Override + public void purpurTick(Player rider) { -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot() * 0.5F; // strafe slower by default ++ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); ++ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F); ++ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F; // strafe slower by default + float vertical = -(rider.xRotO / 90); + + if (forward == 0.0F) { @@ -23141,259 +21601,11 @@ index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6 + setStrafe(entity.getStrafeMot()); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f25abee6dbf99c8d08f8e09db02b41df86115faa ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -@@ -0,0 +1,107 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.animal.Dolphin; -+import net.minecraft.world.entity.projectile.LlamaSpit; -+import net.minecraft.world.entity.projectile.ProjectileUtil; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+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.event.entity.EntityRemoveEvent; -+ -+public class DolphinSpit extends LlamaSpit { -+ public LivingEntity dolphin; -+ public int ticksLived; -+ -+ public DolphinSpit(EntityType type, Level world) { -+ super(type, world); -+ } -+ -+ public DolphinSpit(Level world, Dolphin dolphin) { -+ this(EntityType.LLAMA_SPIT, world); -+ setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); -+ this.dolphin = dolphin; -+ this.setPos( -+ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F), -+ dolphin.getEyeY() - 0.10000000149011612D, -+ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F)); -+ } -+ -+ // Purpur start -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ // Purpur end -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); -+ -+ this.preHitTargetOrDeflectSelf(hitResult); -+ -+ double x = this.getX() + mot.x; -+ double y = this.getY() + mot.y; -+ double z = this.getZ() + mot.z; -+ -+ this.updateRotation(); -+ -+ Vec3 motDouble = mot.scale(2.0); -+ for (int i = 0; i < 5; i++) { -+ ((ServerLevel) level()).sendParticles(null, ParticleTypes.BUBBLE, -+ getX() + random.nextFloat() / 2 - 0.25F, -+ getY() + random.nextFloat() / 2 - 0.25F, -+ getZ() + random.nextFloat() / 2 - 0.25F, -+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(EntityRemoveEvent.Cause.DISCARD); -+ } else { -+ this.setDeltaMovement(mot.scale(0.99D)); -+ if (!this.isNoGravity()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); -+ } -+ -+ this.setPos(x, y, z); -+ } -+ } -+ -+ @Override -+ public void shoot(double x, double y, double z, float speed, float inaccuracy) { -+ setDeltaMovement(new Vec3(x, y, z).normalize().add( -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) -+ .scale(speed)); -+ } -+ -+ @Override -+ protected void onHitEntity(EntityHitResult entityHitResult) { -+ Entity shooter = this.getOwner(); -+ if (shooter instanceof LivingEntity) { -+ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage); -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ if (this.hitCancelled) { -+ return; -+ } -+ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(this.level(), state, blockHitResult, this); -+ this.discard(EntityRemoveEvent.Cause.DISCARD); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..58957b0bd3cd2c37fd4a6766a02e2506d9f51010 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -@@ -0,0 +1,129 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.decoration.ArmorStand; -+import net.minecraft.world.entity.monster.Phantom; -+import net.minecraft.world.entity.projectile.LlamaSpit; -+import net.minecraft.world.entity.projectile.ProjectileUtil; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockBehaviour; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.phys.BlockHitResult; -+import net.minecraft.world.phys.EntityHitResult; -+import net.minecraft.world.phys.HitResult; -+import net.minecraft.world.phys.Vec3; -+ -+public class PhantomFlames extends LlamaSpit { -+ public Phantom phantom; -+ public int ticksLived; -+ public boolean canGrief = false; -+ -+ public PhantomFlames(EntityType type, Level world) { -+ super(type, world); -+ } -+ -+ public PhantomFlames(Level world, Phantom phantom) { -+ this(EntityType.LLAMA_SPIT, world); -+ setOwner(phantom.getRider() != null ? phantom.getRider() : phantom); -+ this.phantom = phantom; -+ this.setPos( -+ phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * 0.017453292F), -+ phantom.getEyeY() - 0.10000000149011612D, -+ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * 0.017453292F)); -+ } -+ -+ // Purpur start -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ // Purpur end -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); -+ -+ this.preHitTargetOrDeflectSelf(hitResult); -+ -+ double x = this.getX() + mot.x; -+ double y = this.getY() + mot.y; -+ double z = this.getZ() + mot.z; -+ -+ this.updateRotation(); -+ -+ Vec3 motDouble = mot.scale(2.0); -+ for (int i = 0; i < 5; i++) { -+ ((ServerLevel) level()).sendParticles(null, ParticleTypes.FLAME, -+ getX() + random.nextFloat() / 2 - 0.25F, -+ getY() + random.nextFloat() / 2 - 0.25F, -+ getZ() + random.nextFloat() / 2 - 0.25F, -+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } else if (this.isInWaterOrBubble()) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } else { -+ this.setDeltaMovement(mot.scale(0.99D)); -+ if (!this.isNoGravity()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); -+ } -+ -+ this.setPos(x, y, z); -+ } -+ } -+ -+ @Override -+ public void shoot(double x, double y, double z, float speed, float inaccuracy) { -+ setDeltaMovement(new Vec3(x, y, z).normalize().add( -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) -+ .scale(speed)); -+ } -+ -+ @Override -+ protected void onHitEntity(EntityHitResult entityHitResult) { -+ Level world = this.level(); -+ -+ if (world instanceof ServerLevel worldserver) { -+ Entity shooter = this.getOwner(); -+ if (shooter instanceof LivingEntity) { -+ Entity target = entityHitResult.getEntity(); -+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { -+ boolean hurt = target.hurtServer(worldserver, target.damageSources().mobProjectile(this, (LivingEntity) shooter), worldserver.purpurConfig.phantomFlameDamage); -+ if (hurt && worldserver.purpurConfig.phantomFlameFireTime > 0) { -+ target.igniteForSeconds(worldserver.purpurConfig.phantomFlameFireTime); -+ } -+ } -+ } -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ Level world = this.level(); -+ -+ if (world instanceof ServerLevel worldserver) { -+ if (this.hitCancelled) { -+ return; -+ } -+ if (this.canGrief) { -+ BlockState state = worldserver.getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(worldserver, state, blockHitResult, this); -+ } -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java +diff --git a/org/purpurmc/purpur/entity/PurpurStoredBee.java b/org/purpurmc/purpur/entity/PurpurStoredBee.java new file mode 100644 index 0000000000000000000000000000000000000000..7608bf0981fa0d37031e51e57e4086cb5ec4c88b --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java ++++ b/org/purpurmc/purpur/entity/PurpurStoredBee.java @@ -0,0 +1,106 @@ +package org.purpurmc.purpur.entity; + @@ -23501,11 +21713,11 @@ index 0000000000000000000000000000000000000000..7608bf0981fa0d37031e51e57e4086cb + } + } +} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java +diff --git a/org/purpurmc/purpur/entity/ai/HasRider.java b/org/purpurmc/purpur/entity/ai/HasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java ++++ b/org/purpurmc/purpur/entity/ai/HasRider.java @@ -0,0 +1,20 @@ +package org.purpurmc.purpur.entity.ai; + @@ -23527,11 +21739,11 @@ index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445 + return entity.getRider() != null && entity.isControllable(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java +diff --git a/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/org/purpurmc/purpur/entity/ai/HorseHasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java ++++ b/org/purpurmc/purpur/entity/ai/HorseHasRider.java @@ -0,0 +1,17 @@ +package org.purpurmc.purpur.entity.ai; + @@ -23550,11 +21762,11 @@ index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a + return super.canUse() && horse.isSaddled(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java +diff --git a/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/org/purpurmc/purpur/entity/ai/LlamaHasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java ++++ b/org/purpurmc/purpur/entity/ai/LlamaHasRider.java @@ -0,0 +1,17 @@ +package org.purpurmc.purpur.entity.ai; + @@ -23573,11 +21785,11 @@ index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5d + return super.canUse() && llama.isSaddled() && llama.isControllable(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java +diff --git a/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/org/purpurmc/purpur/entity/ai/ReceiveFlower.java new file mode 100644 index 0000000000000000000000000000000000000000..9660716f4162a4441c6e1b0baddef8f5086566c5 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java ++++ b/org/purpurmc/purpur/entity/ai/ReceiveFlower.java @@ -0,0 +1,91 @@ +package org.purpurmc.purpur.entity.ai; + @@ -23670,11 +21882,255 @@ index 0000000000000000000000000000000000000000..9660716f4162a4441c6e1b0baddef8f5 + return item.getItem() == Blocks.POPPY.asItem(); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java +diff --git a/org/purpurmc/purpur/entity/projectile/DolphinSpit.java b/org/purpurmc/purpur/entity/projectile/DolphinSpit.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8ffacde0417d29bb2de12d58fe42fd0717735f9d +--- /dev/null ++++ b/org/purpurmc/purpur/entity/projectile/DolphinSpit.java +@@ -0,0 +1,105 @@ ++package org.purpurmc.purpur.entity.projectile; ++ ++import net.minecraft.core.particles.ParticleTypes; ++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.LivingEntity; ++import net.minecraft.world.entity.animal.Dolphin; ++import net.minecraft.world.entity.projectile.LlamaSpit; ++import net.minecraft.world.entity.projectile.ProjectileUtil; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++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.event.entity.EntityRemoveEvent; ++ ++public class DolphinSpit extends LlamaSpit { ++ public LivingEntity dolphin; ++ public int ticksLived; ++ ++ public DolphinSpit(EntityType type, Level world) { ++ super(type, world); ++ } ++ ++ public DolphinSpit(Level world, Dolphin dolphin) { ++ this(EntityType.LLAMA_SPIT, world); ++ this.setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); ++ this.dolphin = dolphin; ++ this.setPos( ++ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5 * (double) Mth.sin(dolphin.yBodyRot * (float) (Math.PI / 180.0)), ++ dolphin.getEyeY() - 0.1F, ++ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5 * (double) Mth.cos(dolphin.yBodyRot * (float) (Math.PI / 180.0))); ++ } ++ ++ @Override ++ public boolean canSaveToDisk() { ++ return false; ++ } ++ ++ public void tick() { ++ projectileTick(); ++ ++ Vec3 mot = this.getDeltaMovement(); ++ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); ++ ++ this.preHitTargetOrDeflectSelf(hitResult); ++ ++ double x = this.getX() + mot.x; ++ double y = this.getY() + mot.y; ++ double z = this.getZ() + mot.z; ++ ++ this.updateRotation(); ++ ++ Vec3 motDouble = mot.scale(2.0); ++ for (int i = 0; i < 5; i++) { ++ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.BUBBLE, ++ false, true, ++ getX() + random.nextFloat() / 2 - 0.25F, ++ getY() + random.nextFloat() / 2 - 0.25F, ++ getZ() + random.nextFloat() / 2 - 0.25F, ++ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D); ++ } ++ ++ if (++ticksLived > 20) { ++ this.discard(EntityRemoveEvent.Cause.DISCARD); ++ } else { ++ this.setDeltaMovement(mot.scale(0.99D)); ++ if (!this.isNoGravity()) { ++ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); ++ } ++ ++ this.setPos(x, y, z); ++ } ++ } ++ ++ @Override ++ public void shoot(double x, double y, double z, float speed, float inaccuracy) { ++ setDeltaMovement(new Vec3(x, y, z).normalize().add( ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) ++ .scale(speed)); ++ } ++ ++ @Override ++ protected void onHitEntity(EntityHitResult entityHitResult) { ++ Entity shooter = this.getOwner(); ++ if (shooter instanceof LivingEntity) { ++ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage); ++ } ++ } ++ ++ @Override ++ protected void onHitBlock(BlockHitResult blockHitResult) { ++ if (this.hitCancelled) { ++ return; ++ } ++ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); ++ state.onProjectileHit(this.level(), state, blockHitResult, this); ++ this.discard(EntityRemoveEvent.Cause.DISCARD); ++ } ++} +diff --git a/org/purpurmc/purpur/entity/projectile/PhantomFlames.java b/org/purpurmc/purpur/entity/projectile/PhantomFlames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..580d8dc556da951e2e1ab4e199317e2f5b166f2f +--- /dev/null ++++ b/org/purpurmc/purpur/entity/projectile/PhantomFlames.java +@@ -0,0 +1,127 @@ ++package org.purpurmc.purpur.entity.projectile; ++ ++import net.minecraft.core.particles.ParticleTypes; ++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.LivingEntity; ++import net.minecraft.world.entity.decoration.ArmorStand; ++import net.minecraft.world.entity.monster.Phantom; ++import net.minecraft.world.entity.projectile.LlamaSpit; ++import net.minecraft.world.entity.projectile.ProjectileUtil; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockBehaviour; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.phys.BlockHitResult; ++import net.minecraft.world.phys.EntityHitResult; ++import net.minecraft.world.phys.HitResult; ++import net.minecraft.world.phys.Vec3; ++ ++public class PhantomFlames extends LlamaSpit { ++ public Phantom phantom; ++ public int ticksLived; ++ public boolean canGrief = false; ++ ++ public PhantomFlames(EntityType type, Level world) { ++ super(type, world); ++ } ++ ++ public PhantomFlames(Level world, Phantom phantom) { ++ this(EntityType.LLAMA_SPIT, world); ++ setOwner(phantom.getRider() != null ? phantom.getRider() : phantom); ++ this.phantom = phantom; ++ this.setPos( ++ phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * (float) (Math.PI / 180.0)), ++ phantom.getEyeY() - 0.10000000149011612D, ++ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * (float) (Math.PI / 180.0))); ++ } ++ ++ @Override ++ public boolean canSaveToDisk() { ++ return false; ++ } ++ ++ public void tick() { ++ projectileTick(); ++ ++ Vec3 mot = this.getDeltaMovement(); ++ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); ++ ++ this.preHitTargetOrDeflectSelf(hitResult); ++ ++ double x = this.getX() + mot.x; ++ double y = this.getY() + mot.y; ++ double z = this.getZ() + mot.z; ++ ++ this.updateRotation(); ++ ++ Vec3 motDouble = mot.scale(2.0); ++ for (int i = 0; i < 5; i++) { ++ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.FLAME, ++ false, true, ++ getX() + random.nextFloat() / 2 - 0.25F, ++ getY() + random.nextFloat() / 2 - 0.25F, ++ getZ() + random.nextFloat() / 2 - 0.25F, ++ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D); ++ } ++ ++ if (++ticksLived > 20) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } else if (this.isInWaterOrBubble()) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } else { ++ this.setDeltaMovement(mot.scale(0.99D)); ++ if (!this.isNoGravity()) { ++ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); ++ } ++ ++ this.setPos(x, y, z); ++ } ++ } ++ ++ @Override ++ public void shoot(double x, double y, double z, float speed, float inaccuracy) { ++ setDeltaMovement(new Vec3(x, y, z).normalize().add( ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) ++ .scale(speed)); ++ } ++ ++ @Override ++ protected void onHitEntity(EntityHitResult entityHitResult) { ++ Level world = this.level(); ++ ++ if (world instanceof ServerLevel worldserver) { ++ Entity shooter = this.getOwner(); ++ if (shooter instanceof LivingEntity) { ++ Entity target = entityHitResult.getEntity(); ++ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { ++ boolean hurt = target.hurtServer(worldserver, target.damageSources().mobProjectile(this, (LivingEntity) shooter), worldserver.purpurConfig.phantomFlameDamage); ++ if (hurt && worldserver.purpurConfig.phantomFlameFireTime > 0) { ++ target.igniteForSeconds(worldserver.purpurConfig.phantomFlameFireTime); ++ } ++ } ++ } ++ } ++ } ++ ++ @Override ++ protected void onHitBlock(BlockHitResult blockHitResult) { ++ Level world = this.level(); ++ ++ if (world instanceof ServerLevel worldserver) { ++ if (this.hitCancelled) { ++ return; ++ } ++ if (this.canGrief) { ++ BlockState state = worldserver.getBlockState(blockHitResult.getBlockPos()); ++ state.onProjectileHit(worldserver, state, blockHitResult, this); ++ } ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ } ++ } ++} +diff --git a/org/purpurmc/purpur/gui/GUIColor.java b/org/purpurmc/purpur/gui/GUIColor.java new file mode 100644 index 0000000000000000000000000000000000000000..550222758bf0e7deff26a6e813a860b7be365e87 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java ++++ b/org/purpurmc/purpur/gui/GUIColor.java @@ -0,0 +1,58 @@ +package org.purpurmc.purpur.gui; + @@ -23734,11 +22190,11 @@ index 0000000000000000000000000000000000000000..550222758bf0e7deff26a6e813a860b7 + } + } +} -diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java +diff --git a/org/purpurmc/purpur/gui/JColorTextPane.java b/org/purpurmc/purpur/gui/JColorTextPane.java new file mode 100644 index 0000000000000000000000000000000000000000..d75fb5e77eff27d86135ed7d605dbc250b660f7d --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java ++++ b/org/purpurmc/purpur/gui/JColorTextPane.java @@ -0,0 +1,83 @@ +package org.purpurmc.purpur.gui; + @@ -23823,11 +22279,11 @@ index 0000000000000000000000000000000000000000..d75fb5e77eff27d86135ed7d605dbc25 + } + } +} -diff --git a/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java +diff --git a/org/purpurmc/purpur/item/GlowBerryItem.java b/org/purpurmc/purpur/item/GlowBerryItem.java new file mode 100644 index 0000000000000000000000000000000000000000..b257f35caa13b660854cf17f41fd8fba1d56c458 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java ++++ b/org/purpurmc/purpur/item/GlowBerryItem.java @@ -0,0 +1,26 @@ +package org.purpurmc.purpur.item; + @@ -23855,11 +22311,11 @@ index 0000000000000000000000000000000000000000..b257f35caa13b660854cf17f41fd8fba + return result; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java +diff --git a/org/purpurmc/purpur/item/SpawnerItem.java b/org/purpurmc/purpur/item/SpawnerItem.java new file mode 100644 index 0000000000000000000000000000000000000000..ed50cb2115401c9039df4136caf5a087a5f5991c --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java ++++ b/org/purpurmc/purpur/item/SpawnerItem.java @@ -0,0 +1,40 @@ +package org.purpurmc.purpur.item; + @@ -23901,11 +22357,11 @@ index 0000000000000000000000000000000000000000..ed50cb2115401c9039df4136caf5a087 + return handled; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java b/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java +diff --git a/org/purpurmc/purpur/network/ClientboundBeehivePayload.java b/org/purpurmc/purpur/network/ClientboundBeehivePayload.java new file mode 100644 index 0000000000000000000000000000000000000000..793a3ea45fe04e84725926f17615c26e008b0ce4 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java ++++ b/org/purpurmc/purpur/network/ClientboundBeehivePayload.java @@ -0,0 +1,27 @@ +package org.purpurmc.purpur.network; + @@ -23934,11 +22390,11 @@ index 0000000000000000000000000000000000000000..793a3ea45fe04e84725926f17615c26e + return TYPE; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java b/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java +diff --git a/org/purpurmc/purpur/network/ServerboundBeehivePayload.java b/org/purpurmc/purpur/network/ServerboundBeehivePayload.java new file mode 100644 index 0000000000000000000000000000000000000000..fa72769e06061609e1e658a0250e99c8cb026c0e --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java ++++ b/org/purpurmc/purpur/network/ServerboundBeehivePayload.java @@ -0,0 +1,26 @@ +package org.purpurmc.purpur.network; + @@ -23966,11 +22422,11 @@ index 0000000000000000000000000000000000000000..fa72769e06061609e1e658a0250e99c8 + return TYPE; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java +diff --git a/org/purpurmc/purpur/task/BeehiveTask.java b/org/purpurmc/purpur/task/BeehiveTask.java new file mode 100644 index 0000000000000000000000000000000000000000..664f9d5e1ce5e2787bf699bd11758b9e3aa8ed3a --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java ++++ b/org/purpurmc/purpur/task/BeehiveTask.java @@ -0,0 +1,67 @@ +package org.purpurmc.purpur.task; + @@ -24039,11 +22495,11 @@ index 0000000000000000000000000000000000000000..664f9d5e1ce5e2787bf699bd11758b9e + player.sendPluginMessage(this.plugin, customPacketPayload.type().id().toString(), byteArray); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/task/BossBarTask.java b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java +diff --git a/org/purpurmc/purpur/task/BossBarTask.java b/org/purpurmc/purpur/task/BossBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..3c3d4cd52db93b97a40321030a70ebc282c9636b --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java ++++ b/org/purpurmc/purpur/task/BossBarTask.java @@ -0,0 +1,121 @@ +package org.purpurmc.purpur.task; + @@ -24166,11 +22622,11 @@ index 0000000000000000000000000000000000000000..3c3d4cd52db93b97a40321030a70ebc2 + CompassTask.instance().removePlayer(player); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/task/CompassTask.java b/src/main/java/org/purpurmc/purpur/task/CompassTask.java +diff --git a/org/purpurmc/purpur/task/CompassTask.java b/org/purpurmc/purpur/task/CompassTask.java new file mode 100644 index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2fb916c025 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/CompassTask.java ++++ b/org/purpurmc/purpur/task/CompassTask.java @@ -0,0 +1,68 @@ +package org.purpurmc.purpur.task; + @@ -24240,11 +22696,11 @@ index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2f + return yaw; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/task/RamBarTask.java b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java +diff --git a/org/purpurmc/purpur/task/RamBarTask.java b/org/purpurmc/purpur/task/RamBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa0c3ab38f --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java ++++ b/org/purpurmc/purpur/task/RamBarTask.java @@ -0,0 +1,117 @@ +package org.purpurmc.purpur.task; + @@ -24363,11 +22819,11 @@ index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa + return this.percent; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java +diff --git a/org/purpurmc/purpur/task/TPSBarTask.java b/org/purpurmc/purpur/task/TPSBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc562c15d1 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java ++++ b/org/purpurmc/purpur/task/TPSBarTask.java @@ -0,0 +1,142 @@ +package org.purpurmc.purpur.task; + @@ -24511,11 +22967,11 @@ index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc + TPS, MSPT, PING + } +} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Actionable.java b/src/main/java/org/purpurmc/purpur/tool/Actionable.java +diff --git a/org/purpurmc/purpur/tool/Actionable.java b/org/purpurmc/purpur/tool/Actionable.java new file mode 100644 index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b1477c1e70e --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Actionable.java ++++ b/org/purpurmc/purpur/tool/Actionable.java @@ -0,0 +1,24 @@ +package org.purpurmc.purpur.tool; + @@ -24541,11 +22997,11 @@ index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b14 + return drops; + } +} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Flattenable.java b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java +diff --git a/org/purpurmc/purpur/tool/Flattenable.java b/org/purpurmc/purpur/tool/Flattenable.java new file mode 100644 index 0000000000000000000000000000000000000000..345d4ee4ff0b78bd1050959711a4f5d16a5e8aee --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java ++++ b/org/purpurmc/purpur/tool/Flattenable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + @@ -24559,11 +23015,11 @@ index 0000000000000000000000000000000000000000..345d4ee4ff0b78bd1050959711a4f5d1 + super(into, drops); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Strippable.java b/src/main/java/org/purpurmc/purpur/tool/Strippable.java +diff --git a/org/purpurmc/purpur/tool/Strippable.java b/org/purpurmc/purpur/tool/Strippable.java new file mode 100644 index 0000000000000000000000000000000000000000..bf5402214f41af9c09bd6c5c4f45d330516d742e --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Strippable.java ++++ b/org/purpurmc/purpur/tool/Strippable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + @@ -24577,11 +23033,11 @@ index 0000000000000000000000000000000000000000..bf5402214f41af9c09bd6c5c4f45d330 + super(into, drops); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Tillable.java b/src/main/java/org/purpurmc/purpur/tool/Tillable.java +diff --git a/org/purpurmc/purpur/tool/Tillable.java b/org/purpurmc/purpur/tool/Tillable.java new file mode 100644 index 0000000000000000000000000000000000000000..715f6dd44480347eebced43c11bc364e05727498 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Tillable.java ++++ b/org/purpurmc/purpur/tool/Tillable.java @@ -0,0 +1,50 @@ +package org.purpurmc.purpur.tool; + @@ -24633,11 +23089,11 @@ index 0000000000000000000000000000000000000000..715f6dd44480347eebced43c11bc364e + } + } +} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Waxable.java b/src/main/java/org/purpurmc/purpur/tool/Waxable.java +diff --git a/org/purpurmc/purpur/tool/Waxable.java b/org/purpurmc/purpur/tool/Waxable.java new file mode 100644 index 0000000000000000000000000000000000000000..64adb13b29b6757dcf227a55588da70ecabe083f --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Waxable.java ++++ b/org/purpurmc/purpur/tool/Waxable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + @@ -24651,11 +23107,11 @@ index 0000000000000000000000000000000000000000..64adb13b29b6757dcf227a55588da70e + super(into, drops); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Weatherable.java b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java +diff --git a/org/purpurmc/purpur/tool/Weatherable.java b/org/purpurmc/purpur/tool/Weatherable.java new file mode 100644 index 0000000000000000000000000000000000000000..b7586f494528f30eb0da82420d3bcf5b83a1a902 --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java ++++ b/org/purpurmc/purpur/tool/Weatherable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + @@ -24669,27 +23125,28 @@ index 0000000000000000000000000000000000000000..b7586f494528f30eb0da82420d3bcf5b + super(into, drops); + } +} -diff --git a/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java +diff --git a/org/purpurmc/purpur/util/MinecraftInternalPlugin.java b/org/purpurmc/purpur/util/MinecraftInternalPlugin.java new file mode 100644 -index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32dc91d64d1 +index 0000000000000000000000000000000000000000..77a18f3048b18d38c5bd0129065efff27fa774fa --- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java -@@ -0,0 +1,152 @@ ++++ b/org/purpurmc/purpur/util/MinecraftInternalPlugin.java +@@ -0,0 +1,149 @@ +package org.purpurmc.purpur.util; + ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.ChunkGenerator; ++import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -+ +import java.io.File; +import java.io.InputStream; +import java.util.List; @@ -24704,7 +23161,6 @@ index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32d + this.pluginName = "Minecraft"; + pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); + } -+ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } @@ -24718,12 +23174,11 @@ index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32d + public PluginDescriptionFile getDescription() { + return pdf; + } -+ // Paper start ++ + @Override + public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { + return pdf; + } -+ // Paper end + + @Override + public FileConfiguration getConfig() { @@ -24820,91 +23275,8 @@ index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32d + throw new UnsupportedOperationException("Not supported."); + } + -+ // Paper start - lifecycle events + @Override -+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { ++ public @NotNull LifecycleEventManager getLifecycleManager() { + throw new UnsupportedOperationException("Not supported."); + } -+ // Paper end - lifecycle events +} -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 62bb1fa77045ea83afe8a181c3b3a4d7284103b7..a526121ff6af27bb0a87fec672aa23493d018df0 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -211,6 +211,8 @@ public class ActivationRange - continue; - } - -+ if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur -+ - // Paper start - int worldHeight = world.getHeight(); - ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); -@@ -405,6 +407,7 @@ public class ActivationRange - */ - public static boolean checkIfActive(Entity entity) - { -+ if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof net.minecraft.world.entity.animal.Squid) return true; // Purpur - // Never safe to skip fireworks or item gravity - if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick - return true; -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index d2a75850af9c6ad2aca66a5f994f1b587d73eac4..a056aa167887abef9e6d531a9edd2cda433567d2 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -2,7 +2,16 @@ - - - -- -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - -diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -index 75ed5050f72c001d6eab117a2c0b352a413548bd..180c0a532bbac10a8280b63eb7aa783a1bfbb237 100644 ---- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -+++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -@@ -46,6 +46,7 @@ public class MinecraftCommandPermissionsTest { - Set foundPerms = new HashSet<>(); - for (CommandNode child : root.getChildren()) { - final String vanillaPerm = VanillaCommandWrapper.getPermission(child); -+ if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur - if (!perms.contains(vanillaPerm)) { - missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); - } else { -@@ -58,6 +59,25 @@ public class MinecraftCommandPermissionsTest { - } - - private static final List TO_SKIP = List.of( -+ // Purpur start -+ "minecraft.command.compass", -+ "minecraft.command.credits", -+ "minecraft.command.demo", -+ "minecraft.command.ping", -+ "minecraft.command.ram", -+ "minecraft.command.rambar", -+ "minecraft.command.tpsbar", -+ "minecraft.command.uptime", -+ "minecraft.command.debug", -+ "minecraft.command.gamemode.adventure", -+ "minecraft.command.gamemode.adventure.other", -+ "minecraft.command.gamemode.creative", -+ "minecraft.command.gamemode.creative.other", -+ "minecraft.command.gamemode.spectator", -+ "minecraft.command.gamemode.spectator.other", -+ "minecraft.command.gamemode.survival", -+ "minecraft.command.gamemode.survival.other", -+ // Purpur end - "minecraft.command.selector" - ); - diff --git a/Leaf-Server/minecraft-patches/features/0007-Fix-Pufferfish-and-Purpur-patches.patch b/Leaf-Server/minecraft-patches/features/0007-Fix-Pufferfish-and-Purpur-patches.patch new file mode 100644 index 00000000..ae9279a6 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0007-Fix-Pufferfish-and-Purpur-patches.patch @@ -0,0 +1,227 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 29 Apr 2024 14:18:58 -0400 +Subject: [PATCH] Fix Pufferfish and Purpur patches + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index f348202ae2cc78586cf4b7926180c659b5fca56f..66418672e548a022980a403de912e05984137f37 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -278,7 +278,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1256,9 +1256,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop byteAllowed) { +- ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); // Leaf - Fix Pufferfish and Purpur patches + org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - PlayerBookTooLargeEvent + this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect + return; +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 3f97182bc9ef86476c25deb3106dab7152014edf..3e1a5ef63d97e2ad43d98c5736a185ade7afb4bd 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -536,21 +536,31 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // Purpur end - Add canSaveToDisk to Entity + ++ // Gale start - JettPack - optimize sun burn tick - cache eye blockpos ++ private BlockPos cached_eye_blockpos; ++ private net.minecraft.world.phys.Vec3 cached_position; ++ // Gale end - JettPack - optimize sun burn tick - cache eye blockpos ++ + // Purpur start - copied from Mob - API for any mob to burn daylight + public boolean isSunBurnTick() { + if (this.level().isDay() && !this.level().isClientSide) { +- float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue(); +- BlockPos blockPos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); +- boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; +- if (lightLevelDependentMagicValue > 0.5F +- && this.random.nextFloat() * 30.0F < (lightLevelDependentMagicValue - 0.4F) * 2.0F +- && !flag +- && this.level().canSeeSky(blockPos)) { +- return true; ++ // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos ++ if (this.cached_position != this.position) { ++ this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); ++ this.cached_position = this.position; + } ++ ++ float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness ++ ++ // Check brightness first ++ if (lightLevelDependentMagicValue <= 0.5F) return false; ++ if (this.random.nextFloat() * 30.0F >= (lightLevelDependentMagicValue - 0.4F) * 2.0F) return false; ++ // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos ++ boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; ++ return !flag && this.level().canSeeSky(this.cached_eye_blockpos); // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos + } + +- return false; ++ return false; // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - diff on change + } + // Purpur end - copied from Mob - API for any mob to burn daylight + +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 8728aba69ac24b52e6221ea2ddd8fc6d5a26f3f3..956c1b5422c177e0da5140c4184720d10aa4e790 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1024,13 +1024,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + // Gale start - Petal - reduce skull ItemStack lookups for reduced visibility + EntityType type = lookingEntity.getType(); + // Purpur start - Mob head visibility percent +- if (type == EntityType.SKELETON && itemBySlot.is(Items.SKELETON_SKULL)) { ++ if (type == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL)) { + d *= lookingEntity.level().purpurConfig.skeletonHeadVisibilityPercent; +- } else if (type == EntityType.ZOMBIE && itemBySlot.is(Items.ZOMBIE_HEAD)) { ++ } else if (type == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD)) { + d *= lookingEntity.level().purpurConfig.zombieHeadVisibilityPercent; +- } else if ((type == EntityType.PIGLIN || type == EntityType.PIGLIN_BRUTE) && itemBySlot.is(Items.PIGLIN_HEAD)) { ++ } else if ((type == EntityType.PIGLIN || type == EntityType.PIGLIN_BRUTE) && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD)) { + d *= lookingEntity.level().purpurConfig.piglinHeadVisibilityPercent; +- } else if (type == EntityType.CREEPER && itemBySlot.is(Items.CREEPER_HEAD)) { ++ } else if (type == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { + d *= lookingEntity.level().purpurConfig.creeperHeadVisibilityPercent; + } + // Purpur end - Mob head visibility percent +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 7b8804844ab5636323bc8d136c775f1e9591e89c..14d9dceacc82cc6c085dab8f52e59a318dd8cae5 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -1630,11 +1630,6 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + protected void playAttackSound() { + } + +- // Gale start - JettPack - optimize sun burn tick - cache eye blockpos +- private BlockPos cached_eye_blockpos; +- private net.minecraft.world.phys.Vec3 cached_position; +- // Gale end - JettPack - optimize sun burn tick - cache eye blockpos +- + public boolean isSunBurnTick() { + // Purpur - implemented in Entity - API for any mob to burn daylight + return super.isSunBurnTick(); +diff --git a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +index 6f96551ba91da214054b89a255254ca597977cc0..3f1a80db81c0f0cd7bb1d5df4a2c2cb1b6a8d2d9 100644 +--- a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +@@ -22,19 +22,15 @@ public class SecondaryPoiSensor extends Sensor { + + @Override + protected void doTick(ServerLevel level, Villager entity) { ++ // Purpur start - Option for Villager Clerics to farm Nether Wart - make sure clerics don't wander to soul sand when the option is off + // Gale start - Lithium - skip secondary POI sensor if absent + var secondaryPoi = entity.getVillagerData().getProfession().secondaryPoi(); +- if (secondaryPoi.isEmpty()) { +- entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); +- return; +- } +- // Gale end - Lithium - skip secondary POI sensor if absent +- // Purpur start - Option for Villager Clerics to farm Nether Wart - make sure clerics don't wander to soul sand when the option is off + Brain brain = entity.getBrain(); +- if (!level.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { ++ if (secondaryPoi.isEmpty() || (!level.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC)) { + brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); + return; + } ++ // Gale end - Lithium - skip secondary POI sensor if absent + // Purpur end - Option for Villager Clerics to farm Nether Wart + ResourceKey resourceKey = level.dimension(); + BlockPos blockPos = entity.blockPosition(); +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index 347affae3cc18e01474734d2da2699c9b7b17e26..5dbf28c15f828505d42dc31c1a2185e01a7542c1 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -404,6 +404,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.getBrain().tick(level, this); // Paper - EAR 2 + } + // Pufferfish end ++ else if (this.isLobotomized && shouldRestock()) restock(); // Leaf - Purpur - Lobotomize stuck villagers + if (this.assignProfessionWhenSpawned) { + this.assignProfessionWhenSpawned = false; + } +diff --git a/net/minecraft/world/entity/projectile/Projectile.java b/net/minecraft/world/entity/projectile/Projectile.java +index bd93ee97982038789114f17ee369208fc6413796..0b1263000f872431e44abfdd57134b6efe245641 100644 +--- a/net/minecraft/world/entity/projectile/Projectile.java ++++ b/net/minecraft/world/entity/projectile/Projectile.java +@@ -74,7 +74,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + int maxChunkLoadsPerProjectile = maxProjectileChunkLoadsConfig.perProjectile.max; + if (maxChunkLoadsPerProjectile >= 0 && this.chunksLoadedByProjectile >= maxChunkLoadsPerProjectile) { + if (maxProjectileChunkLoadsConfig.perProjectile.removeFromWorldAfterReachLimit) { +- this.discard(); ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Leaf - Purpur + } else if (maxProjectileChunkLoadsConfig.perProjectile.resetMovementAfterReachLimit) { + this.setDeltaMovement(0, this.getDeltaMovement().y, 0); + } diff --git a/Leaf-Server/minecraft-patches/features/0008-Purpur-Configurable-server-mod-name.patch b/Leaf-Server/minecraft-patches/features/0008-Purpur-Configurable-server-mod-name.patch new file mode 100644 index 00000000..d77e53ed --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0008-Purpur-Configurable-server-mod-name.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 17 Jul 2023 08:31:51 +0800 +Subject: [PATCH] Purpur: Configurable server mod name + +Original license: MIT +Original project: https://github.com/PurpurMC/Purpur + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 66418672e548a022980a403de912e05984137f37..919dc4dddea64f97161b5e0d417dc06875f8318c 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1855,7 +1855,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 14 Jan 2024 05:14:09 -0500 +Subject: [PATCH] Configurable server GUI name + + +diff --git a/net/minecraft/server/gui/MinecraftServerGui.java b/net/minecraft/server/gui/MinecraftServerGui.java +index 614c7d9f673c926562acc8fa3b3788623900db41..51f37d026382369ac0d53b0ce64495dafe5084af 100644 +--- a/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/net/minecraft/server/gui/MinecraftServerGui.java +@@ -51,7 +51,7 @@ public class MinecraftServerGui extends JComponent { + } catch (Exception var3) { + } + +- final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI ++ final JFrame jFrame = new JFrame(org.dreeam.leaf.config.modules.misc.ServerBrand.serverGUIName); // Purpur - Improve GUI // Leaf - Configurable server GUI name + final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server); + jFrame.setDefaultCloseOperation(2); + jFrame.add(minecraftServerGui); +@@ -59,7 +59,7 @@ public class MinecraftServerGui extends JComponent { + jFrame.setLocationRelativeTo(null); + jFrame.setVisible(true); + // Paper start - Improve ServerGUI +- jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI ++ jFrame.setName(org.dreeam.leaf.config.modules.misc.ServerBrand.serverGUIName); // Purpur - Improve GUI // Leaf - Configurable server GUI name + try { + jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); + } catch (java.io.IOException ignore) { +@@ -69,7 +69,7 @@ public class MinecraftServerGui extends JComponent { + @Override + public void windowClosing(WindowEvent event) { + if (!minecraftServerGui.isClosing.getAndSet(true)) { +- jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI ++ jFrame.setTitle(org.dreeam.leaf.config.modules.misc.ServerBrand.serverGUIName + " - shutting down!"); // Purpur - Improve GUI // Leaf - Configurable server GUI name + server.halt(true); + minecraftServerGui.runFinalizers(); + } diff --git a/Leaf-Server/minecraft-patches/features/0010-Remove-vanilla-username-check.patch b/Leaf-Server/minecraft-patches/features/0010-Remove-vanilla-username-check.patch new file mode 100644 index 00000000..4ebce894 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0010-Remove-vanilla-username-check.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 12 Oct 2022 14:36:58 -0400 +Subject: [PATCH] Remove vanilla username check + + +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 634933a6c98a0043cfe3ff4122dfc53e3c20a192..780d85f4afe221f8861b248457bfe6462f0b8a2a 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -183,7 +183,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + public void handleHello(ServerboundHelloPacket packet) { + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet"); + // Paper start - Validate usernames +- if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ++ if (!org.dreeam.leaf.config.modules.misc.RemoveVanillaUsernameCheck.enabled // Leaf - Remove Vanilla username check ++ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() + && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation + && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { + Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username"); +diff --git a/net/minecraft/server/players/GameProfileCache.java b/net/minecraft/server/players/GameProfileCache.java +index 6fb3712f33a84a3612752dcfd9e97d67066f610e..c01e998c97e1226e7e10984493d67612dfe7ae15 100644 +--- a/net/minecraft/server/players/GameProfileCache.java ++++ b/net/minecraft/server/players/GameProfileCache.java +@@ -77,7 +77,7 @@ public class GameProfileCache { + } + + private static Optional lookupGameProfile(GameProfileRepository profileRepo, String name) { +- if (!StringUtil.isValidPlayerName(name)) { ++ if (!StringUtil.isValidPlayerName(name, false)) { // Leaf start - Remove Vanilla username check - Directly return, skip unnecessary following logic + return createUnknownProfile(name); + } else { + final AtomicReference atomicReference = new AtomicReference<>(); +diff --git a/net/minecraft/util/StringUtil.java b/net/minecraft/util/StringUtil.java +index c3a99fe7b49858bc0ca9a7f800b0db40465f6901..43f62ce3d967f534cdc4e388233933a5ad4af762 100644 +--- a/net/minecraft/util/StringUtil.java ++++ b/net/minecraft/util/StringUtil.java +@@ -64,6 +64,13 @@ public class StringUtil { + } + + public static boolean isValidPlayerName(String playerName) { ++ // Leaf start - Remove Vanilla username check ++ return isValidPlayerName(playerName, org.dreeam.leaf.config.modules.misc.RemoveVanillaUsernameCheck.enabled); ++ } ++ ++ public static boolean isValidPlayerName(String playerName, boolean bypassCheck) { ++ if (bypassCheck) return playerName.length() <= 16; ++ // Leaf end- Remove Vanilla username check + return playerName.length() <= 16 && playerName.chars().filter(i -> i <= 32 || i >= 127).findAny().isEmpty(); + } + diff --git a/Leaf-Server/minecraft-patches/features/0011-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch b/Leaf-Server/minecraft-patches/features/0011-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch new file mode 100644 index 00000000..e560f310 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0011-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 12 Oct 2022 14:48:45 -0400 +Subject: [PATCH] Remove Spigot Check for Broken BungeeCord Configurations + + +diff --git a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index c166091d1b33c8f0ea57fb723e9d9b0c83bcedfb..cbd71a13ad64edbcb6d01fd024d53b3d8d163f5d 100644 +--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -172,7 +172,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + if (split.length == 4) { + this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class); + } +- } else if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) { ++ } else if (!org.dreeam.leaf.config.modules.misc.RemoveSpigotCheckBungee.enabled && (split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) { // Leaf - Remove Spigot check for broken BungeeCord configurations + Component message = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?"); + this.connection.send(new ClientboundLoginDisconnectPacket(message)); + this.connection.disconnect(message); diff --git a/Leaf-Server/minecraft-patches/features/0012-Remove-UseItemOnPacket-Too-Far-Check.patch b/Leaf-Server/minecraft-patches/features/0012-Remove-UseItemOnPacket-Too-Far-Check.patch new file mode 100644 index 00000000..14ca4a8c --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0012-Remove-UseItemOnPacket-Too-Far-Check.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Fri, 18 Nov 2022 23:26:16 -0500 +Subject: [PATCH] Remove UseItemOnPacket Too Far Check + +This Check is added in 1.17.x -> 1.18.x that updated by Mojang. +By removing this check, it gives ability for hackers to use some modules of hack clients. + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ec6276f3335fd963c8554dbc43adf5bdfe3d2412..162617ea984e54898f19c16ff8e7d759bddcc190 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1998,8 +1998,14 @@ public class ServerGamePacketListenerImpl + BlockPos blockPos = hitResult.getBlockPos(); + if (this.player.canInteractWithBlock(blockPos, 1.0)) { + Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos)); +- double d = 1.0000001; +- if (Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) { ++ // Leaf start - Remove UseItemOnPacket Too Far Check and make it configurable ++ //double d = 1.0000001; ++ if (org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance <= 0 ++ || (Math.abs(vec3.x()) < org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance ++ && Math.abs(vec3.y()) < org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance ++ && Math.abs(vec3.z()) < org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance) ++ ) { ++ // Leaf end - Remove UseItemOnPacket Too Far Check and make it configurable + Direction direction = hitResult.getDirection(); + this.player.resetLastActionTime(); + int maxY = this.player.level().getMaxY(); diff --git a/Leaf-Server/minecraft-patches/features/0013-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch b/Leaf-Server/minecraft-patches/features/0013-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch new file mode 100644 index 00000000..274bae56 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0013-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nostalgic853 +Date: Sun, 23 Oct 2022 23:21:45 +0800 +Subject: [PATCH] KeYi: Add an option for spigot item merging mechanism + +Original license: MIT +Original project: https://github.com/KeYiMC/KeYi + +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index 88f55f9ea0a98a07ba8277b4b43f6957e6f16283..0e21c644d62597cf3425c8717ab1e70c766e22f2 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -313,7 +313,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + ItemStack item = this.getItem(); + ItemStack item1 = itemEntity.getItem(); + if (Objects.equals(this.target, itemEntity.target) && areMergable(item, item1)) { +- if (true || item1.getCount() < item.getCount()) { // Spigot ++ if (org.dreeam.leaf.config.modules.gameplay.UseSpigotItemMergingMech.enabled || item1.getCount() < item.getCount()) { // Spigot // Leaf - KeYi - Configurable spigot item merging mechanism + merge(this, item, itemEntity, item1); + } else { + merge(itemEntity, item1, this, item); diff --git a/patches/server/0022-Carpet-Fixes-Optimized-getBiome-method.patch b/Leaf-Server/minecraft-patches/features/0014-Carpet-Fixes-Optimized-getBiome-method.patch similarity index 67% rename from patches/server/0022-Carpet-Fixes-Optimized-getBiome-method.patch rename to Leaf-Server/minecraft-patches/features/0014-Carpet-Fixes-Optimized-getBiome-method.patch index 74f440d4..72529152 100644 --- a/patches/server/0022-Carpet-Fixes-Optimized-getBiome-method.patch +++ b/Leaf-Server/minecraft-patches/features/0014-Carpet-Fixes-Optimized-getBiome-method.patch @@ -9,48 +9,33 @@ Original project: https://github.com/fxmorin/carpet-fixes Optimized the getBiome call to be 25% - 75% faster This is a fully vanilla optimization. -diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java -index 90f8360f547ce709fd13ee34f8e67d8bfa94b498..7750c0fe4416943ac82fc9682767e34fce6d8388 100644 ---- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java -+++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java +index 73962e79a0f3d892e3155443a1b84508b0f4042e..a48175a7ebb1788ace46395621ed78d910178a53 100644 +--- a/net/minecraft/world/level/biome/BiomeManager.java ++++ b/net/minecraft/world/level/biome/BiomeManager.java @@ -14,6 +14,7 @@ public class BiomeManager { private static final int ZOOM_MASK = 3; private final BiomeManager.NoiseBiomeSource noiseBiomeSource; private final long biomeZoomSeed; -+ private static final double maxOffset = 0.4500000001D; // KeYi ++ private static final double maxOffset = 0.4500000001D; // Leaf - Carpet-Fixes - Optimized getBiome method - public BiomeManager(BiomeManager.NoiseBiomeSource storage, long seed) { - this.noiseBiomeSource = storage; -@@ -29,39 +30,68 @@ public class BiomeManager { + public BiomeManager(BiomeManager.NoiseBiomeSource noiseBiomeSource, long biomeZoomSeed) { + this.noiseBiomeSource = noiseBiomeSource; +@@ -29,39 +30,67 @@ public class BiomeManager { } public Holder getBiome(BlockPos pos) { - int i = pos.getX() - 2; -- int j = pos.getY() - 2; -- int k = pos.getZ() - 2; -- int l = i >> 2; -- int m = j >> 2; -- int n = k >> 2; -- double d = (double)(i & 3) / 4.0; -- double e = (double)(j & 3) / 4.0; -- double f = (double)(k & 3) / 4.0; -- int o = 0; -- double g = Double.POSITIVE_INFINITY; -- -- for (int p = 0; p < 8; p++) { -- boolean bl = (p & 4) == 0; -- boolean bl2 = (p & 2) == 0; -- boolean bl3 = (p & 1) == 0; -- int q = bl ? l : l + 1; -- int r = bl2 ? m : m + 1; -- int s = bl3 ? n : n + 1; -- double h = bl ? d : d - 1.0; -- double t = bl2 ? e : e - 1.0; -- double u = bl3 ? f : f - 1.0; -- double v = getFiddledDistance(this.biomeZoomSeed, q, r, s, h, t, u); -- if (g > v) { -- o = p; -- g = v; +- int i1 = pos.getY() - 2; +- int i2 = pos.getZ() - 2; +- int i3 = i >> 2; +- int i4 = i1 >> 2; +- int i5 = i2 >> 2; +- double d = (i & 3) / 4.0; +- double d1 = (i1 & 3) / 4.0; +- double d2 = (i2 & 3) / 4.0; +- int i6 = 0; +- double d3 = Double.POSITIVE_INFINITY; + // Leaf start - Carpet-Fixes - Optimized getBiome method + int xMinus2 = pos.getX() - 2; + int yMinus2 = pos.getY() - 2; @@ -70,7 +55,21 @@ index 90f8360f547ce709fd13ee34f8e67d8bfa94b498..7750c0fe4416943ac82fc9682767e34f + double quartXX = everyOtherQuad ? quartX : quartX - 1.0; //[-1.0,-0.75,-0.5,-0.25,0.0,0.25,0.5,0.75] + double quartYY = everyOtherPair ? quartY : quartY - 1.0; + double quartZZ = everyOther ? quartZ : quartZ - 1.0; -+ + +- for (int i7 = 0; i7 < 8; i7++) { +- boolean flag = (i7 & 4) == 0; +- boolean flag1 = (i7 & 2) == 0; +- boolean flag2 = (i7 & 1) == 0; +- int i8 = flag ? i3 : i3 + 1; +- int i9 = flag1 ? i4 : i4 + 1; +- int i10 = flag2 ? i5 : i5 + 1; +- double d4 = flag ? d : d - 1.0; +- double d5 = flag1 ? d1 : d1 - 1.0; +- double d6 = flag2 ? d2 : d2 - 1.0; +- double fiddledDistance = getFiddledDistance(this.biomeZoomSeed, i8, i9, i10, d4, d5, d6); +- if (d3 > fiddledDistance) { +- i6 = i7; +- d3 = fiddledDistance; + //This code block is new + double maxQuartYY = 0.0, maxQuartZZ = 0.0; + if (biomeX != 0) { @@ -80,11 +79,6 @@ index 90f8360f547ce709fd13ee34f8e67d8bfa94b498..7750c0fe4416943ac82fc9682767e34f + if (smallestDist < maxQuartXX + maxQuartYY + maxQuartZZ) continue; } - } - -- int w = (o & 4) == 0 ? l : l + 1; -- int x = (o & 2) == 0 ? m : m + 1; -- int y = (o & 1) == 0 ? n : n + 1; -- return this.noiseBiomeSource.getNoiseBiome(w, x, y); + int xx = everyOtherQuad ? x : x + 1; + int yy = everyOtherPair ? y : y + 1; + int zz = everyOther ? z : z + 1; @@ -106,16 +100,20 @@ index 90f8360f547ce709fd13ee34f8e67d8bfa94b498..7750c0fe4416943ac82fc9682767e34f + seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); + double offsetZ = getFiddle(seed); + double biomeDist = sqrX + sqrY + Mth.square(quartZZ + offsetZ); -+ + +- int i7x = (i6 & 4) == 0 ? i3 : i3 + 1; +- int i11 = (i6 & 2) == 0 ? i4 : i4 + 1; +- int i12 = (i6 & 1) == 0 ? i5 : i5 + 1; +- return this.noiseBiomeSource.getNoiseBiome(i7x, i11, i12); + if (smallestDist > biomeDist) { + smallestX = biomeX; + smallestDist = biomeDist; + } + } + return this.noiseBiomeSource.getNoiseBiome( -+ (smallestX & 4) == 0 ? x : x + 1, -+ (smallestX & 2) == 0 ? y : y + 1, -+ (smallestX & 1) == 0 ? z : z + 1 ++ (smallestX & 4) == 0 ? x : x + 1, ++ (smallestX & 2) == 0 ? y : y + 1, ++ (smallestX & 1) == 0 ? z : z + 1 + ); + // Leaf end - Carpet-Fixes - Optimized getBiome method } diff --git a/patches/server/0023-Carpet-Fixes-Use-optimized-RecipeManager.patch b/Leaf-Server/minecraft-patches/features/0015-Carpet-Fixes-Use-optimized-RecipeManager.patch similarity index 57% rename from patches/server/0023-Carpet-Fixes-Use-optimized-RecipeManager.patch rename to Leaf-Server/minecraft-patches/features/0015-Carpet-Fixes-Use-optimized-RecipeManager.patch index e318cdd1..300a4161 100644 --- a/patches/server/0023-Carpet-Fixes-Use-optimized-RecipeManager.patch +++ b/Leaf-Server/minecraft-patches/features/0015-Carpet-Fixes-Use-optimized-RecipeManager.patch @@ -14,30 +14,30 @@ Optimized the RecipeManager getFirstMatch call to be up to 3x faster This is a fully vanilla optimization. Improves: [Blast]Furnace/Campfire/Smoker/Stonecutter/Crafting/Sheep Color Choosing This was mostly made for the auto crafting table, since the performance boost is much more visible while using that mod -diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -index f6dd363ececf967d282f5ba713013085da1ddf37..1e05a89a4e4e3a5d2fa9f7dc72fd89a9e0d93468 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -+++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -@@ -195,7 +195,7 @@ public class RecipeManager extends SimplePreparableReloadListener imp +diff --git a/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java +index aefaac550b58be479cc282f52dea91d4b1e530f6..400d11b0a7266cee642546aa190553e60ad0723b 100644 +--- a/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/net/minecraft/world/item/crafting/RecipeManager.java +@@ -167,7 +167,7 @@ public class RecipeManager extends SimplePreparableReloadListener imp - public > Optional> getRecipeFor(RecipeType type, I input, Level world) { + public > Optional> getRecipeFor(RecipeType recipeType, I input, Level level) { // CraftBukkit start -- List> list = this.recipes.getRecipesFor(type, input, world).toList(); -+ List> list = this.recipes.getRecipesForList(type, input, world); // Leaf - Carpet-Fixes - Remove streams to be faster +- List> list = this.recipes.getRecipesFor(recipeType, input, level).toList(); ++ List> list = this.recipes.getRecipesForList(recipeType, input, level); // Leaf - Carpet-Fixes - Remove streams to be faster return (list.isEmpty()) ? Optional.empty() : Optional.of(list.getLast()); // CraftBukkit - SPIGOT-4638: last recipe gets priority // CraftBukkit end } -diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeMap.java b/src/main/java/net/minecraft/world/item/crafting/RecipeMap.java -index c4067fbf827fed882772962a0e4b3ead0d642e62..c62ecec9bbc40f66b84d384bacf39d6d09e10a61 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/RecipeMap.java -+++ b/src/main/java/net/minecraft/world/item/crafting/RecipeMap.java -@@ -105,4 +105,24 @@ public class RecipeMap { - return recipeholder.value().matches(input, world); - }); +diff --git a/net/minecraft/world/item/crafting/RecipeMap.java b/net/minecraft/world/item/crafting/RecipeMap.java +index 098753ddd215b6ef5915fac71d8c4f0b19cf4142..c888daa9a8cdc8168cca173acb69b3c3919c5bda 100644 +--- a/net/minecraft/world/item/crafting/RecipeMap.java ++++ b/net/minecraft/world/item/crafting/RecipeMap.java +@@ -75,4 +75,24 @@ public class RecipeMap { + public > Stream> getRecipesFor(RecipeType type, I input, Level level) { + return input.isEmpty() ? Stream.empty() : this.byType(type).stream().filter(recipeHolder -> recipeHolder.value().matches(input, level)); } + + // Leaf start - Carpet-Fixes - Remove streams to be faster -+ public > java.util.List> getRecipesForList(RecipeType type, I input, Level world) { ++ public > java.util.List> getRecipesForList(RecipeType type, I input, Level level) { + java.util.List> list; + + if (input.isEmpty()) { @@ -47,7 +47,7 @@ index c4067fbf827fed882772962a0e4b3ead0d642e62..c62ecec9bbc40f66b84d384bacf39d6d + } + + for (RecipeHolder recipeholder : this.byType(type)) { -+ if (recipeholder.value().matches(input, world)) { ++ if (recipeholder.value().matches(input, level)) { + list.add(recipeholder); + } + } diff --git a/Leaf-Server/minecraft-patches/features/0016-Akarin-Save-Json-list-asynchronously.patch b/Leaf-Server/minecraft-patches/features/0016-Akarin-Save-Json-list-asynchronously.patch new file mode 100644 index 00000000..8d1114e1 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0016-Akarin-Save-Json-list-asynchronously.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=E3=84=97=E3=84=A0=CB=8B=20=E3=84=91=E3=84=A7=CB=8A?= + +Date: Thu, 5 Jan 2023 09:08:17 +0800 +Subject: [PATCH] Akarin: Save Json list asynchronously + +Original license: GPL v3 +Original project: https://github.com/Akarin-project/Akarin + +diff --git a/net/minecraft/server/players/StoredUserList.java b/net/minecraft/server/players/StoredUserList.java +index 39483f7b453d6faedeccc1ab1eda76669395ea5a..7f2031559fe84e9b57fb4c3fb55cee022b442ebb 100644 +--- a/net/minecraft/server/players/StoredUserList.java ++++ b/net/minecraft/server/players/StoredUserList.java +@@ -97,13 +97,23 @@ public abstract class StoredUserList> { + } + + public void save() throws IOException { ++ Runnable saveTask = () -> {// Leaf - Akarin - Save json list async + this.removeExpired(); // Paper - remove expired values before saving + JsonArray jsonArray = new JsonArray(); + this.map.values().stream().map(storedEntry -> Util.make(new JsonObject(), storedEntry::serialize)).forEach(jsonArray::add); + ++ try { // Leaf - Akarin - Save json list async + try (BufferedWriter writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) { + GSON.toJson(jsonArray, GSON.newJsonWriter(writer)); + } ++ ++ // Leaf start - Akarin - Save json list async ++ } catch (Exception e) { ++ StoredUserList.LOGGER.warn("Failed to async save {}", this.file, e); ++ } ++ }; ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(saveTask); ++ // Leaf end - Akarin - Save json list async + } + + public void load() throws IOException { diff --git a/Leaf-Server/minecraft-patches/features/0017-Slice-Smooth-Teleports.patch b/Leaf-Server/minecraft-patches/features/0017-Slice-Smooth-Teleports.patch new file mode 100644 index 00000000..dba3de7d --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0017-Slice-Smooth-Teleports.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sat, 13 Aug 2022 08:58:14 -0500 +Subject: [PATCH] Slice: Smooth Teleports + +Original license: MIT +Original project: https://github.com/Cryptite/Slice + +Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> + +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index b7829a91a7ef79706ec6d90b8b2673fd369b9931..c798869665397de5b435e992873b566f766c2ff9 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -396,6 +396,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + private boolean tpsBar = false; // Purpur - Implement TPSBar + private boolean compassBar = false; // Purpur - Add compass command + private boolean ramBar = false; // Purpur - Implement rambar commands ++ public boolean smoothWorldTeleport; // Slice + + // Paper start - rewrite chunk system + private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index b4f2b794ca0c6e04da0355e02c19493c892ebccf..9c323dbaff302ca37850af70ee42c2588f85b20d 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -798,11 +798,11 @@ public abstract class PlayerList { + byte b = (byte)(keepInventory ? 1 : 0); + ServerLevel serverLevel = serverPlayer.serverLevel(); + LevelData levelData = serverLevel.getLevelData(); +- serverPlayer.connection.send(new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(serverLevel), b)); ++ if (!serverPlayer.smoothWorldTeleport || !isSameLogicalHeight((ServerLevel) fromWorld, level)) serverPlayer.connection.send(new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(serverLevel), b)); // Leaf - Slice + // serverPlayer.connection.teleport(serverPlayer.getX(), serverPlayer.getY(), serverPlayer.getZ(), serverPlayer.getYRot(), serverPlayer.getXRot()); + serverPlayer.connection.send(new ClientboundSetChunkCacheRadiusPacket(serverLevel.spigotConfig.viewDistance)); // Spigot + serverPlayer.connection.send(new ClientboundSetSimulationDistancePacket(serverLevel.spigotConfig.simulationDistance)); // Spigot +- serverPlayer.connection.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(serverPlayer.position(), serverLevel.getWorld(), serverPlayer.getYRot(), serverPlayer.getXRot())); // CraftBukkit ++ if (!serverPlayer.smoothWorldTeleport || !isSameLogicalHeight((ServerLevel) fromWorld, level)) serverPlayer.connection.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(serverPlayer.position(), serverLevel.getWorld(), serverPlayer.getYRot(), serverPlayer.getXRot())); // CraftBukkit // Leaf - Slice + serverPlayer.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle())); + serverPlayer.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked())); + serverPlayer.connection +@@ -876,6 +876,8 @@ public abstract class PlayerList { + return serverPlayer; + } + ++ private boolean isSameLogicalHeight(ServerLevel fromLevel, ServerLevel toLevel) { return fromLevel.getLogicalHeight() == toLevel.getLogicalHeight(); } // Leaf - Slice - Check world height before smooth teleport ++ + public void sendActivePlayerEffects(ServerPlayer player) { + this.sendActiveEffects(player, player.connection); + } diff --git a/patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch b/Leaf-Server/minecraft-patches/features/0018-Parchment-Make-FixLight-use-action-bar.patch similarity index 88% rename from patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch rename to Leaf-Server/minecraft-patches/features/0018-Parchment-Make-FixLight-use-action-bar.patch index d395d8f2..a4f961a4 100644 --- a/patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch +++ b/Leaf-Server/minecraft-patches/features/0018-Parchment-Make-FixLight-use-action-bar.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Parchment: Make FixLight use action bar Original license: GPLv3 Original project: https://github.com/ProjectEdenGG/Parchment -diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java index 85950a1aa732ab8c01ad28bec9e0de140e1a172e..8e8d1a38290c2dc3f88deda64d050e89273a5b89 100644 ---- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +--- a/io/papermc/paper/command/subcommands/FixLightCommand.java ++++ b/io/papermc/paper/command/subcommands/FixLightCommand.java @@ -95,17 +95,20 @@ public final class FixLightCommand implements PaperSubcommand { ((StarLightLightingProvider)lightengine).starlight$serverRelightChunks(chunks, (final ChunkPos chunkPos) -> { diff --git a/Leaf-Server/minecraft-patches/features/0019-Leaves-Protocol-Core.patch b/Leaf-Server/minecraft-patches/features/0019-Leaves-Protocol-Core.patch new file mode 100644 index 00000000..668eeab0 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0019-Leaves-Protocol-Core.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Tue, 26 Sep 2023 19:00:41 +0800 +Subject: [PATCH] Leaves: Protocol Core + +TODO: Check whether Leaves's Return-nether-portal-fix.patch improves performance +and change store way to sql maybe? + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +Commit: 41476d86922416c45f703df2871890831fc42bb5 + +diff --git a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +index fb263fa1f30a7dfcb7ec2656abfb38e5fe88eac9..96ec0a0133ec244a5eb79dfcb34e7f9de22ea0f4 100644 +--- a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java ++++ b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +@@ -40,13 +40,23 @@ public interface CustomPacketPayload { + + @Override + public void encode(B buffer, CustomPacketPayload value) { ++ // Leaves start - protocol core ++ if (value instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload leavesCustomPayload) { ++ buffer.writeResourceLocation(leavesCustomPayload.id()); ++ leavesCustomPayload.write(buffer); ++ return; ++ } ++ // Leaves end - protocol core + this.writeCap(buffer, value.type(), value); + } + + @Override + public CustomPacketPayload decode(B buffer) { + ResourceLocation resourceLocation = buffer.readResourceLocation(); +- return (CustomPacketPayload)this.findCodec(resourceLocation).decode(buffer); ++ // Leaves start - protocol core ++ var leavesCustomPayload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, buffer); ++ return java.util.Objects.requireNonNullElseGet(leavesCustomPayload, () -> this.findCodec(resourceLocation).decode(buffer)); ++ // Leaves end - protocol core + } + }; + } +diff --git a/net/minecraft/resources/ResourceLocation.java b/net/minecraft/resources/ResourceLocation.java +index ea8cfa76093c70a44d065c1f80adaa9127fe4e07..7435e2c3f0defe98cbaa488219974887ee572c57 100644 +--- a/net/minecraft/resources/ResourceLocation.java ++++ b/net/minecraft/resources/ResourceLocation.java +@@ -36,7 +36,7 @@ public final class ResourceLocation implements Comparable { + private final String namespace; + private final String path; + +- private ResourceLocation(String namespace, String path) { ++ public ResourceLocation(String namespace, String path) { // Leaves - private -> public + assert isValidNamespace(namespace); + + assert isValidPath(path); +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 919dc4dddea64f97161b5e0d417dc06875f8318c..4f307671699b0dfbdab61257e28c5a90bcf2e049 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1753,6 +1753,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop leavesPayload) { ++ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(player, leavesPayload); ++ } ++ // Leaves end - protocol + // CraftBukkit start + // Paper start - Brand support + if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) { +@@ -169,6 +174,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + String channels = payload.toString(com.google.common.base.Charsets.UTF_8); + for (String channel : channels.split("\0")) { + this.getCraftPlayer().addChannel(channel); ++ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, player); // Leaves - protocol + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex); +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 9c323dbaff302ca37850af70ee42c2588f85b20d..e8d458ab8094434d4af3441f6ade2bb47e30868f 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -341,6 +341,8 @@ public abstract class PlayerList { + + player.didPlayerJoinEvent = true; // Gale - EMC - do not process chat/commands before player has joined + ++ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol ++ + final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); + + if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure +@@ -518,6 +520,7 @@ public abstract class PlayerList { + return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName()))); + } + public net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) { ++ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(player); // Leaves - protocol + // Paper end - Fix kick event leave message not being sent + org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar + ServerLevel serverLevel = player.serverLevel(); diff --git a/Leaf-Server/minecraft-patches/features/0020-Leaves-Jade-Protocol.patch b/Leaf-Server/minecraft-patches/features/0020-Leaves-Jade-Protocol.patch new file mode 100644 index 00000000..bcc2b404 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0020-Leaves-Jade-Protocol.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Sat, 3 Dec 2022 08:57:15 +0800 +Subject: [PATCH] Leaves: Jade Protocol + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +This patch is Powered by Jade (https://github.com/Snownee/Jade) + +diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +index a24ed1747fb8836927ac41b822dc666862701516..d840577023d42dc986e2b811382dfc433083ffb3 100644 +--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java ++++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +@@ -59,7 +59,7 @@ public class Armadillo extends Animal { + public final AnimationState rollOutAnimationState = new AnimationState(); + public final AnimationState rollUpAnimationState = new AnimationState(); + public final AnimationState peekAnimationState = new AnimationState(); +- private int scuteTime; ++ public int scuteTime; // Leaves - private -> public + private boolean peekReceivedClient = false; + + public Armadillo(EntityType entityType, Level level) { +diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java +index 0ca35514a920dddf230d749bc1a5fe15f1c7940a..669f7d6e5f481fb209e800c8e4acc52cf0c6dfce 100644 +--- a/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -286,7 +286,7 @@ public class Tadpole extends AbstractFish { + } + } + +- private int getTicksLeftUntilAdult() { ++ public int getTicksLeftUntilAdult() { // Leaves - private -> public + return Math.max(0, ticksToBeFrog - this.age); + } + +diff --git a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +index 3e3c72a4a52e72c15e0d3288f805d7887dcac351..38cd62d1a3c17913fdadf02e3c3871dac7619f82 100644 +--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java ++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +@@ -67,7 +67,7 @@ public class TrialSpawnerData { + ); + public final Set detectedPlayers = new HashSet<>(); + public final Set currentMobs = new HashSet<>(); +- protected long cooldownEndsAt; ++ public long cooldownEndsAt; // Leaves - protected -> public + protected long nextMobSpawnsAt; + protected int totalMobsSpawned; + public Optional nextSpawnData; +diff --git a/net/minecraft/world/level/storage/loot/LootPool.java b/net/minecraft/world/level/storage/loot/LootPool.java +index 29ad43245a310756c4227acd7532e905f7f8b8ee..ad422817593449b8e914628b51d760e732e2d50c 100644 +--- a/net/minecraft/world/level/storage/loot/LootPool.java ++++ b/net/minecraft/world/level/storage/loot/LootPool.java +@@ -36,7 +36,7 @@ public class LootPool { + ) + .apply(instance, LootPool::new) + ); +- private final List entries; ++ public final List entries; // Leaves - private -> public + private final List conditions; + private final Predicate compositeCondition; + private final List functions; +diff --git a/net/minecraft/world/level/storage/loot/LootTable.java b/net/minecraft/world/level/storage/loot/LootTable.java +index f95d0f2da3d958519d28278079555c800aad02f8..bebffd07047e41c53b9e4f1ad5917680b8e8c796 100644 +--- a/net/minecraft/world/level/storage/loot/LootTable.java ++++ b/net/minecraft/world/level/storage/loot/LootTable.java +@@ -45,7 +45,7 @@ public class LootTable { + public static final Codec> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, DIRECT_CODEC); + private final ContextKeySet paramSet; + private final Optional randomSequence; +- private final List pools; ++ public final List pools; // Leaves - private -> public + private final List functions; + private final BiFunction compositeFunction; + public org.bukkit.craftbukkit.CraftLootTable craftLootTable; // CraftBukkit +diff --git a/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java b/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java +index 8e91ddc6c0e492d165ad8322b4a3d5c3bad5409c..6e420bfb3c223b094157bdfec7dad20d8eab4968 100644 +--- a/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java ++++ b/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java +@@ -9,7 +9,7 @@ import net.minecraft.world.level.storage.loot.ValidationContext; + import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; + + public abstract class CompositeEntryBase extends LootPoolEntryContainer { +- protected final List children; ++ public final List children; // Leaves - private -> public + private final ComposableEntryContainer composedChildren; + + protected CompositeEntryBase(List children, List conditions) { +diff --git a/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java b/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +index e0e933245e038b7229eeddbda272b081161ab603..c5e3834fa970ac909cefea43420378394153d781 100644 +--- a/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java ++++ b/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +@@ -13,7 +13,7 @@ import net.minecraft.world.level.storage.loot.predicates.ConditionUserBuilder; + import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; + + public abstract class LootPoolEntryContainer implements ComposableEntryContainer { +- protected final List conditions; ++ public final List conditions; // Leaves - private -> public + private final Predicate compositeCondition; + + protected LootPoolEntryContainer(List conditions) { +diff --git a/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java b/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java +index d5e697a0cf6091a7f37c68e3c2a52851535735b1..a8a5a872a8647896e80f91cb5a89adead4005cf7 100644 +--- a/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java ++++ b/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java +@@ -25,7 +25,7 @@ public class NestedLootTable extends LootPoolSingletonContainer { + .and(singletonFields(instance)) + .apply(instance, NestedLootTable::new) + ); +- private final Either, LootTable> contents; ++ public final Either, LootTable> contents; // Leaves - private -> public + + private NestedLootTable( + Either, LootTable> contents, int weight, int quality, List conditions, List functions +diff --git a/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java b/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java +index 7134c54984a12949cd6a2e8dc35c2e1c0431e524..52f36fbb9bfcad81004e531efab85e9b87d3284d 100644 +--- a/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java ++++ b/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java +@@ -11,7 +11,7 @@ import net.minecraft.world.level.storage.loot.LootContext; + import net.minecraft.world.level.storage.loot.ValidationContext; + + public abstract class CompositeLootItemCondition implements LootItemCondition { +- protected final List terms; ++ public final List terms; // Leaves - private -> public + private final Predicate composedPredicate; + + protected CompositeLootItemCondition(List terms, Predicate composedPredicate) { diff --git a/Leaf-Server/minecraft-patches/features/0021-Leaves-Xaero-Map-Protocol.patch b/Leaf-Server/minecraft-patches/features/0021-Leaves-Xaero-Map-Protocol.patch new file mode 100644 index 00000000..e60d191d --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0021-Leaves-Xaero-Map-Protocol.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Fri, 27 Jan 2023 09:42:57 +0800 +Subject: [PATCH] Leaves: Xaero Map Protocol + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +This patch is Powered by Xaero Map + +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index e8d458ab8094434d4af3441f6ade2bb47e30868f..f4ac7a383ea9732a915d100ec9a83e225a1a383d 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -1216,6 +1216,7 @@ public abstract class PlayerList { + player.connection.send(new ClientboundInitializeBorderPacket(worldBorder)); + player.connection.send(new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); + player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle())); ++ org.leavesmc.leaves.protocol.XaeroMapProtocol.onSendWorldInfo(player); // Leaves - xaero map protocol + if (level.isRaining()) { + // CraftBukkit start - handle player weather + // player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F)); diff --git a/Leaf-Server/minecraft-patches/features/0022-Leaves-Syncmatica-Protocol.patch b/Leaf-Server/minecraft-patches/features/0022-Leaves-Syncmatica-Protocol.patch new file mode 100644 index 00000000..a9c2ed0f --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0022-Leaves-Syncmatica-Protocol.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 18 May 2023 16:16:56 +0800 +Subject: [PATCH] Leaves: Syncmatica Protocol + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +This patch is Powered by Syncmatica (https://github.com/End-Tech/syncmatica) + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 162617ea984e54898f19c16ff8e7d759bddcc190..7e656b426aafc2581e13e8a1ae9e304530f3d05b 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -324,6 +324,7 @@ public class ServerGamePacketListenerImpl + player.getTextFilter().join(); + this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile); + this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat ++ this.exchangeTarget = new org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget(this); // Leaves - Syncmatica Protocol + } + + // Purpur start - AFK API +@@ -340,6 +341,8 @@ public class ServerGamePacketListenerImpl + ); + // Purpur end - AFK API + ++ public final org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget exchangeTarget; // Leaves - Syncmatica Protocol ++ + @Override + public void tick() { + if (this.ackBlockChangesUpTo > -1) { diff --git a/Leaf-Server/minecraft-patches/features/0023-Leaves-Replay-Mod-API.patch b/Leaf-Server/minecraft-patches/features/0023-Leaves-Replay-Mod-API.patch new file mode 100644 index 00000000..9a0a4ffd --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0023-Leaves-Replay-Mod-API.patch @@ -0,0 +1,423 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 3 Aug 2023 20:36:38 +0800 +Subject: [PATCH] Leaves: Replay Mod API + +Co-authored-by: alazeprt + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +This patch is Powered by ReplayMod(https://github.com/ReplayMod) + +diff --git a/net/minecraft/commands/CommandSourceStack.java b/net/minecraft/commands/CommandSourceStack.java +index 59c70c567051bc7dba0d308387352d1b15f3c842..6c13bf624bbb1af62f879ea08b72346a9932d75e 100644 +--- a/net/minecraft/commands/CommandSourceStack.java ++++ b/net/minecraft/commands/CommandSourceStack.java +@@ -629,6 +629,13 @@ public class CommandSourceStack implements ExecutionCommandSource sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().getName()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players + } + ++ // Leaves start - skip photographer ++ @Override ++ public Collection getOnlineRealPlayerNames() { ++ return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> !(serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer) && sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().getName()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players ++ } ++ // Leaves end - skip photographer ++ + @Override + public Collection getAllTeams() { + return this.server.getScoreboard().getTeamNames(); +diff --git a/net/minecraft/commands/SharedSuggestionProvider.java b/net/minecraft/commands/SharedSuggestionProvider.java +index a2f13a86c635acef24ded974c96a400e1439011d..78e7948dfc99dda55455e964c3356b6c5002869c 100644 +--- a/net/minecraft/commands/SharedSuggestionProvider.java ++++ b/net/minecraft/commands/SharedSuggestionProvider.java +@@ -29,6 +29,8 @@ public interface SharedSuggestionProvider { + + Collection getOnlinePlayerNames(); + ++ Collection getOnlineRealPlayerNames(); // Leaves - skip photographer ++ + default Collection getCustomTabSugggestions() { + return this.getOnlinePlayerNames(); + } +diff --git a/net/minecraft/commands/arguments/EntityArgument.java b/net/minecraft/commands/arguments/EntityArgument.java +index 0a01df6ebd14afe79bc76364cb1df5e0c5c08074..7eea7e3345b889b885e9a118bb23fa08bc237150 100644 +--- a/net/minecraft/commands/arguments/EntityArgument.java ++++ b/net/minecraft/commands/arguments/EntityArgument.java +@@ -149,7 +149,7 @@ public class EntityArgument implements ArgumentType { + return entitySelectorParser.fillSuggestions( + builder, + offsetBuilder -> { +- Collection onlinePlayerNames = sharedSuggestionProvider.getOnlinePlayerNames(); ++ Collection onlinePlayerNames = sharedSuggestionProvider.getOnlineRealPlayerNames(); // Leaves - skip photographer + Iterable iterable = (Iterable)(this.playersOnly + ? onlinePlayerNames + : Iterables.concat(onlinePlayerNames, sharedSuggestionProvider.getSelectedEntities())); +diff --git a/net/minecraft/commands/arguments/selector/EntitySelector.java b/net/minecraft/commands/arguments/selector/EntitySelector.java +index b305ba9bab617bf4e52d0e6ddf160bacc5751a94..b8215b71971d16705bc11f19343823acf9970a3a 100644 +--- a/net/minecraft/commands/arguments/selector/EntitySelector.java ++++ b/net/minecraft/commands/arguments/selector/EntitySelector.java +@@ -128,11 +128,12 @@ public class EntitySelector { + return this.findPlayers(source); + } else if (this.playerName != null) { + ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName); ++ playerByName = playerByName instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : playerByName; // Leaves - skip photographer + return playerByName == null ? List.of() : List.of(playerByName); + } else if (this.entityUUID != null) { + for (ServerLevel serverLevel : source.getServer().getAllLevels()) { + Entity entity = serverLevel.getEntity(this.entityUUID); +- if (entity != null) { ++ if (entity != null && !(entity instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // Leaves - skip photographer + if (entity.getType().isEnabled(source.enabledFeatures())) { + return List.of(entity); + } +@@ -146,7 +147,7 @@ public class EntitySelector { + AABB absoluteAabb = this.getAbsoluteAabb(vec3); + if (this.currentEntity) { + Predicate predicate = this.getPredicate(vec3, absoluteAabb, null); +- return source.getEntity() != null && predicate.test(source.getEntity()) ? List.of(source.getEntity()) : List.of(); ++ return source.getEntity() != null && !(source.getEntity() instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(source.getEntity()) ? List.of(source.getEntity()) : List.of(); // Leaves - skip photographer + } else { + Predicate predicate = this.getPredicate(vec3, absoluteAabb, source.enabledFeatures()); + List list = new ObjectArrayList<>(); +@@ -157,6 +158,7 @@ public class EntitySelector { + this.addEntities(list, serverLevel1, absoluteAabb, predicate); + } + } ++ list.removeIf(entity -> entity instanceof org.leavesmc.leaves.replay.ServerPhotographer); // Leaves - skip photographer + + return this.sortAndLimit(vec3, list); + } +@@ -192,27 +194,29 @@ public class EntitySelector { + this.checkPermissions(source); + if (this.playerName != null) { + ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName); ++ playerByName = playerByName instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : playerByName; // Leaves - skip photographer + return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector + } else if (this.entityUUID != null) { + ServerPlayer playerByName = source.getServer().getPlayerList().getPlayer(this.entityUUID); ++ playerByName = playerByName instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : playerByName; // Leaves - skip photographer + return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector + } else { + Vec3 vec3 = this.position.apply(source.getPosition()); + AABB absoluteAabb = this.getAbsoluteAabb(vec3); + Predicate predicate = this.getPredicate(vec3, absoluteAabb, null); + if (this.currentEntity) { +- return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) && canSee(source, serverPlayer) ? List.of(serverPlayer) : List.of(); // Purpur - Hide hidden players from entity selector ++ return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) && !(serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer) && canSee(source, serverPlayer) ? List.of(serverPlayer) : List.of(); // Purpur - Hide hidden players from entity selector // Leaves - skip photographer + } else { + int resultLimit = this.getResultLimit(); + List players; + if (this.isWorldLimited()) { + players = source.getLevel().getPlayers(predicate, resultLimit); +- players.removeIf(entityplayer3 -> !canSee(source, entityplayer3)); // Purpur - Hide hidden players from entity selector ++ players.removeIf(entityplayer3 -> entityplayer3 instanceof org.leavesmc.leaves.replay.ServerPhotographer || !canSee(source, entityplayer3)); // Purpur - Hide hidden players from entity selector // Leaves - skip photographer + } else { + players = new ObjectArrayList<>(); + + for (ServerPlayer serverPlayer1 : source.getServer().getPlayerList().getPlayers()) { +- if (predicate.test(serverPlayer1) && canSee(source, serverPlayer1)) { // Purpur - Hide hidden players from entity selector ++ if (!(serverPlayer1 instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(serverPlayer1) && canSee(source, serverPlayer1)) { // Purpur - Hide hidden players from entity selector // Leaves - skip photographer + players.add(serverPlayer1); + if (players.size() >= resultLimit) { + return players; +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 4f307671699b0dfbdab61257e28c5a90bcf2e049..449cfd4f6282f28d7470403c0063874b906a6003 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1656,7 +1656,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players = this.playerList.getPlayers(); ++ List players = this.playerList.realPlayers; // Leaves - only real player + int maxPlayers = this.getMaxPlayers(); + if (this.hidesOnlinePlayers()) { + return new ServerStatus.Players(maxPlayers, players.size(), List.of()); +diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java +index 792ba93b531e9586e26aafa00830022a8996fc04..e4ea26ae84efde7ce54e08a246a6ea2ae2a17151 100644 +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -168,6 +168,11 @@ public class PlayerAdvancements { + } + + public boolean award(AdvancementHolder advancement, String criterionKey) { ++ // Leaves start - photographer can't get advancement ++ if (player instanceof org.leavesmc.leaves.replay.ServerPhotographer) { ++ return false; ++ } ++ // Leaves end - photographer can't get advancement + boolean flag = false; + AdvancementProgress orStartProgress = this.getOrStartProgress(advancement); + boolean isDone = orStartProgress.isDone(); +diff --git a/net/minecraft/server/commands/OpCommand.java b/net/minecraft/server/commands/OpCommand.java +index 5c0a04db38821dbb0cba2bb6f0787f113d167efd..8a071166262fbb7d24735fec394cb19d4dd98096 100644 +--- a/net/minecraft/server/commands/OpCommand.java ++++ b/net/minecraft/server/commands/OpCommand.java +@@ -25,7 +25,7 @@ public class OpCommand { + (context, builder) -> { + PlayerList playerList = context.getSource().getServer().getPlayerList(); + return SharedSuggestionProvider.suggest( +- playerList.getPlayers() ++ playerList.realPlayers // Leaves - skip + .stream() + .filter(player -> !playerList.isOp(player.getGameProfile())) + .map(player -> player.getGameProfile().getName()), +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index a13aa7b896a998975d2ee14eafb86a85db988e69..bf364e3fad2823400ec671bc632ba50751a3f473 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -216,6 +216,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent + private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur - Ridables ++ final List realPlayers; // Leaves - skip + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately +@@ -700,6 +701,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - rewrite chunk system + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle ++ this.realPlayers = Lists.newArrayList(); // Leaves - skip + } + + // Paper start +@@ -2669,6 +2671,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.add(serverPlayer); ++ // Leaves start - skip ++ if (!(serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { ++ ServerLevel.this.realPlayers.add(serverPlayer); ++ } ++ // Leaves end - skip + ServerLevel.this.updateSleepingPlayerList(); + } + +@@ -2739,6 +2746,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ServerLevel.this.getChunkSource().removeEntity(entity); + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.remove(serverPlayer); ++ // Leaves start - skip ++ if (!(serverPlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { ++ ServerLevel.this.realPlayers.remove(serverPlayer); ++ } ++ // Leaves end - skip + ServerLevel.this.updateSleepingPlayerList(); + } + +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index c798869665397de5b435e992873b566f766c2ff9..08578d7398658d226318a28324a1eae0d4453add 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -195,7 +195,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + ); + public ServerGamePacketListenerImpl connection; + public final MinecraftServer server; +- public final ServerPlayerGameMode gameMode; ++ public ServerPlayerGameMode gameMode; // Leaves - final -> null + private final PlayerAdvancements advancements; + private final ServerStatsCounter stats; + private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE; +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index f4ac7a383ea9732a915d100ec9a83e225a1a383d..3b679578858b869425dc60ae73fa12ac19d253ef 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -132,6 +132,7 @@ public abstract class PlayerList { + private boolean allowCommandsForAllPlayers; + private static final boolean ALLOW_LOGOUTIVATOR = false; + private int sendAllPlayerInfoIn; ++ public final List realPlayers = new java.util.concurrent.CopyOnWriteArrayList(); // Leaves - replay api + + // CraftBukkit start + private org.bukkit.craftbukkit.CraftServer cserver; +@@ -150,6 +151,105 @@ public abstract class PlayerList { + + abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + ++ // Leaves start - replay api ++ public void placeNewPhotographer(Connection connection, org.leavesmc.leaves.replay.ServerPhotographer player, ServerLevel worldserver, org.bukkit.Location location) { ++ player.isRealPlayer = true; // Paper ++ player.loginTime = System.currentTimeMillis(); // Paper ++ ++ ServerLevel worldserver1 = worldserver; ++ ++ player.setServerLevel(worldserver1); ++ player.spawnIn(worldserver1); ++ player.gameMode.setLevel((ServerLevel) player.level()); ++ ++ LevelData worlddata = worldserver1.getLevelData(); ++ ++ player.loadGameTypes(null); ++ ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, CommonListenerCookie.createInitial(player.gameProfile, false)); ++ GameRules gamerules = worldserver1.getGameRules(); ++ boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN); ++ boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); ++ boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); ++ ++ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); // Paper - replace old player chunk management ++ player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit ++ playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); ++ playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); ++ playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected)); ++ RecipeManager craftingmanager = this.server.getRecipeManager(); ++ playerconnection.send(new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes())); ++ this.sendPlayerPermissionLevel(player); ++ player.getStats().markAllDirty(); ++ player.getRecipeBook().sendInitialRecipeBook(player); ++ this.updateEntireScoreboard(worldserver1.getScoreboard(), player); ++ this.server.invalidateStatus(); ++ ++ playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); ++ ServerStatus serverping = this.server.getStatus(); ++ ++ if (serverping != null) { ++ player.sendServerStatus(serverping); ++ } ++ ++ this.players.add(player); ++ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot ++ this.playersByUUID.put(player.getUUID(), player); ++ ++ player.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); ++ org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity(); ++ ++ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); ++ if (!player.connection.isAcceptingMessages()) { ++ return; ++ } ++ ++ // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol ++ ++ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); ++ for (int i = 0; i < this.players.size(); ++i) { ++ ServerPlayer entityplayer1 = this.players.get(i); ++ ++ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { ++ continue; ++ } ++ ++ onlinePlayers.add(entityplayer1); ++ } ++ if (!onlinePlayers.isEmpty()) { ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); ++ } ++ ++ player.sentListPacket = true; ++ player.supressTrackerForLogin = false; ++ ((ServerLevel) player.level()).getChunkSource().chunkMap.addEntity(player); ++ ++ this.sendLevelInfo(player, worldserver1); ++ ++ if (player.level() == worldserver1 && !worldserver1.players().contains(player)) { ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); ++ } ++ ++ worldserver1 = player.serverLevel(); ++ java.util.Iterator iterator = player.getActiveEffects().iterator(); ++ while (iterator.hasNext()) { ++ MobEffectInstance mobeffect = iterator.next(); ++ playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect, false)); ++ } ++ ++ if (player.isDeadOrDying()) { ++ net.minecraft.core.Holder plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) ++ .getOrThrow(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, false) ++ ); ++ } ++ } ++ // Leaves end - replay api ++ + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) { + player.isRealPlayer = true; // Paper + player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed +@@ -315,6 +415,7 @@ public abstract class PlayerList { + + // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below + this.players.add(player); ++ this.realPlayers.add(player); // Leaves - replay api + this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot + this.playersByUUID.put(player.getUUID(), player); + this.addToSendAllPlayerInfoBuckets(player); // Gale - Purpur - spread out sending all player info +@@ -374,6 +475,12 @@ public abstract class PlayerList { + continue; + } + ++ // Leaves start - skip photographer ++ if (entityplayer1 instanceof org.leavesmc.leaves.replay.ServerPhotographer) { ++ continue; ++ } ++ // Leaves end - skip photographer ++ + onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join + } + // Paper start - Use single player info update packet on join +@@ -515,6 +622,43 @@ public abstract class PlayerList { + } + } + ++ // Leaves start - replay mod api ++ public void removePhotographer(org.leavesmc.leaves.replay.ServerPhotographer entityplayer) { ++ ServerLevel worldserver = entityplayer.serverLevel(); ++ ++ entityplayer.awardStat(Stats.LEAVE_GAME); ++ ++ if (entityplayer.containerMenu != entityplayer.inventoryMenu) { ++ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); ++ } ++ ++ if (server.isSameThread()) entityplayer.doTick(); ++ ++ if (this.collideRuleTeamName != null) { ++ final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName); ++ if (entityplayer.getTeam() == team && team != null) { ++ scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team); ++ } ++ } ++ ++ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ entityplayer.retireScheduler(); ++ entityplayer.getAdvancements().stopListening(); ++ this.players.remove(entityplayer); ++ this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); ++ this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); ++ UUID uuid = entityplayer.getUUID(); ++ ServerPlayer entityplayer1 = this.playersByUUID.get(uuid); ++ ++ if (entityplayer1 == entityplayer) { ++ this.playersByUUID.remove(uuid); ++ } ++ ++ this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); ++ } ++ // Leaves stop - replay mod api ++ + public net.kyori.adventure.text.Component remove(ServerPlayer player) { // CraftBukkit - return string // Paper - return Component + // Paper start - Fix kick event leave message not being sent + return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName()))); +@@ -591,6 +735,7 @@ public abstract class PlayerList { + player.retireScheduler(); // Paper - Folia schedulers + player.getAdvancements().stopListening(); + this.players.remove(player); ++ this.realPlayers.remove(player); // Leaves - replay api + this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot + this.removeFromSendAllPlayerInfoBuckets(player); // Gale - Purpur - spread out sending all player info + this.server.getCustomBossEvents().onPlayerDisconnect(player); +@@ -688,7 +833,7 @@ public abstract class PlayerList { + // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile) + // ? Component.translatable("multiplayer.disconnect.server_full") + // : null; +- if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission ++ if (this.realPlayers.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission // Leaves - only real player + event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure + } + } diff --git a/Leaf-Server/minecraft-patches/features/0024-Leaves-Disable-moved-wrongly-threshold.patch b/Leaf-Server/minecraft-patches/features/0024-Leaves-Disable-moved-wrongly-threshold.patch new file mode 100644 index 00000000..3986e2cc --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0024-Leaves-Disable-moved-wrongly-threshold.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 28 Sep 2023 20:30:46 +0800 +Subject: [PATCH] Leaves: Disable moved wrongly threshold + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 7e656b426aafc2581e13e8a1ae9e304530f3d05b..465e34eb906ec78f49ff1183b635e77a1fcaa758 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -575,7 +575,7 @@ public class ServerGamePacketListenerImpl + return; + } + // Paper end - Prevent moving into unloaded chunks +- if (d7 - d6 > Math.max(100.0, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled && d7 - d6 > Math.max(100.0, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { // Leaves - disable can + // CraftBukkit end + LOGGER.warn( + "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5 +@@ -605,7 +605,7 @@ public class ServerGamePacketListenerImpl + d5 = d2 - rootVehicle.getZ(); + d7 = d3 * d3 + d4 * d4 + d5 * d5; + boolean flag2 = false; +- if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot ++ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot // Leaves - disable can + flag2 = true; // Paper - diff on change, this should be moved wrongly + LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7)); + } +@@ -1450,7 +1450,7 @@ public class ServerGamePacketListenerImpl + io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, + toX, toY, toZ, toYaw, toPitch, true); + if (!event.isAllowed()) { +- if (event.getLogWarning()) { ++ if (event.getLogWarning()) { // Leaves - disable can + LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5); + } + this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); +@@ -1516,7 +1516,8 @@ public class ServerGamePacketListenerImpl + d5 = d2 - this.player.getZ(); + d7 = d3 * d3 + d4 * d4 + d5 * d5; + boolean movedWrongly = false; // Paper - Add fail move event; rename +- if (!this.player.isChangingDimension() ++ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled // Leaves - disable can ++ && !this.player.isChangingDimension() + && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold // Spigot + && !this.player.isSleeping() + && !this.player.gameMode.isCreative() diff --git a/Leaf-Server/minecraft-patches/features/0025-Petal-Async-Pathfinding.patch b/Leaf-Server/minecraft-patches/features/0025-Petal-Async-Pathfinding.patch new file mode 100644 index 00000000..264531a4 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0025-Petal-Async-Pathfinding.patch @@ -0,0 +1,851 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: peaches94 +Date: Sun, 26 Jun 2022 16:51:37 -0500 +Subject: [PATCH] Petal: Async Pathfinding + +Fixed & Updated by KaiijuMC +Original license: GPLv3 +Original project: https://github.com/KaiijuMC/Kaiiju + +Original license: GPLv3 +Original project: https://github.com/Bloom-host/Petal + +This patch was ported downstream from the Petal fork. + +Makes most pathfinding-related work happen asynchronously + +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 14d9dceacc82cc6c085dab8f52e59a318dd8cae5..8b3dfb1385a2252a4aaead5558c0ffbd5c204971 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -255,6 +255,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + @Nullable + @Override + public LivingEntity getTarget() { ++ //if (Thread.currentThread().getName().contains("petal-async-pathfinding-thread")) return this.target; // Kaiiju - Don't reset target when async pathfinding! // Leaf - Don't need this + return this.target; + } + +diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +index 7f0975f8bd6d5f8ca28f503f93c8cb5c42557420..eb71f045d3e8698a8a9e9f51176c2884f71a034c 100644 +--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -102,21 +102,20 @@ public class AcquirePoi { + } + } + // Paper end - optimise POI access +- Path path = findPathToPois(mob, set); +- if (path != null && path.canReach()) { +- BlockPos target = path.getTarget(); +- poiManager.getType(target).ifPresent(holder -> { +- poiManager.take(acquirablePois, (holder1, blockPos) -> blockPos.equals(target), target, 1); +- memoryAccessor.set(GlobalPos.of(level.dimension(), target)); +- entityEventId.ifPresent(id -> level.broadcastEntityEvent(mob, id)); +- map.clear(); +- DebugPackets.sendPoiTicketCountPacket(level, target); ++ // Kaiiju start - petal - Async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ // await on path async ++ Path possiblePath = findPathToPois(mob, set); ++ ++ // wait on the path to be processed ++ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ processPath(acquirablePois, entityEventId, (Long2ObjectMap) map, memoryAccessor, level, mob, time, poiManager, set, path); + }); + } else { +- for (Pair, BlockPos> pair : set) { +- map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); +- } +- } ++ // Kaiiju end ++ Path path = findPathToPois(mob, set); ++ processPath(acquirablePois, entityEventId, (Long2ObjectMap) map, memoryAccessor, level, mob, time, poiManager, set, path); ++ } // Kaiiju - Async path processing + + return true; + } +@@ -128,6 +127,34 @@ public class AcquirePoi { + : BehaviorBuilder.create(instance -> instance.group(instance.absent(existingAbsentMemory)).apply(instance, memoryAccessor -> oneShot)); + } + ++ // Leaf start - Kaiiju - Async path processing ++ private static void processPath(Predicate> acquirablePois, ++ Optional entityEventId, ++ Long2ObjectMap map, ++ net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor, GlobalPos> memoryAccessor, ++ ServerLevel level, ++ PathfinderMob mob, ++ long time, ++ PoiManager poiManager, ++ Set, BlockPos>> set, ++ Path path) { ++ if (path != null && path.canReach()) { ++ BlockPos target = path.getTarget(); ++ poiManager.getType(target).ifPresent(holder -> { ++ poiManager.take(acquirablePois, (holder1, blockPos) -> blockPos.equals(target), target, 1); ++ memoryAccessor.set(GlobalPos.of(level.dimension(), target)); ++ entityEventId.ifPresent(id -> level.broadcastEntityEvent(mob, id)); ++ map.clear(); ++ DebugPackets.sendPoiTicketCountPacket(level, target); ++ }); ++ } else { ++ for (Pair, BlockPos> pair : set) { ++ map.computeIfAbsent(pair.getSecond().asLong(), l -> new JitteredLinearRetry(level.random, time)); ++ } ++ } ++ } ++ // Leaf end - Kaiiju - Async path processing ++ + @Nullable + public static Path findPathToPois(Mob mob, Set, BlockPos>> poiPositions) { + if (poiPositions.isEmpty()) { +diff --git a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +index 621ba76784f2b92790eca62be4d0688834335ab6..e2e1532d2ffb709e347db42b1b5b6cae5e7e9700 100644 +--- a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java ++++ b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +@@ -21,6 +21,7 @@ public class MoveToTargetSink extends Behavior { + private int remainingCooldown; + @Nullable + private Path path; ++ private boolean finishedProcessing; // Kaiiju - petal - track when path is processed + @Nullable + private BlockPos lastTargetPos; + private float speedModifier; +@@ -53,9 +54,10 @@ public class MoveToTargetSink extends Behavior { + Brain brain = owner.getBrain(); + WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); + boolean flag = this.reachedTarget(owner, walkTarget); +- if (!flag && this.tryComputePath(owner, walkTarget, level.getGameTime())) { ++ if (!org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled && !flag && this.tryComputePath(owner, walkTarget, level.getGameTime())) { // Kaiiju - petal - async path processing means we can't know if the path is reachable here + this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); + return true; ++ } else if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled && !flag) { return true; // Kaiiju - async pathfinding + } else { + brain.eraseMemory(MemoryModuleType.WALK_TARGET); + if (flag) { +@@ -69,6 +71,7 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected boolean canStillUse(ServerLevel level, Mob entity, long gameTime) { ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled && !this.finishedProcessing) return true; // Kaiiju - petal - wait for processing + if (this.path != null && this.lastTargetPos != null) { + Optional memory = entity.getBrain().getMemory(MemoryModuleType.WALK_TARGET); + boolean flag = memory.map(MoveToTargetSink::isWalkTargetSpectator).orElse(false); +@@ -95,12 +98,68 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected void start(ServerLevel level, Mob entity, long gameTime) { ++ // Kaiiju start - petal - start processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ Brain brain = entity.getBrain(); ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); ++ ++ this.finishedProcessing = false; ++ this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ this.path = this.computePath(entity, walkTarget); ++ return; ++ } ++ // Kaiiju end + entity.getBrain().setMemory(MemoryModuleType.PATH, this.path); + entity.getNavigation().moveTo(this.path, (double)this.speedModifier); + } + + @Override + protected void tick(ServerLevel level, Mob owner, long gameTime) { ++ // Kaiiju start - petal - Async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ if (this.path != null && !this.path.isProcessed()) return; // wait for processing ++ ++ if (!this.finishedProcessing) { ++ this.finishedProcessing = true; ++ ++ Brain brain = owner.getBrain(); ++ boolean canReach = this.path != null && this.path.canReach(); ++ if (canReach) { ++ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); ++ } else if (!brain.hasMemoryValue(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)) { ++ brain.setMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, gameTime); ++ } ++ ++ if (!canReach) { ++ Optional walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET); ++ ++ if (!walkTarget.isPresent()) return; ++ ++ BlockPos blockPos = walkTarget.get().getTarget().currentBlockPosition(); ++ Vec3 vec3 = DefaultRandomPos.getPosTowards((PathfinderMob) owner, 10, 7, Vec3.atBottomCenterOf(blockPos), (float) Math.PI / 2F); ++ if (vec3 != null) { ++ // try recalculating the path using a random position ++ this.path = owner.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); ++ this.finishedProcessing = false; ++ return; ++ } ++ } ++ ++ owner.getBrain().setMemory(MemoryModuleType.PATH, this.path); ++ owner.getNavigation().moveTo(this.path, this.speedModifier); ++ } ++ ++ Path path = owner.getNavigation().getPath(); ++ Brain brain = owner.getBrain(); ++ ++ if (path != null && this.lastTargetPos != null && brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) { ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); // we know isPresent = true ++ if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D) { ++ this.start(level, owner, gameTime); ++ } ++ } ++ } else { ++ // Kaiiju end + Path path = owner.getNavigation().getPath(); + Brain brain = owner.getBrain(); + if (this.path != path) { +@@ -115,7 +174,23 @@ public class MoveToTargetSink extends Behavior { + this.start(level, owner, gameTime); + } + } ++ } // Kaiiju - async path processing ++ } ++ ++ // Kaiiju start - petal - Async path processing ++ @Nullable ++ private Path computePath(Mob entity, WalkTarget walkTarget) { ++ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); ++ // don't pathfind outside region ++ //if (!io.papermc.paper.util.TickThread.isTickThreadFor((ServerLevel) entity.level(), blockPos)) return null; // Leaf - Don't need this ++ this.speedModifier = walkTarget.getSpeedModifier(); ++ Brain brain = entity.getBrain(); ++ if (this.reachedTarget(entity, walkTarget)) { ++ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); ++ } ++ return entity.getNavigation().createPath(blockPos, 0); + } ++ // Kaiiju end + + private boolean tryComputePath(Mob mob, WalkTarget target, long time) { + BlockPos blockPos = target.getTarget().currentBlockPosition(); +diff --git a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java +index 4f9f3367b1ca3903df03a80fa2b01a3d24e6e77d..51413df5cd61b3ff59c6c6c3ec69d6732ab07d83 100644 +--- a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java ++++ b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java +@@ -60,17 +60,20 @@ public class SetClosestHomeAsWalkTarget { + poi -> poi.is(PoiTypes.HOME), predicate, mob.blockPosition(), 48, PoiManager.Occupancy.ANY + ) + .collect(Collectors.toSet()); ++ // Kaiiju start - petal - Async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ // await on path async ++ Path possiblePath = AcquirePoi.findPathToPois(mob, set); ++ ++ // wait on the path to be processed ++ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ processPath(speedModifier, map, mutableLong, walkTarget, level, poiManager, mutableInt, path); ++ }); ++ } else { ++ // Kaiiju end + Path path = AcquirePoi.findPathToPois(mob, set); +- if (path != null && path.canReach()) { +- BlockPos target = path.getTarget(); +- Optional> type = poiManager.getType(target); +- if (type.isPresent()) { +- walkTarget.set(new WalkTarget(target, speedModifier, 1)); +- DebugPackets.sendPoiTicketCountPacket(level, target); +- } +- } else if (mutableInt.getValue() < 5) { +- map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); +- } ++ processPath(speedModifier, map, mutableLong, walkTarget, level, poiManager, mutableInt, path); ++ } // Kaiiju - async path processing + + return true; + } else { +@@ -81,4 +84,26 @@ public class SetClosestHomeAsWalkTarget { + ) + ); + } ++ ++ // Leaf start - Kaiiju - petal - Async path processing ++ private static void processPath(float speedModifier, ++ Long2LongMap map, ++ MutableLong mutableLong, ++ net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor, WalkTarget> walkTarget, ++ net.minecraft.server.level.ServerLevel level, ++ PoiManager poiManager, ++ MutableInt mutableInt, ++ @org.jetbrains.annotations.Nullable Path path) { ++ if (path != null && path.canReach()) { ++ BlockPos target = path.getTarget(); ++ Optional> type = poiManager.getType(target); ++ if (type.isPresent()) { ++ walkTarget.set(new WalkTarget(target, speedModifier, 1)); ++ DebugPackets.sendPoiTicketCountPacket(level, target); ++ } ++ } else if (mutableInt.getValue() < 5) { ++ map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); ++ } ++ } ++ // Leaf end - Kaiiju - petal - Async path processing + } +diff --git a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java b/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java +index d8f532c5e68ff4dff933556c4f981e9474c044e6..95733482a647935e1e7f81fa71a0e99ea52e9a9b 100644 +--- a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java ++++ b/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java +@@ -56,7 +56,7 @@ public abstract class DoorInteractGoal extends Goal { + } else { + GroundPathNavigation groundPathNavigation = (GroundPathNavigation)this.mob.getNavigation(); + Path path = groundPathNavigation.getPath(); +- if (path != null && !path.isDone()) { ++ if (path != null && path.isProcessed() && !path.isDone()) { // Kaiiju - async pathfinding - ensure path is processed + for (int i = 0; i < Math.min(path.getNextNodeIndex() + 2, path.getNodeCount()); i++) { + Node node = path.getNode(i); + this.doorPos = new BlockPos(node.x, node.y + 1, node.z); +diff --git a/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java b/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java +index 66a02fe7594522ef391d67e09856bf3f70fe597d..55e4e6542ac05d89b8d062c546de85b29fb0099f 100644 +--- a/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java +@@ -12,9 +12,25 @@ public class AmphibiousPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // Kaiiju start - petal - async path processing ++ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ AmphibiousNodeEvaluator nodeEvaluator = new AmphibiousNodeEvaluator(false); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // Kaiiju end ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new AmphibiousNodeEvaluator(false); ++ // Kaiiju start - petal - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +index 71ea68b56b3069bdf8e47931156b6ef49ea8ce5d..5de4c1138c7092ff8240fcb30cd64ff1f4d12088 100644 +--- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java +@@ -16,9 +16,25 @@ public class FlyingPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // Kaiiju start - petal - async path processing ++ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ FlyNodeEvaluator nodeEvaluator = new FlyNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // Kaiiju end ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new FlyNodeEvaluator(); ++ // Kaiiju start - petal - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +@@ -48,6 +64,7 @@ public class FlyingPathNavigation extends PathNavigation { + if (this.hasDelayedRecomputation) { + this.recomputePath(); + } ++ if (this.path != null && !this.path.isProcessed()) return; // Kaiiju - petal - async path processing + + if (!this.isDone()) { + if (this.canUpdatePath()) { +diff --git a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +index 045cfafb3afe8271d60852ae3c7cdcb039b44d4f..3f55b00e2f8924a8450df8a3f9812a4ae2983df3 100644 +--- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +@@ -24,9 +24,25 @@ public class GroundPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // Kaiiju start - petal - async path processing ++ protected static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // Kaiiju end ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // Kaiiju start - petal - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index 6c8fb611943aee8cabc471c63166f9b44ef14826..25ef9b67eee01c6df466031c5dbc728b1a754ab2 100644 +--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -167,6 +167,10 @@ public abstract class PathNavigation { + return null; + } else if (!this.canUpdatePath()) { + return null; ++ // Kaiiju start - petal - catch early if it's still processing these positions let it keep processing ++ } else if (this.path instanceof org.dreeam.leaf.async.path.AsyncPath asyncPath && !asyncPath.isProcessed() && asyncPath.hasSameProcessingPositions(targets)) { ++ return this.path; ++ // Kaiiju end + } else if (this.path != null && !this.path.isDone() && targets.contains(this.targetPos)) { + return this.path; + } else { +@@ -191,11 +195,29 @@ public abstract class PathNavigation { + int i = (int)(followRange + regionOffset); + PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i)); + Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, targets, followRange, accuracy, this.maxVisitedNodesMultiplier); ++ // Kaiiju start - petal - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ // assign early a target position. most calls will only have 1 position ++ if (!targets.isEmpty()) this.targetPos = targets.iterator().next(); ++ ++ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(path, processedPath -> { ++ // check that processing didn't take so long that we calculated a new path ++ if (processedPath != this.path) return; ++ ++ if (processedPath != null && processedPath.getTarget() != null) { ++ this.targetPos = processedPath.getTarget(); ++ this.reachRange = accuracy; ++ this.resetStuckTimeout(); ++ } ++ }); ++ } else { ++ // Kaiiju end + if (path != null && path.getTarget() != null) { + this.targetPos = path.getTarget(); + this.reachRange = accuracy; + this.resetStuckTimeout(); + } ++ } // Kaiiju - async path processing + + return path; + } +@@ -246,8 +268,8 @@ public abstract class PathNavigation { + if (this.isDone()) { + return false; + } else { +- this.trimPath(); +- if (this.path.getNodeCount() <= 0) { ++ if (path.isProcessed()) this.trimPath(); // Kaiiju - petal - only trim if processed ++ if (path.isProcessed() && this.path.getNodeCount() <= 0) { // Kaiiju - petal - only check node count if processed + return false; + } else { + this.speedModifier = speed; +@@ -270,6 +292,7 @@ public abstract class PathNavigation { + if (this.hasDelayedRecomputation) { + this.recomputePath(); + } ++ if (this.path != null && !this.path.isProcessed()) return; // Kaiiju - petal - skip pathfinding if we're still processing + + if (!this.isDone()) { + if (this.canUpdatePath()) { +@@ -299,6 +322,7 @@ public abstract class PathNavigation { + } + + protected void followThePath() { ++ if (!this.path.isProcessed()) return; // Kaiiju - petal - skip if not processed + Vec3 tempMobPos = this.getTempMobPos(); + this.maxDistanceToWaypoint = this.mob.getBbWidth() > 0.75F ? this.mob.getBbWidth() / 2.0F : 0.75F - this.mob.getBbWidth() / 2.0F; + Vec3i nextNodePos = this.path.getNextNodePos(); +@@ -455,7 +479,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.isProcessed() && !this.path.isDone() && this.path.getNodeCount() != 0) { // Kaiiju - petal - Skip if not processed + Node endNode = this.path.getEndNode(); + Vec3 vec3 = new Vec3((endNode.x + this.mob.getX()) / 2.0, (endNode.y + this.mob.getY()) / 2.0, (endNode.z + this.mob.getZ()) / 2.0); + return pos.closerToCenterThan(vec3, this.path.getNodeCount() - this.path.getNextNodeIndex()); +diff --git a/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java b/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java +index 2979846853898d78a2df19df2287da16dbe4ae71..1289a6e85f3fdb9187323343b6c20e17b6a7e296 100644 +--- a/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java +@@ -15,11 +15,27 @@ public class WaterBoundPathNavigation extends PathNavigation { + super(mob, level); + } + ++ // Kaiiju start - petal - async path processing ++ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ SwimNodeEvaluator nodeEvaluator = new SwimNodeEvaluator(nodeEvaluatorFeatures.allowBreaching()); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // Kaiiju end ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.allowBreaching = this.mob.getType() == EntityType.DOLPHIN; + this.nodeEvaluator = new SwimNodeEvaluator(this.allowBreaching); + this.nodeEvaluator.setCanPassDoors(false); ++ // Kaiiju start - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..8819717c5307a90abc493cf801b4e795c13b3460 100644 +--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +@@ -57,17 +57,32 @@ public class NearestBedSensor extends Sensor { + 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(), level.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur - Configurable villager search radius +- Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); +- // Paper end - optimise POI access +- if (path != null && path.canReach()) { +- BlockPos target = path.getTarget(); +- Optional> type = poiManager.getType(target); +- if (type.isPresent()) { +- entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target); +- } +- } else if (this.triedCount < 5) { +- this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); ++ // Kaiiju start - await on async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ processPath(entity, poiManager, path); ++ }); ++ } else { ++ // Kaiiju end ++ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ // Paper end - optimise POI access ++ processPath(entity, poiManager, path); ++ } // Kaiiju - async path processing ++ } ++ } ++ ++ // Leaf start - Kaiiju - await on async path processing ++ private void processPath(Mob entity, PoiManager poiManager, @org.jetbrains.annotations.Nullable Path path) { ++ if (path != null && path.canReach()) { ++ BlockPos target = path.getTarget(); ++ Optional> type = poiManager.getType(target); ++ if (type.isPresent()) { ++ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, target); + } ++ } else if (this.triedCount < 5) { ++ this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); + } + } ++ // Leaf end - Kaiiju - await on async path processing + } +diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java +index 57c50ce5724b073b1aedf4df3129285143097303..91635b344ac02b66e51aa5620acf9ca481004853 100644 +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -934,7 +934,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + } else { + Bee.this.pathfindRandomlyTowards(Bee.this.hivePos); + } +- } else { ++ } else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Kaiiju - petal - check processing + boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos); + if (!flag) { + this.dropAndBlacklistHive(); +@@ -988,7 +988,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + return true; + } else { + Path path = Bee.this.navigation.getPath(); +- return path != null && path.getTarget().equals(pos) && path.canReach() && path.isDone(); ++ return path != null && path.isProcessed() && path.getTarget().equals(pos) && path.canReach() && path.isDone(); // Kaiiju - petal - ensure path is processed + } + } + } +diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java +index 4ecc6b6247a6ab14a5d46f9a05d5df8412ae2a5f..2c28e97e76155fe4de309422f4913c1269972ff4 100644 +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java +@@ -479,9 +479,25 @@ public class Frog extends Animal implements VariantHolder> { + return pathType != PathType.WATER_BORDER && super.canCutCorner(pathType); + } + ++ // Kaiiju start - petal - async path processing ++ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ Frog.FrogNodeEvaluator nodeEvaluator = new Frog.FrogNodeEvaluator(true); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // Kaiiju end ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new Frog.FrogNodeEvaluator(true); ++ // Kaiiju start - petal - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + } +diff --git a/net/minecraft/world/entity/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java +index 6c73245b8d04f194e72165aa0000ca79a95db59d..f5e6673ff2bd3029585b9ffea10df5d549f1cdd6 100644 +--- a/net/minecraft/world/entity/monster/Drowned.java ++++ b/net/minecraft/world/entity/monster/Drowned.java +@@ -313,7 +313,7 @@ public class Drowned extends Zombie implements RangedAttackMob { + + protected boolean closeToNextPos() { + Path path = this.getNavigation().getPath(); +- if (path != null) { ++ if (path != null && path.isProcessed()) { // Kaiiju - petal - ensure path is processed + BlockPos target = path.getTarget(); + if (target != null) { + double d = this.distanceToSqr(target.getX(), target.getY(), target.getZ()); +diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java +index 241526239bdbd5d9276f85e7fca46a7051f46a25..ae4ee948971e931e4fdc4ec2187f5182195c626c 100644 +--- a/net/minecraft/world/entity/monster/Strider.java ++++ b/net/minecraft/world/entity/monster/Strider.java +@@ -579,9 +579,25 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { + super(strider, level); + } + ++ // Kaiiju start - petal - async path processing ++ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // Kaiiju end ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // Kaiiju start - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes); + } + +diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java +index 26f3fe1c80b0d87b96076432f35fe4f95f92ce13..3a43790fb91e778f4fc0730aecd0dde4a6d301c8 100644 +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -599,6 +599,16 @@ public class Warden extends Monster implements VibrationSystem { + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // Kaiiju start - petal - async path processing ++ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, GroundPathNavigation.nodeEvaluatorGenerator) { ++ @Override ++ protected float distance(Node first, Node second) { ++ return first.distanceToXZ(second); ++ } ++ }; ++ } ++ // Kaiiju end + return new PathFinder(this.nodeEvaluator, maxVisitedNodes) { + @Override + protected float distance(Node first, Node second) { +diff --git a/net/minecraft/world/level/block/ShulkerBoxBlock.java b/net/minecraft/world/level/block/ShulkerBoxBlock.java +index cdf835ff107bc1eadde706d69384e687626fce70..31066accd82da2b8b36d9a9d8676dc887af31fed 100644 +--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java ++++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java +@@ -217,9 +217,16 @@ public class ShulkerBoxBlock extends BaseEntityBlock { + + @Override + protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { ++ //if (Thread.currentThread().getName().contains("petal-async-pathfinding-thread")) return Shapes.block(); // Kaiiju - async pathfinding - we cannot get block entities // Leaf - Don't need this + return level.getBlockEntity(pos) instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity + ? Shapes.create(shulkerBoxBlockEntity.getBoundingBox(state)) + : Shapes.block(); ++ // Kaiiju start - async pathfinding - workaround // Leaf - Don't need this ++ /* ++ } catch (NullPointerException e) { ++ return Shapes.block(); ++ } ++ */ + } + + @Override +diff --git a/net/minecraft/world/level/pathfinder/Path.java b/net/minecraft/world/level/pathfinder/Path.java +index d6d3c8f5e5dd4a8cab0d3fcc131c3a59f06130c6..add5b8b98e4d09617cbd4e7dd2710dc50781613a 100644 +--- a/net/minecraft/world/level/pathfinder/Path.java ++++ b/net/minecraft/world/level/pathfinder/Path.java +@@ -26,6 +26,17 @@ public class Path { + this.reached = reached; + } + ++ // Kaiiju start - petal - async path processing ++ /** ++ * checks if the path is completely processed in the case of it being computed async ++ * ++ * @return true if the path is processed ++ */ ++ public boolean isProcessed() { ++ return true; ++ } ++ // Kaiiju end ++ + public void advance() { + this.nextNodeIndex++; + } +@@ -99,6 +110,7 @@ public class Path { + } + + public boolean sameAs(@Nullable Path pathentity) { ++ if (pathentity == this) return true; // Kaiiju - petal - short circuit + if (pathentity == null) { + return false; + } else if (pathentity.nodes.size() != this.nodes.size()) { +diff --git a/net/minecraft/world/level/pathfinder/PathFinder.java b/net/minecraft/world/level/pathfinder/PathFinder.java +index d48057d387b6937a0194e5300eb1cb46dec2896b..9912ba3bc165dfbdd55fbf02dcec28dc2902b30a 100644 +--- a/net/minecraft/world/level/pathfinder/PathFinder.java ++++ b/net/minecraft/world/level/pathfinder/PathFinder.java +@@ -22,10 +22,18 @@ public class PathFinder { + public final NodeEvaluator nodeEvaluator; + private static final boolean DEBUG = false; + private final BinaryHeap openSet = new BinaryHeap(); ++ private final @Nullable org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator; // Kaiiju - petal - we use this later to generate an evaluator + +- public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes) { ++ public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes, @Nullable org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator) { // Kaiiju - petal - add nodeEvaluatorGenerator + this.nodeEvaluator = nodeEvaluator; + this.maxVisitedNodes = maxVisitedNodes; ++ // Kaiiju start - petal - support nodeEvaluatorgenerators ++ this.nodeEvaluatorGenerator = nodeEvaluatorGenerator; ++ } ++ ++ public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes) { ++ this(nodeEvaluator, maxVisitedNodes, null); ++ // Kaiiju end + } + + public void setMaxVisitedNodes(int maxVisitedNodes) { +@@ -34,26 +42,63 @@ public class PathFinder { + + @Nullable + public Path findPath(PathNavigationRegion region, Mob mob, Set targetPositions, float maxRange, int accuracy, float searchDepthMultiplier) { +- this.openSet.clear(); +- this.nodeEvaluator.prepare(region, mob); +- Node start = this.nodeEvaluator.getStart(); ++ if (!org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) ++ this.openSet.clear(); // Kaiiju - petal - it's always cleared in processPath ++ // Kaiiju start - petal - use a generated evaluator if we have one otherwise run sync ++ NodeEvaluator nodeEvaluator = this.nodeEvaluatorGenerator == null ++ ? this.nodeEvaluator ++ : org.dreeam.leaf.async.path.NodeEvaluatorCache.takeNodeEvaluator(this.nodeEvaluatorGenerator, this.nodeEvaluator); ++ nodeEvaluator.prepare(region, mob); ++ Node start = nodeEvaluator.getStart(); ++ // Kaiiju end + if (start == null) { ++ org.dreeam.leaf.async.path.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); // Kaiiju - petal - handle nodeEvaluatorGenerator + return null; + } else { + // Paper start - Perf: remove streams and optimize collection + List> map = Lists.newArrayList(); + for (BlockPos pos : targetPositions) { +- map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos)); ++ map.add(new java.util.AbstractMap.SimpleEntry<>(nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos)); // Kaiiju - petal - handle nodeEvaluatorGenerator + } + // Paper end - Perf: remove streams and optimize collection +- Path path = this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier); +- this.nodeEvaluator.done(); +- return path; ++ // Kaiiju start - petal - async path processing ++ if (this.nodeEvaluatorGenerator == null) { ++ // run sync :( ++ org.dreeam.leaf.async.path.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); ++ return this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier); ++ } ++ ++ return new org.dreeam.leaf.async.path.AsyncPath(Lists.newArrayList(), targetPositions, () -> { ++ try { ++ return this.processPath(nodeEvaluator, start, map, maxRange, accuracy, searchDepthMultiplier); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ return null; ++ } finally { ++ nodeEvaluator.done(); ++ org.dreeam.leaf.async.path.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator); ++ } ++ }); ++ // Kaiiju end + } + } + + @Nullable + private Path findPath(Node node, List> positions, float maxRange, int accuracy, float searchDepthMultiplier) { // Paper - optimize collection ++ // Kaiiju start - petal - split pathfinding into the original sync method for compat and processing for delaying ++ try { ++ return this.processPath(this.nodeEvaluator, node, positions, maxRange, accuracy, searchDepthMultiplier); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ return null; ++ } finally { ++ this.nodeEvaluator.done(); ++ } ++ } ++ ++ private synchronized @org.jetbrains.annotations.NotNull Path processPath(NodeEvaluator nodeEvaluator, Node node, List> positions, float maxRange, int accuracy, float searchDepthMultiplier) { // sync to only use the caching functions in this class on a single thread ++ org.apache.commons.lang3.Validate.isTrue(!positions.isEmpty()); // ensure that we have at least one position, which means we'll always return a path ++ // Kaiiju end + // Set set = targetPositions.keySet(); // Paper + node.g = 0.0F; + node.h = this.getBestH(node, positions); // Paper - optimize collection +@@ -89,7 +134,7 @@ public class PathFinder { + } + + if (!(node1.distanceTo(node) >= maxRange)) { +- int neighbors = this.nodeEvaluator.getNeighbors(this.neighbors, node1); ++ int neighbors = nodeEvaluator.getNeighbors(this.neighbors, node1); // Kaiiju - petal - use provided nodeEvaluator + + for (int i2 = 0; i2 < neighbors; i2++) { + Node node2 = this.neighbors[i2]; +@@ -124,6 +169,7 @@ public class PathFinder { + } + } + ++ //noinspection ConstantConditions // Kaiiju - petal - ignore this warning, we know that the above loop always runs at least once since positions is not empty + return best; + // Paper end - Perf: remove streams and optimize collection + } +diff --git a/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java b/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java +index f18aa938692a1c29228ff5e98ab9d58c4bfff094..6495dd7ace05dda1e8e39fa5ae682797dd5980bf 100644 +--- a/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java ++++ b/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java +@@ -15,7 +15,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.material.FluidState; + + public class SwimNodeEvaluator extends NodeEvaluator { +- private final boolean allowBreaching; ++ public final boolean allowBreaching; // Kaiiju - make this public + private final Long2ObjectMap pathTypesByPosCache = new Long2ObjectOpenHashMap<>(); + + public SwimNodeEvaluator(boolean allowBreaching) { diff --git a/patches/server/0039-Petal-reduce-work-done-by-game-event-system.patch b/Leaf-Server/minecraft-patches/features/0026-Petal-reduce-work-done-by-game-event-system.patch similarity index 52% rename from patches/server/0039-Petal-reduce-work-done-by-game-event-system.patch rename to Leaf-Server/minecraft-patches/features/0026-Petal-reduce-work-done-by-game-event-system.patch index a0f8eeb7..b0b47b9b 100644 --- a/patches/server/0039-Petal-reduce-work-done-by-game-event-system.patch +++ b/Leaf-Server/minecraft-patches/features/0026-Petal-reduce-work-done-by-game-event-system.patch @@ -10,38 +10,38 @@ Original project: https://github.com/Bloom-host/Petal 2. EuclideanGameEventListenerRegistry is not used concurrently so we ban that usage for improved performance with allays -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -index 4729befa12732a9fd65cce243b33b3b479026c41..f61295465d89d6aaab83666677617f5198b75f3c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -@@ -68,7 +68,7 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi +diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +index 1638eccef431fb68775af624110f1968f0c6dabd..1b63730f508813f380460860a6193d419112c08f 100644 +--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -65,7 +65,7 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi return this.catalystListener; } - public static class CatalystListener implements GameEventListener { + public class CatalystListener implements GameEventListener { // Leaf - petal - public static final int PULSE_TICKS = 8; final SculkSpreader sculkSpreader; -@@ -139,6 +139,13 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi - world.playSound((Player) null, pos, SoundEvents.SCULK_CATALYST_BLOOM, SoundSource.BLOCKS, 2.0F, 0.6F + random.nextFloat() * 0.4F); + private final BlockState blockState; +@@ -127,6 +127,13 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + level.playSound(null, pos, SoundEvents.SCULK_CATALYST_BLOOM, SoundSource.BLOCKS, 2.0F, 0.6F + random.nextFloat() * 0.4F); } -+ // Leaf start - petal -+ @Override -+ public boolean listensToEvent(GameEvent gameEvent, GameEvent.Context context) { -+ return !SculkCatalystBlockEntity.this.isRemoved() && gameEvent == GameEvent.ENTITY_DIE.value() && context.sourceEntity() instanceof LivingEntity; -+ } -+ // Leaf end - petal ++ // Leaf start - petal ++ @Override ++ public boolean listensToEvent(GameEvent gameEvent, GameEvent.Context context) { ++ return !SculkCatalystBlockEntity.this.isRemoved() && gameEvent == GameEvent.ENTITY_DIE.value() && context.sourceEntity() instanceof LivingEntity; ++ } ++ // Leaf end - petal + - private void tryAwardItSpreadsAdvancement(Level world, LivingEntity deadEntity) { - LivingEntity entityliving1 = deadEntity.getLastHurtByMob(); - -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 370266dfa461432b9e22b3ce35d6094949dc2f49..b8246d7255bffc7e12a67772df2ceac1925b2a05 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -81,7 +81,18 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + private void tryAwardItSpreadsAdvancement(Level level, LivingEntity entity) { + if (entity.getLastHurtByMob() instanceof ServerPlayer serverPlayer) { + DamageSource damageSource = entity.getLastDamageSource() == null +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index a5f76c81dfb148fc184d137395d5961229cb799b..d97d8e79034eb1484d4e3646faacc6f11289bb28 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -79,7 +79,19 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p private Supplier fullStatus; @Nullable private LevelChunk.PostLoadProcessor postLoad; @@ -57,28 +57,31 @@ index 370266dfa461432b9e22b3ce35d6094949dc2f49..b8246d7255bffc7e12a67772df2ceac1 + private static int getGameEventSectionLength(int sectionCount) { + return sectionCount + (GAME_EVENT_DISPATCHER_RADIUS * 2); + } ++ + // Leaf end - petal private final LevelChunkTicks blockTicks; private final LevelChunkTicks fluidTicks; - private LevelChunk.UnsavedListener unsavedListener; -@@ -108,7 +119,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - this.unsavedListener = (chunkcoordintpair1) -> { - }; - this.level = (ServerLevel) world; // CraftBukkit - type -- this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); + private LevelChunk.UnsavedListener unsavedListener = chunkPos -> {}; +@@ -154,7 +166,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + ) { + super(pos, data, level, net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry + this.level = (ServerLevel) level; // CraftBukkit - type +- this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>(); + this.gameEventListenerRegistrySections = new GameEventListenerRegistry[getGameEventSectionLength(this.getSectionsCount())]; // Leaf - petal - Heightmap.Types[] aheightmap_type = Heightmap.Types.values(); - int j = aheightmap_type.length; -@@ -266,9 +277,23 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - Level world = this.level; + for (Heightmap.Types types : Heightmap.Types.values()) { + if (ChunkStatus.FULL.heightmapsAfter().contains(types)) { +@@ -268,10 +280,27 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - if (world instanceof ServerLevel worldserver) { -- return (GameEventListenerRegistry) this.gameEventListenerRegistrySections.computeIfAbsent(ySectionCoord, (j) -> { -- return new EuclideanGameEventListenerRegistry(worldserver, ySectionCoord, this::removeGameEventListenerRegistry); -- }); -+ // Leaf start - petal -+ int sectionIndex = getGameEventSectionIndex(this.getSectionIndexFromSectionY(ySectionCoord)); + @Override + public GameEventListenerRegistry getListenerRegistry(int sectionY) { +- return this.level instanceof ServerLevel serverLevel +- ? this.gameEventListenerRegistrySections +- .computeIfAbsent(sectionY, i -> new EuclideanGameEventListenerRegistry(serverLevel, sectionY, this::removeGameEventListenerRegistry)) +- : super.getListenerRegistry(sectionY); ++ // Leaf start - petal ++ if (this.level instanceof ServerLevel serverLevel) { ++ int sectionIndex = getGameEventSectionIndex(this.getSectionIndexFromSectionY(sectionY)); + + // drop game events that are too far away (32 blocks) from loaded sections + // this matches the highest radius of game events in the game @@ -89,27 +92,30 @@ index 370266dfa461432b9e22b3ce35d6094949dc2f49..b8246d7255bffc7e12a67772df2ceac1 + var dispatcher = this.gameEventListenerRegistrySections[sectionIndex]; + + if (dispatcher == null) { -+ dispatcher = this.gameEventListenerRegistrySections[sectionIndex] = new EuclideanGameEventListenerRegistry(worldserver, ySectionCoord, this::removeGameEventListenerRegistry); ++ dispatcher = this.gameEventListenerRegistrySections[sectionIndex] = new EuclideanGameEventListenerRegistry(serverLevel, sectionY, this::removeGameEventListenerRegistry); + } + + return dispatcher; -+ // Leaf end - petal - } else { - return super.getListenerRegistry(ySectionCoord); - } -@@ -659,7 +684,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p ++ } ++ ++ return super.getListenerRegistry(sectionY); ++ // Leaf end - petal } - private void removeGameEventListenerRegistry(int ySectionCoord) { -- this.gameEventListenerRegistrySections.remove(ySectionCoord); -+ this.gameEventListenerRegistrySections[getGameEventSectionIndex(this.getSectionIndexFromSectionY(ySectionCoord))] = null; // Leaf - petal + // Paper start - Perf: Reduce instructions and provide final method +@@ -611,7 +640,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + } + + private void removeGameEventListenerRegistry(int sectionY) { +- this.gameEventListenerRegistrySections.remove(sectionY); ++ this.gameEventListenerRegistrySections[getGameEventSectionIndex(this.getSectionIndexFromSectionY(sectionY))] = null; // Leaf - petal } private void removeBlockEntityTicker(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java b/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java -index 1e93f3a1b11196a431a1f5a0957036fe0c9191a4..f1f0dbb20d4ee2f3f47e202f2cb3dd4bde9ac0b3 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java +diff --git a/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java b/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java +index 5175fc90a1fc61c832c6697997a97ae199b195ac..17ba3e94021365f0ea28126b5bef61cafc58461f 100644 +--- a/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java ++++ b/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java @@ -14,8 +14,8 @@ import net.minecraft.world.phys.Vec3; public class EuclideanGameEventListenerRegistry implements GameEventListenerRegistry { @@ -147,7 +153,7 @@ index 1e93f3a1b11196a431a1f5a0957036fe0c9191a4..f1f0dbb20d4ee2f3f47e202f2cb3dd4b + if (false) { // Leaf - petal - Disallow concurrent modification iterator.remove(); } else { - Optional optional = getPostableListenerPosition(this.level, pos, gameEventListener); + Optional postableListenerPosition = getPostableListenerPosition(this.level, pos, gameEventListener); @@ -80,6 +80,8 @@ public class EuclideanGameEventListenerRegistry implements GameEventListenerRegi this.processing = false; } @@ -161,27 +167,27 @@ index 1e93f3a1b11196a431a1f5a0957036fe0c9191a4..f1f0dbb20d4ee2f3f47e202f2cb3dd4b this.listeners.removeAll(this.listenersToRemove); this.listenersToRemove.clear(); } -+ */ ++ */ + // Leaf end - petal - return bl; + return flag; } -diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java -index df6c97be1b278c97a20390be5d3e60f429383702..75a34d60d6729f3767dc9bef283db6e9e3baf3a7 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java -@@ -45,6 +45,7 @@ public class GameEventDispatcher { - int k1 = SectionPos.blockToSectionCoord(blockposition.getZ() + i); - List list = new ArrayList(); - GameEventListenerRegistry.ListenerVisitor gameeventlistenerregistry_a = (gameeventlistener, vec3d1) -> { -+ if (!gameeventlistener.listensToEvent(event.value(), emitter)) return; // Leaf - petal - If they don't listen, ignore - if (gameeventlistener.getDeliveryMode() == GameEventListener.DeliveryMode.BY_DISTANCE) { - list.add(new GameEvent.ListenerInfo(event, emitterPos, emitter, gameeventlistener, vec3d1)); +diff --git a/net/minecraft/world/level/gameevent/GameEventDispatcher.java b/net/minecraft/world/level/gameevent/GameEventDispatcher.java +index 1e9b066ef468ae840eda3c1f6c4b68111a5e862c..a0c7debc62cbbc3f8911e06d453f51d239917d8d 100644 +--- a/net/minecraft/world/level/gameevent/GameEventDispatcher.java ++++ b/net/minecraft/world/level/gameevent/GameEventDispatcher.java +@@ -44,6 +44,7 @@ public class GameEventDispatcher { + int sectionPosCoord5 = SectionPos.blockToSectionCoord(blockPos.getZ() + notificationRadius); + List list = new ArrayList<>(); + GameEventListenerRegistry.ListenerVisitor listenerVisitor = (listener, pos1) -> { ++ if (!listener.listensToEvent(gameEvent.value(), context)) return; // Leaf - petal - If they don't listen, ignore + if (listener.getDeliveryMode() == GameEventListener.DeliveryMode.BY_DISTANCE) { + list.add(new GameEvent.ListenerInfo(gameEvent, pos, context, listener, pos1)); } else { -diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java -index 0f3a79cc644a5b89fa35fdd413ff5781987c4ef3..9751ddc24899fd4d854899de209e24d9264baebc 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java +diff --git a/net/minecraft/world/level/gameevent/GameEventListener.java b/net/minecraft/world/level/gameevent/GameEventListener.java +index 5a31b5f1e75dd7b412ab577ea6621b7e87fc0590..6411c12392c4989ed3ce9ead52d42c49dfdfa201 100644 +--- a/net/minecraft/world/level/gameevent/GameEventListener.java ++++ b/net/minecraft/world/level/gameevent/GameEventListener.java @@ -23,4 +23,10 @@ public interface GameEventListener { public interface Provider { T getListener(); diff --git a/patches/server/0040-Reduce-canSee-work.patch b/Leaf-Server/minecraft-patches/features/0027-Reduce-canSee-work.patch similarity index 80% rename from patches/server/0040-Reduce-canSee-work.patch rename to Leaf-Server/minecraft-patches/features/0027-Reduce-canSee-work.patch index e4075b26..53d3c326 100644 --- a/patches/server/0040-Reduce-canSee-work.patch +++ b/Leaf-Server/minecraft-patches/features/0027-Reduce-canSee-work.patch @@ -6,11 +6,11 @@ Subject: [PATCH] Reduce canSee work Co-authored by: Martijn Muijsers Co-authored by: MachineBreaker -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index c3b9a9904f7b34df3dbd0a25e52946ab45f0f4d6..859708f1ab4b9f1bd318ca08c73cb67b1c3fe010 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -993,17 +993,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 78654e7ae26c0b2b2512f4e29a331e2ead2d6916..b7cbf1c7bb56668763c4f968ea0d69f730cb5285 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -1006,17 +1006,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl for (int i = 0, len = entities.size(); i < len; ++i) { Entity entity = entities.get(i); diff --git a/patches/server/0041-Fix-sprint-glitch.patch b/Leaf-Server/minecraft-patches/features/0028-Fix-sprint-glitch.patch similarity index 59% rename from patches/server/0041-Fix-sprint-glitch.patch rename to Leaf-Server/minecraft-patches/features/0028-Fix-sprint-glitch.patch index f1d72964..b5d996ab 100644 --- a/patches/server/0041-Fix-sprint-glitch.patch +++ b/Leaf-Server/minecraft-patches/features/0028-Fix-sprint-glitch.patch @@ -4,11 +4,11 @@ Date: Mon, 4 Dec 2023 16:11:36 +0200 Subject: [PATCH] Fix sprint glitch -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 6f3986ba57ce794a1f78b8960a7c8de8a3508c19..d5395553034c148bdc12b065328f52ef72b9bd86 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1456,7 +1456,8 @@ public abstract class LivingEntity extends Entity implements Attackable { +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 956c1b5422c177e0da5140c4184720d10aa4e790..c041cba43b4687e2f2f057edfae448a42f6d8753 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1379,7 +1379,8 @@ public abstract class LivingEntity extends Entity implements Attackable { player.setRealHealth(health); } diff --git a/patches/server/0042-Configurable-movement-speed-of-more-entities.patch b/Leaf-Server/minecraft-patches/features/0029-Configurable-movement-speed-of-more-entities.patch similarity index 57% rename from patches/server/0042-Configurable-movement-speed-of-more-entities.patch rename to Leaf-Server/minecraft-patches/features/0029-Configurable-movement-speed-of-more-entities.patch index e7b34b43..2ff0ed6e 100644 --- a/patches/server/0042-Configurable-movement-speed-of-more-entities.patch +++ b/Leaf-Server/minecraft-patches/features/0029-Configurable-movement-speed-of-more-entities.patch @@ -4,11 +4,11 @@ Date: Sun, 17 Dec 2023 19:34:46 -0500 Subject: [PATCH] Configurable movement speed of more entities -diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -index 53ebc584a7eb8b18e959aff75a11a37fd0b8ace2..03de4f06c461a17ec7a3516ca579f79472bb3919 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -@@ -93,6 +93,7 @@ public class Drowned extends Zombie implements RangedAttackMob { +diff --git a/net/minecraft/world/entity/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java +index f5e6673ff2bd3029585b9ffea10df5d549f1cdd6..6b328ea277bb3adcb674b05845f82f7427237555 100644 +--- a/net/minecraft/world/entity/monster/Drowned.java ++++ b/net/minecraft/world/entity/monster/Drowned.java +@@ -97,6 +97,7 @@ public class Drowned extends Zombie implements RangedAttackMob { public void initAttributes() { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.drownedMaxHealth); this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.drownedScale); @@ -16,11 +16,11 @@ index 53ebc584a7eb8b18e959aff75a11a37fd0b8ace2..03de4f06c461a17ec7a3516ca579f794 } @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java -index 7c8ec5cd88fb2083f458a945e716b6f118555db8..108a0874cf9c17809d31d0f3cf64ceeb52746f32 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Husk.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java -@@ -44,6 +44,7 @@ public class Husk extends Zombie { +diff --git a/net/minecraft/world/entity/monster/Husk.java b/net/minecraft/world/entity/monster/Husk.java +index a5bfc6f5caba1da8cfcb345524e05e8676672cb0..a500584a3736d289bca2f7a76fec2d1567065229 100644 +--- a/net/minecraft/world/entity/monster/Husk.java ++++ b/net/minecraft/world/entity/monster/Husk.java +@@ -43,6 +43,7 @@ public class Husk extends Zombie { @Override public void initAttributes() { this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.huskMaxHealth); @@ -28,40 +28,32 @@ index 7c8ec5cd88fb2083f458a945e716b6f118555db8..108a0874cf9c17809d31d0f3cf64ceeb } @Override -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 85b03e0bf7436cb846df13c575ad78ac6a17a151..5924509cbe36d3fee9d2f119d58e67c4b083e4c4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -132,6 +132,7 @@ public class Zombie extends Monster { +diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java +index 7af71c777dca26cd94b1807a2a77ea0d30e92976..cab69c308a0524137636fd50dc7a0dd0d2a4a2c3 100644 +--- a/net/minecraft/world/entity/monster/Zombie.java ++++ b/net/minecraft/world/entity/monster/Zombie.java +@@ -123,6 +123,7 @@ public class Zombie extends Monster { public void initAttributes() { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth); this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombieScale); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.zombieMovementSpeed); // Leaf - Configurable zombie movement speed } + // Purpur end - Configurable entity base attributes - public boolean jockeyOnlyBaby() { -@@ -189,9 +190,15 @@ public class Zombie extends Monster { - this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); - } - -+ // Leaf start - Configurable zombie movement speed +@@ -190,7 +191,7 @@ public class Zombie extends Monster { public static AttributeSupplier.Builder createAttributes() { -- return Monster.createMonsterAttributes().add(Attributes.FOLLOW_RANGE, 35.0D).add(Attributes.MOVEMENT_SPEED, 0.23000000417232513D).add(Attributes.ATTACK_DAMAGE, 3.0D).add(Attributes.ARMOR, 2.0D).add(Attributes.SPAWN_REINFORCEMENTS_CHANCE); -+ return Monster.createMonsterAttributes() -+ .add(Attributes.FOLLOW_RANGE, 35.0D) -+ .add(Attributes.ATTACK_DAMAGE, 3.0D) -+ .add(Attributes.ARMOR, 2.0D) -+ .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE); - } -+ // Leaf end - - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { -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 6f6b32bf7f68d05e4173c31f2e631a409b858a05..160fcd1f8917d69dde01089111661337827da20a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -105,6 +105,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + return Monster.createMonsterAttributes() + .add(Attributes.FOLLOW_RANGE, 35.0) +- .add(Attributes.MOVEMENT_SPEED, 0.23F) ++ //.add(Attributes.MOVEMENT_SPEED, 0.23F) // Leaf - Configurable zombie movement speed + .add(Attributes.ATTACK_DAMAGE, 3.0) + .add(Attributes.ARMOR, 2.0) + .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE); +diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java +index 1ca0514732916d325c4a76d73120aaf613c3f780..750f63e337661b5448d0d863ab4dc99398fd5655 100644 +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -99,6 +99,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { @Override public void initAttributes() { this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth); @@ -69,46 +61,40 @@ index 6f6b32bf7f68d05e4173c31f2e631a409b858a05..160fcd1f8917d69dde01089111661337 } @Override -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 8c3271dcc8c9aa58e2e007eba282c11e42b4e0c9..8afc2234a179840308ad371296d6f3247c80f3ae 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -@@ -84,6 +84,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { +diff --git a/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/net/minecraft/world/entity/monster/ZombifiedPiglin.java +index fddbbffafea275dad187b7908386cf4c05c86743..10eac072872b9e61c25d368d19e86b18bc3d19e7 100644 +--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java ++++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java +@@ -85,6 +85,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { public void initAttributes() { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth); this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombifiedPiglinScale); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.zombifiedPiglinMovementSpeed); // Leaf - Configurable zombifiedPiglin movement speed } + // Purpur end - Configurable entity base attributes - @Override -@@ -125,9 +126,13 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); - } - -+ // Leaf start - Configurable zombieVillager movement speed +@@ -136,7 +137,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { public static AttributeSupplier.Builder createAttributes() { -- return Zombie.createAttributes().add(Attributes.SPAWN_REINFORCEMENTS_CHANCE, 0.0D).add(Attributes.MOVEMENT_SPEED, 0.23000000417232513D).add(Attributes.ATTACK_DAMAGE, 5.0D); -+ return Zombie.createAttributes() -+ .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE, 0.0D) -+ .add(Attributes.ATTACK_DAMAGE, 5.0D); + return Zombie.createAttributes() + .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE, 0.0) +- .add(Attributes.MOVEMENT_SPEED, 0.23F) ++ //.add(Attributes.MOVEMENT_SPEED, 0.23F) // Leaf - Configurable zombie movement speed + .add(Attributes.ATTACK_DAMAGE, 5.0); } -+ // Leaf end - @Override - public EntityDimensions getDefaultDimensions(Pose pose) { -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -index 36b875a1cb10f1704e8530b6eb7b7e9dc51ef995..8c9a6084f09f644bf22ac98edac797eea753277f 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -1554,6 +1554,7 @@ public class PurpurWorldConfig { +diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java +index 2aac26f50958d8653eb1472daa7761d36093cf4c..fd5a5b9cb9958e96ecfeb4846e290c756265b300 100644 +--- a/org/purpurmc/purpur/PurpurWorldConfig.java ++++ b/org/purpurmc/purpur/PurpurWorldConfig.java +@@ -1563,6 +1563,7 @@ public class PurpurWorldConfig { public boolean drownedTakeDamageFromWater = false; public boolean drownedBreakDoors = false; public boolean drownedAlwaysDropExp = false; -+ public double drownedMovementSpeed = 0.23000000417232513D; // Leaf - Configurable drowned movement speed ++ public double drownedMovementSpeed = 0.23F; // Leaf - Configurable drowned movement speed private void drownedSettings() { drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); -@@ -1572,6 +1573,7 @@ public class PurpurWorldConfig { +@@ -1581,6 +1582,7 @@ public class PurpurWorldConfig { drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater); drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors); drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp); @@ -116,15 +102,15 @@ index 36b875a1cb10f1704e8530b6eb7b7e9dc51ef995..8c9a6084f09f644bf22ac98edac797ee } public boolean elderGuardianRidable = false; -@@ -1946,6 +1948,7 @@ public class PurpurWorldConfig { +@@ -1955,6 +1957,7 @@ public class PurpurWorldConfig { public boolean huskJockeyTryExistingChickens = true; public boolean huskTakeDamageFromWater = false; public boolean huskAlwaysDropExp = false; -+ public double huskMovementSpeed = 0.23000000417232513D; // Leaf - Configurable husk movement speed ++ public double huskMovementSpeed = 0.23F; // Leaf - Configurable husk movement speed private void huskSettings() { huskRidable = getBoolean("mobs.husk.ridable", huskRidable); huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); -@@ -1963,6 +1966,7 @@ public class PurpurWorldConfig { +@@ -1972,6 +1975,7 @@ public class PurpurWorldConfig { huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater); huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp); @@ -132,15 +118,15 @@ index 36b875a1cb10f1704e8530b6eb7b7e9dc51ef995..8c9a6084f09f644bf22ac98edac797ee } public boolean illusionerRidable = false; -@@ -3260,6 +3264,7 @@ public class PurpurWorldConfig { +@@ -3269,6 +3273,7 @@ public class PurpurWorldConfig { public boolean zombieTakeDamageFromWater = false; public boolean zombieAlwaysDropExp = false; public double zombieHeadVisibilityPercent = 0.5D; -+ public double zombieMovementSpeed = 0.23000000417232513D; // Leaf - Configurable zombie movement speed ++ public double zombieMovementSpeed = 0.23F; // Leaf - Configurable zombie movement speed private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -3280,6 +3285,7 @@ public class PurpurWorldConfig { +@@ -3289,6 +3294,7 @@ public class PurpurWorldConfig { zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater); zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp); zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent); @@ -148,15 +134,15 @@ index 36b875a1cb10f1704e8530b6eb7b7e9dc51ef995..8c9a6084f09f644bf22ac98edac797ee } public boolean zombieHorseRidable = false; -@@ -3329,6 +3335,7 @@ public class PurpurWorldConfig { +@@ -3338,6 +3344,7 @@ public class PurpurWorldConfig { public int zombieVillagerCuringTimeMax = 6000; public boolean zombieVillagerCureEnabled = true; public boolean zombieVillagerAlwaysDropExp = false; -+ public double zombieVillagerMovementSpeed = 0.23000000417232513D; // Leaf - Configurable zombieVillager movement speed ++ public double zombieVillagerMovementSpeed = 0.23F; // Leaf - Configurable zombieVillager movement speed private void zombieVillagerSettings() { zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); -@@ -3349,6 +3356,7 @@ public class PurpurWorldConfig { +@@ -3358,6 +3365,7 @@ public class PurpurWorldConfig { zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); @@ -164,15 +150,15 @@ index 36b875a1cb10f1704e8530b6eb7b7e9dc51ef995..8c9a6084f09f644bf22ac98edac797ee } public boolean zombifiedPiglinRidable = false; -@@ -3363,6 +3371,7 @@ public class PurpurWorldConfig { +@@ -3372,6 +3380,7 @@ public class PurpurWorldConfig { public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; public boolean zombifiedPiglinTakeDamageFromWater = false; public boolean zombifiedPiglinAlwaysDropExp = false; -+ public double zombifiedPiglinMovementSpeed = 0.23000000417232513D; // Leaf - Configurable zombifiedPiglin movement speed ++ public double zombifiedPiglinMovementSpeed = 0.23F; // Leaf - Configurable zombifiedPiglin movement speed private void zombifiedPiglinSettings() { zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); -@@ -3381,6 +3390,7 @@ public class PurpurWorldConfig { +@@ -3390,6 +3399,7 @@ public class PurpurWorldConfig { zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); diff --git a/Leaf-Server/minecraft-patches/features/0030-Faster-sequencing-of-futures-for-chunk-structure-gen.patch b/Leaf-Server/minecraft-patches/features/0030-Faster-sequencing-of-futures-for-chunk-structure-gen.patch new file mode 100644 index 00000000..0b7dfafb --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0030-Faster-sequencing-of-futures-for-chunk-structure-gen.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Tue, 2 Jan 2024 21:13:53 -0500 +Subject: [PATCH] Faster sequencing of futures for chunk structure gen + +Replace `thenApply` with `thenCompose`. Once one task is completed then the next task starts immediately, +to prevent blocking threads while waiting to complete all tasks. But may cause the sequence of future compose disorder. + +diff --git a/net/minecraft/Util.java b/net/minecraft/Util.java +index 80a7a85e1a03a1ca406259207e1ae3b909b3284f..ad41d6d5723d1194344fd9c5a9151356be8bb602 100644 +--- a/net/minecraft/Util.java ++++ b/net/minecraft/Util.java +@@ -607,17 +607,44 @@ public class Util { + return map; + } + ++ // Leaf start - Faster sequencing of futures for chunk structure gen + public static CompletableFuture> sequence(List> futures) { ++ return sequence(futures, false); ++ } ++ public static CompletableFuture> sequence(List> futures, boolean useFaster) { ++ // Leaf end - Faster sequencing of futures for chunk structure gen + if (futures.isEmpty()) { + return CompletableFuture.completedFuture(List.of()); + } else if (futures.size() == 1) { + return futures.get(0).thenApply(List::of); + } else { + CompletableFuture completableFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); ++ // Leaf start - Faster sequencing of futures for chunk structure gen ++ if (org.dreeam.leaf.config.modules.opt.FasterStructureGenFutureSequencing.enabled && useFaster) { ++ return sequenceFaster(futures, completableFuture); ++ } ++ // Leaf end - Faster sequencing of futures for chunk structure gen ++ + return completableFuture.thenApply(_void -> futures.stream().map(CompletableFuture::join).toList()); + } + } + ++ // Leaf start - Faster sequencing of futures for chunk structure gen ++ private static CompletableFuture> sequenceFaster(List> futures, CompletableFuture completableFuture) { ++ return completableFuture.thenCompose(void_ -> ++ CompletableFuture.supplyAsync(() -> { ++ List list = new java.util.ArrayList<>(); ++ ++ for (CompletableFuture future : futures) { ++ list.add(future.join()); ++ } ++ ++ return list; ++ } ++ )); ++ } ++ // Leaf end - Faster sequencing of futures for chunk structure gen ++ + public static CompletableFuture> sequenceFailFast(List> completableFutures) { + CompletableFuture> completableFuture = new CompletableFuture<>(); + return fallibleSequence(completableFutures, completableFuture::completeExceptionally).applyToEither(completableFuture, Function.identity()); +diff --git a/net/minecraft/server/ReloadableServerRegistries.java b/net/minecraft/server/ReloadableServerRegistries.java +index d8c472b8c6aadcaadef14abd8ab43f466e94417e..bc079b6c3d751f2a63d089bf209cf7d8e0da76e8 100644 +--- a/net/minecraft/server/ReloadableServerRegistries.java ++++ b/net/minecraft/server/ReloadableServerRegistries.java +@@ -52,7 +52,7 @@ public class ReloadableServerRegistries { + List>> list1 = LootDataType.values() + .map(lootDataType -> scheduleRegistryLoad((LootDataType)lootDataType, registryOps, resourceManager, backgroundExecutor, conversions)) // Paper + .toList(); +- CompletableFuture>> completableFuture = Util.sequence(list1); ++ CompletableFuture>> completableFuture = Util.sequence(list1, false); // Leaf - Faster sequencing of futures for chunk structure gen + return completableFuture.thenApplyAsync( + list2 -> createAndValidateFullContext(registryAccess, provider, (List>)list2), backgroundExecutor + ); +diff --git a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +index 619b98e42e254c0c260c171a26a2472ddf59b885..4483d7764ddca635fb6fb841fdc2185357106fc5 100644 +--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java ++++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +@@ -255,7 +255,7 @@ public class ChunkGeneratorStructureState { + } + } + +- return Util.sequence(list).thenApply(completed -> { ++ return Util.sequence(list, true).thenApply(completed -> { // Leaf - Faster sequencing of futures for chunk structure gen + double d2 = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) / 1000.0; + LOGGER.debug("Calculation for {} took {}s", structureSet, d2); + return completed; diff --git a/Leaf-Server/minecraft-patches/features/0031-Reduce-items-finding-hopper-nearby-check.patch b/Leaf-Server/minecraft-patches/features/0031-Reduce-items-finding-hopper-nearby-check.patch new file mode 100644 index 00000000..b0d524e7 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0031-Reduce-items-finding-hopper-nearby-check.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 15 Jan 2024 10:53:10 -0500 +Subject: [PATCH] Reduce items finding hopper nearby check + +This patch add a toggle for items checking MinecraftHopper nearby, + +But still recommend to turn-off `checkForMinecartNearItemWhileActive` +Since `Reduce-hopper-item-checks.patch` will cause lag under massive dropped items + +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index 0e21c644d62597cf3425c8717ab1e70c766e22f2..6287f5d84f961968b82d350c3d15b587e8d19236 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -241,7 +241,9 @@ public class ItemEntity extends Entity implements TraceableEntity { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + return; // Gale - EMC - reduce hopper item checks + } +- this.markNearbyHopperCartsAsImmune(); // Gale - EMC - reduce hopper item checks ++ if (level().galeConfig().smallOptimizations.reducedIntervals.checkNearbyItem.hopper.minecart.temporaryImmunity.checkForMinecartNearItemWhileActive) { // Leaf - Reduce items finding hopper nearby check ++ this.markNearbyHopperCartsAsImmune(); // Gale - EMC - reduce hopper item checks ++ } + } + } + diff --git a/Leaf-Server/minecraft-patches/features/0032-Linear-region-file-format.patch b/Leaf-Server/minecraft-patches/features/0032-Linear-region-file-format.patch new file mode 100644 index 00000000..c200d816 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0032-Linear-region-file-format.patch @@ -0,0 +1,368 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Sun, 30 Jun 2024 00:35:19 +0800 +Subject: [PATCH] Linear region file format + +Original license: MIT +Original project: https://github.com/LuminolMC/Luminol + +Linear is a region file format that uses ZSTD compression instead of +ZLIB. +This format saves about 50% of disk space. +Documentation: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +index a814512fcfb85312474ae2c2c21443843bf57831..f80c75c561313625b694b433692aa429b8f8fde9 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +@@ -8,9 +8,9 @@ public interface ChunkSystemRegionFileStorage { + + public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ); + +- public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); ++ public org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // LinearPaper + +- public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; ++ public org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // LinearPaper + + public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( + final int chunkX, final int chunkZ, final CompoundTag compound +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +index 1acea58838f057ab87efd103cbecb6f5aeaef393..48a6d8b534943393c26180fbf341b77bd2d5bc48 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +@@ -1462,7 +1462,7 @@ public final class MoonriseRegionFileIO { + + public static interface IORunnable { + +- public void run(final RegionFile regionFile) throws IOException; ++ public void run(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException; // LinearPaper + + } + } +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +index 51c126735ace8fdde89ad97b5cab62f244212db0..4046f0aaa153e00277bf14f009fbe14aa8859fec 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +@@ -8,5 +8,5 @@ public interface ChunkSystemChunkBuffer { + + public void moonrise$setWriteOnClose(final boolean value); + +- public void moonrise$write(final RegionFile regionFile) throws IOException; ++ public void moonrise$write(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException; // LinearPaper + } +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 449cfd4f6282f28d7470403c0063874b906a6003..742835ecfa0cba6dc3114dbe584409183ade9228 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -941,10 +941,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> progressMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>()); + volatile Component status = Component.translatable("optimizeWorld.stage.counting"); +- static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); ++ static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.(linear | mca)$"); // LinearPaper + final DimensionDataStorage overworldDataStorage; + + public WorldUpgrader( +@@ -261,7 +261,7 @@ public class WorldUpgrader implements AutoCloseable { + } + + private static List getAllChunkPositions(RegionStorageInfo regionStorageInfo, Path path) { +- File[] files = path.toFile().listFiles((directory, filename) -> filename.endsWith(".mca")); ++ File[] files = path.toFile().listFiles((directory, filename) -> filename.endsWith(".linear") || filename.endsWith(".mca")); // LinearPaper + if (files == null) { + return List.of(); + } else { +@@ -274,7 +274,7 @@ public class WorldUpgrader implements AutoCloseable { + int i1 = Integer.parseInt(matcher.group(2)) << 5; + List list1 = Lists.newArrayList(); + +- try (RegionFile regionFile = new RegionFile(regionStorageInfo, file.toPath(), path, true)) { ++ try (org.stupidcraft.linearpaper.region.IRegionFile regionFile = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(regionStorageInfo, file.toPath(), path, true)) { // LinearPaper + for (int i2 = 0; i2 < 32; i2++) { + for (int i3 = 0; i3 < 32; i3++) { + ChunkPos chunkPos = new ChunkPos(i2 + i, i3 + i1); +@@ -322,7 +322,7 @@ public class WorldUpgrader implements AutoCloseable { + + protected abstract boolean tryProcessOnePosition(T chunkStorage, ChunkPos chunkPos, ResourceKey dimension); + +- private void onFileFinished(RegionFile regionFile) { ++ private void onFileFinished(org.stupidcraft.linearpaper.region.IRegionFile regionFile) { // LinearPaper + if (WorldUpgrader.this.recreateRegionFiles) { + if (this.previousWriteFuture != null) { + this.previousWriteFuture.join(); +@@ -424,7 +424,7 @@ public class WorldUpgrader implements AutoCloseable { + } + } + +- record FileToUpgrade(RegionFile file, List chunksToUpgrade) { ++ record FileToUpgrade(org.stupidcraft.linearpaper.region.IRegionFile file, List chunksToUpgrade) { // LinearPaper + } + + class PoiUpgrader extends WorldUpgrader.SimpleRegionStorageUpgrader { +diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java +index 231d1905092532acf7e632197ba0e727adc4b1d7..dac802a8c70ff8ab96c000a48e1c5e1095ef7a79 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -22,7 +22,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler; + import net.minecraft.world.level.ChunkPos; + import org.slf4j.Logger; + +-public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system ++public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile, org.stupidcraft.linearpaper.region.IRegionFile { // Paper - rewrite chunk system // LinearPaper + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails + private static final int SECTOR_BYTES = 4096; +@@ -124,7 +124,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } + + // note: only call for CHUNK regionfiles +- boolean recalculateHeader() throws IOException { ++ public boolean recalculateHeader() throws IOException { // LinearPaper - make public + if (!this.canRecalcHeader) { + return false; + } +@@ -786,7 +786,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } + } + +- protected synchronized void write(ChunkPos chunkPos, ByteBuffer chunkData) throws IOException { ++ public synchronized void write(ChunkPos chunkPos, ByteBuffer chunkData) throws IOException { // LinearPaper - protected -> public + int offsetIndex = getOffsetIndex(chunkPos); + int i = this.offsets.get(offsetIndex); + int sectorNumber = getSectorNumber(i); +@@ -904,7 +904,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } + + @Override +- public final void moonrise$write(final RegionFile regionFile) throws IOException { ++ public final void moonrise$write(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException { // LinearPaper + regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count)); + } + // Paper end - rewrite chunk system +@@ -970,11 +970,11 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + return (x & 31) + (z & 31) * 32; + } + +- synchronized boolean isOversized(int x, int z) { ++ public synchronized boolean isOversized(int x, int z) { // LinearPaper - make public + return this.oversized[getChunkIndex(x, z)] == 1; + } + +- synchronized void setOversized(int x, int z, boolean oversized) throws IOException { ++ public synchronized void setOversized(int x, int z, boolean oversized) throws IOException { // LinearPaper - make public + final int offset = getChunkIndex(x, z); + boolean previous = this.oversized[offset] == 1; + this.oversized[offset] = (byte) (oversized ? 1 : 0); +@@ -1013,7 +1013,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt"); + } + +- synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException { ++ public synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException { // LinearPaper - make public + Path file = getOversizedFile(x, z); + try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new java.util.zip.InflaterInputStream(Files.newInputStream(file))))) { + return net.minecraft.nbt.NbtIo.read((java.io.DataInput) out); +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 7fff86a4956f59b2f4a9f7e283256879c034c1b8..58d9840d2557fbb9b357bf4e3d5ad19ef40d73c9 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -18,7 +18,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper + public static final String ANVIL_EXTENSION = ".mca"; + private static final int MAX_CACHE_SIZE = 256; +- public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); ++ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); // LinearPaper + private final RegionStorageInfo info; + private final Path folder; + private final boolean sync; +@@ -33,7 +33,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + @Nullable + public static ChunkPos getRegionFileCoordinates(Path file) { + String fileName = file.getFileName().toString(); +- if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { ++ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca") || !fileName.endsWith(".linear")) { // LinearPaper + return null; + } + +@@ -57,7 +57,11 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + private static final int REGION_SHIFT = 5; + private static final int MAX_NON_EXISTING_CACHE = 1024 * 4; + private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); ++ // Leaf start - Linear region format + private static String getRegionFileName(final int chunkX, final int chunkZ) { ++ if (org.dreeam.leaf.config.modules.misc.RegionFormatConfig.regionFormatType == org.stupidcraft.linearpaper.region.EnumRegionFileExtension.LINEAR) { ++ return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".linear"; ++ } + return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca"; + } + +@@ -93,15 +97,15 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + @Override +- public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { ++ public synchronized final org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // LinearPaper + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT)); + } + + @Override +- public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { ++ public synchronized final org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // LinearPaper + final long key = ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + +- RegionFile ret = this.regionCache.getAndMoveToFirst(key); ++ org.stupidcraft.linearpaper.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // LinearPaper + if (ret != null) { + return ret; + } +@@ -125,7 +129,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + FileUtil.createDirectoriesSafe(this.folder); + +- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); ++ ret = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // LinearPaper + + this.regionCache.putAndMoveToFirst(key, ret); + +@@ -144,7 +148,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); +- final RegionFile regionFile = this.getRegionFile(pos); ++ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(pos); // LinearPaper + + // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input + // (and, the regionfile parameter is unused for writing until the write call) +@@ -178,7 +182,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + ) throws IOException { + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { +- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // LinearPaper + if (regionFile != null) { + regionFile.clear(pos); + } // else: didn't exist +@@ -193,7 +197,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( + final int chunkX, final int chunkZ + ) throws IOException { +- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // LinearPaper + + final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); + +@@ -237,7 +241,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + // Paper end - rewrite chunk system + // Paper start - rewrite chunk system +- public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { ++ public org.stupidcraft.linearpaper.region.IRegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { // LinearPaper + return this.getRegionFile(chunkcoordintpair, false); + } + // Paper end - rewrite chunk system +@@ -249,7 +253,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers + } + +- @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit ++ @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private org.stupidcraft.linearpaper.region.IRegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit // LinearPaper + // Paper start - rewrite chunk system + if (existingOnly) { + return this.moonrise$getRegionFileIfExists(chunkPos.x, chunkPos.z); +@@ -257,7 +261,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + synchronized (this) { + final long key = ChunkPos.asLong(chunkPos.x >> REGION_SHIFT, chunkPos.z >> REGION_SHIFT); + +- RegionFile ret = this.regionCache.getAndMoveToFirst(key); ++ org.stupidcraft.linearpaper.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // LinearPaper + if (ret != null) { + return ret; + } +@@ -266,13 +270,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + this.regionCache.removeLast().close(); + } + +- final Path regionPath = this.folder.resolve(getRegionFileName(chunkPos.x, chunkPos.z)); ++ final Path regionPath = this.folder.resolve(getRegionFileName(chunkPos.x, chunkPos.z)); // LinearPaper + + this.createRegionFile(key); + + FileUtil.createDirectoriesSafe(this.folder); + +- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); ++ ret = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // LinearPaper + + this.regionCache.putAndMoveToFirst(key, ret); + +@@ -292,7 +296,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Gale end - branding changes + } + +- private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { ++ private static CompoundTag readOversizedChunk(org.stupidcraft.linearpaper.region.IRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // LinearPaper + synchronized (regionfile) { + try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) { + CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); +@@ -327,7 +331,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + @Nullable + public CompoundTag read(ChunkPos chunkPos) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionFile = this.getRegionFile(chunkPos, true); ++ org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(chunkPos, true); // LinearPaper + if (regionFile == null) { + return null; + } +@@ -366,7 +370,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionFile = this.getRegionFile(chunkPos, true); ++ org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(chunkPos, true); // LinearPaper + if (regionFile == null) { + return; + } +@@ -380,7 +384,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + public void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException { // Paper - rewrite chunk system - public +- RegionFile regionFile = this.getRegionFile(chunkPos, chunkData == null); // CraftBukkit // Paper - rewrite chunk system ++ org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(chunkPos, chunkData == null); // CraftBukkit // Paper - rewrite chunk system // LinearPaper + // Paper start - rewrite chunk system + if (regionFile == null) { + // if the RegionFile doesn't exist, no point in deleting from it +@@ -410,7 +414,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Paper start - rewrite chunk system + synchronized (this) { + final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); +- for (final RegionFile regionFile : this.regionCache.values()) { ++ for (final org.stupidcraft.linearpaper.region.IRegionFile regionFile : this.regionCache.values()) { // LinearPaper + try { + regionFile.close(); + } catch (final IOException ex) { +@@ -426,7 +430,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Paper start - rewrite chunk system + synchronized (this) { + final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); +- for (final RegionFile regionFile : this.regionCache.values()) { ++ for (final org.stupidcraft.linearpaper.region.IRegionFile regionFile : this.regionCache.values()) { // LinearPaper + try { + regionFile.flush(); + } catch (final IOException ex) { diff --git a/Leaf-Server/minecraft-patches/features/0033-Plazma-Add-some-missing-Pufferfish-configurations.patch b/Leaf-Server/minecraft-patches/features/0033-Plazma-Add-some-missing-Pufferfish-configurations.patch new file mode 100644 index 00000000..3ec52b88 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0033-Plazma-Add-some-missing-Pufferfish-configurations.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlphaKR93 +Date: Wed, 27 Sep 2023 18:29:51 +0900 +Subject: [PATCH] Plazma: Add some missing Pufferfish configurations + +Original license: MIT +Original project: https://github.com/PlazmaMC/PlazmaBukkit + +Add Pufferfish DAB support for Camel, Sniffer +https://github.com/pufferfish-gg/Pufferfish/issues/83 + +diff --git a/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +index d840577023d42dc986e2b811382dfc433083ffb3..6aa8c94f6ce00cd948cd5edef80906f74c19ae25 100644 +--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java ++++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java +@@ -161,8 +161,10 @@ public class Armadillo extends Animal { + return ArmadilloAi.makeBrain(this.brainProvider().makeBrain(dynamic)); + } + ++ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + ((Brain)this.brain).tick(level, this); + ArmadilloAi.updateActivity(this); + if (this.isAlive() && !this.isBaby() && --this.scuteTime <= 0) { +diff --git a/net/minecraft/world/entity/animal/camel/Camel.java b/net/minecraft/world/entity/animal/camel/Camel.java +index 64ff0d2923f16a567aa753cad028a1b21c20101b..a7168edb338c5a77c884e9eef1e48bcdc3623fa4 100644 +--- a/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/net/minecraft/world/entity/animal/camel/Camel.java +@@ -155,9 +155,11 @@ public class Camel extends AbstractHorse { + return pose == Pose.SITTING ? SITTING_DIMENSIONS.scale(this.getAgeScale()) : super.getDefaultDimensions(pose); + } + ++ private int behaviorTick = 0; // Leaf - Plazma - Add missing Pufferfish configurations + @Override + protected void customServerAiStep(ServerLevel level) { + Brain brain = this.getBrain(); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + ((Brain)brain).tick(level, this); + CamelAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/net/minecraft/world/entity/animal/sniffer/Sniffer.java +index 68751f7ed123c3e99f56259ccc23121661f89bc1..fec7ecfe6e692ee74762a6a53e51f92cf66a9177 100644 +--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -482,8 +482,10 @@ public class Sniffer extends Animal { + return Brain.provider(SnifferAi.MEMORY_TYPES, SnifferAi.SENSOR_TYPES); + } + ++ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + this.getBrain().tick(level, this); + SnifferAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/monster/breeze/Breeze.java b/net/minecraft/world/entity/monster/breeze/Breeze.java +index fb643596bd5fb12e4cd323706f51a479d78a5455..542baa54c8b5fbe2d9d71e3ee5d2bd62b69baccc 100644 +--- a/net/minecraft/world/entity/monster/breeze/Breeze.java ++++ b/net/minecraft/world/entity/monster/breeze/Breeze.java +@@ -228,8 +228,10 @@ public class Breeze extends Monster { + return pos.closerThan(center, 4.0, 10.0); + } + ++ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + this.getBrain().tick(level, this); + BreezeAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/monster/creaking/Creaking.java b/net/minecraft/world/entity/monster/creaking/Creaking.java +index 2c6833753950f1bb0941b0cbe54bebddb84b137d..07b60f61b4b676cab2072ad0cf1cf94fed9b44d6 100644 +--- a/net/minecraft/world/entity/monster/creaking/Creaking.java ++++ b/net/minecraft/world/entity/monster/creaking/Creaking.java +@@ -230,8 +230,10 @@ public class Creaking extends Monster { + return (Brain)super.getBrain(); + } + ++ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations + @Override + protected void customServerAiStep(ServerLevel level) { ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + this.getBrain().tick((ServerLevel)this.level(), this); + CreakingAi.updateActivity(this); + } diff --git a/patches/server/0047-Plazma-Add-missing-purpur-configuration-options.patch b/Leaf-Server/minecraft-patches/features/0034-Plazma-Add-missing-purpur-configuration-options.patch similarity index 59% rename from patches/server/0047-Plazma-Add-missing-purpur-configuration-options.patch rename to Leaf-Server/minecraft-patches/features/0034-Plazma-Add-missing-purpur-configuration-options.patch index 2e809ec2..0d15b239 100644 --- a/patches/server/0047-Plazma-Add-missing-purpur-configuration-options.patch +++ b/Leaf-Server/minecraft-patches/features/0034-Plazma-Add-missing-purpur-configuration-options.patch @@ -8,13 +8,13 @@ Original project: https://github.com/PlazmaMC/PlazmaBukkit Add more Purpur configurable options for entities -diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -index b9f39d873e243da34aafa9f285978d2d9f6b0100..349c444b0c8acc509616de6bc809e2bad9c8e115 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -@@ -160,6 +160,18 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS +diff --git a/net/minecraft/world/entity/animal/allay/Allay.java b/net/minecraft/world/entity/animal/allay/Allay.java +index 10d2a1138d814b83ce4233205a7f0ab2ed1f399d..86977d359f8eb8fcb2e441a9311f663337175113 100644 +--- a/net/minecraft/world/entity/animal/allay/Allay.java ++++ b/net/minecraft/world/entity/animal/allay/Allay.java +@@ -179,6 +179,18 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS } - // Purpur end + // Purpur end - Configurable entity base attributes + // Leaf start - Plazma + @Override @@ -30,14 +30,14 @@ index b9f39d873e243da34aafa9f285978d2d9f6b0100..349c444b0c8acc509616de6bc809e2ba + @Override protected Brain.Provider brainProvider() { - return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index c0595b595905e4f876e013a16e75204d9ceeead9..4b54783c004bbf2e6fab3fa65eb64444d009c5bc 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -96,6 +96,18 @@ public class Camel extends AbstractHorse { + return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); +diff --git a/net/minecraft/world/entity/animal/camel/Camel.java b/net/minecraft/world/entity/animal/camel/Camel.java +index a7168edb338c5a77c884e9eef1e48bcdc3623fa4..96836a1d5edda7e275fe1859e3bd661e7be1266b 100644 +--- a/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/net/minecraft/world/entity/animal/camel/Camel.java +@@ -95,6 +95,18 @@ public class Camel extends AbstractHorse { } - // Purpur end + // Purpur end - Make entity breeding times configurable + // Leaf start - Plazma + @Override @@ -52,25 +52,26 @@ index c0595b595905e4f876e013a16e75204d9ceeead9..4b54783c004bbf2e6fab3fa65eb64444 + // Leaf start - Plazma + @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -155,7 +167,7 @@ public class Camel extends AbstractHorse { - protected void customServerAiStep(ServerLevel world) { - Brain behaviorcontroller = (Brain) this.getBrain(); // CraftBukkit - decompile error - + public void addAdditionalSaveData(CompoundTag compound) { + super.addAdditionalSaveData(compound); +@@ -159,7 +171,7 @@ public class Camel extends AbstractHorse { + @Override + protected void customServerAiStep(ServerLevel level) { + Brain brain = this.getBrain(); - if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations // Leaf - Plazma - Add missing purpur configurations - behaviorcontroller.tick(world, this); + ((Brain)brain).tick(level, this); CamelAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index d8fe42996c8ba3d44e31197b24294b86b845e2a8..6575e5f5e574db5bb4243a9bed426f3316e3e2ec 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -@@ -163,6 +163,23 @@ public class Frog extends Animal implements VariantHolder> { + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java +index 2c28e97e76155fe4de309422f4913c1269972ff4..cb8e3e9f0dc7fa8673397e594dea6fd5da3e74aa 100644 +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java +@@ -169,6 +169,24 @@ public class Frog extends Animal implements VariantHolder> { + return this.level().purpurConfig.frogBreedingTicks; } - // Purpur end - + // Purpur end - Make entity breeding times configurable ++ + // Leaf start - Plazma + @Override + public boolean isSensitiveToWater() { @@ -88,16 +89,16 @@ index d8fe42996c8ba3d44e31197b24294b86b845e2a8..6575e5f5e574db5bb4243a9bed426f33 + } + // Leaf end - Plazma + - public int getPurpurBreedTime() { - return this.level().purpurConfig.frogBreedingTicks; + @Override + protected Brain.Provider brainProvider() { + return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); +diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java +index 669f7d6e5f481fb209e800c8e4acc52cf0c6dfce..ec6897093948fc3641138dcf82b7834083fe37fa 100644 +--- a/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -105,6 +105,23 @@ public class Tadpole extends AbstractFish { } -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index 72a858ac4ebcfa7520a74bd52501521af64ffcab..d20d7eb89ead872ab90a5b43d6298d45b657c902 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -93,6 +93,23 @@ public class Tadpole extends AbstractFish { - } - // Purpur end + // Purpur end - Ridables + // Leaf start - Plazma + @Override @@ -117,15 +118,15 @@ index 72a858ac4ebcfa7520a74bd52501521af64ffcab..d20d7eb89ead872ab90a5b43d6298d45 + // Leaf end - Plazma + @Override - protected PathNavigation createNavigation(Level world) { - return new WaterBoundPathNavigation(this, world); -diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index dc0f8bf73170f5012a5d4af3a7e823352d684bd9..47bbb60edcca9840f1bc70b90bd8f2a4dee45a21 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -116,6 +116,18 @@ public class Sniffer extends Animal { + protected PathNavigation createNavigation(Level level) { + return new WaterBoundPathNavigation(this, level); +diff --git a/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/net/minecraft/world/entity/animal/sniffer/Sniffer.java +index fec7ecfe6e692ee74762a6a53e51f92cf66a9177..16cdd65770b8d70e6bde3d4407d9a71b5d2da8c8 100644 +--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -118,6 +118,18 @@ public class Sniffer extends Animal { } - // Purpur end + // Purpur end - Make entity breeding times configurable + // Leaf start - Plazma + @Override @@ -142,22 +143,54 @@ index dc0f8bf73170f5012a5d4af3a7e823352d684bd9..47bbb60edcca9840f1bc70b90bd8f2a4 @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); -@@ -494,7 +506,7 @@ public class Sniffer extends Animal { +@@ -485,7 +497,7 @@ public class Sniffer extends Animal { private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations @Override - protected void customServerAiStep(ServerLevel world) { + protected void customServerAiStep(ServerLevel level) { +- if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations ++ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations // Leaf - Plazma - Add missing purpur configuration + this.getBrain().tick(level, this); + SnifferAi.updateActivity(this); + super.customServerAiStep(level); +diff --git a/net/minecraft/world/entity/monster/creaking/Creaking.java b/net/minecraft/world/entity/monster/creaking/Creaking.java +index 07b60f61b4b676cab2072ad0cf1cf94fed9b44d6..f6d58fac537ee1f40d311c3146e89b82b01a6a58 100644 +--- a/net/minecraft/world/entity/monster/creaking/Creaking.java ++++ b/net/minecraft/world/entity/monster/creaking/Creaking.java +@@ -131,6 +131,18 @@ public class Creaking extends Monster { + } + // Purpur end - Configurable entity base attributes + ++ // Leaf start - Plazma ++ @Override ++ public boolean isSensitiveToWater() { ++ return level().purpurConfig.wardenTakeDamageFromWater; ++ } ++ ++ @Override ++ public boolean isAlwaysExperienceDropper() { ++ return level().purpurConfig.wardenAlwaysDropExp; ++ } ++ // Leaf end - Plazma ++ + @Override + protected BodyRotationControl createBodyControl() { + return new Creaking.CreakingBodyRotationControl(this); +@@ -233,7 +245,7 @@ public class Creaking extends Monster { + private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations + @Override + protected void customServerAiStep(ServerLevel level) { - if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations // Leaf - Plazma - Add missing purpur configurations - this.getBrain().tick(world, this); - SnifferAi.updateActivity(this); - super.customServerAiStep(world); -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 a429de1fdb4823b1f1df42dfe213853cc06ef1c2..ef1bbd47d22a808b3f5bfc715eda3f4d13bd80aa 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 -@@ -151,6 +151,23 @@ public class Warden extends Monster implements VibrationSystem { + this.getBrain().tick((ServerLevel)this.level(), this); + CreakingAi.updateActivity(this); } - // Purpur end +diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java +index 3a43790fb91e778f4fc0730aecd0dde4a6d301c8..7aae3d82ab5dd66f3e85d0a1bf66c9ddaa3d65e9 100644 +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -153,6 +153,23 @@ public class Warden extends Monster implements VibrationSystem { + } + // Purpur end - Ridables + // Leaf start - Plazma + @Override @@ -177,31 +210,31 @@ index a429de1fdb4823b1f1df42dfe213853cc06ef1c2..ef1bbd47d22a808b3f5bfc715eda3f4d + // Leaf end - Plazma + @Override - public Packet getAddEntityPacket(ServerEntity entityTrackerEntry) { - return new ClientboundAddEntityPacket(this, entityTrackerEntry, this.hasPose(Pose.EMERGING) ? 1 : 0); -@@ -300,7 +317,7 @@ public class Warden extends Monster implements VibrationSystem { + public Packet getAddEntityPacket(ServerEntity entity) { + return new ClientboundAddEntityPacket(this, entity, this.hasPose(Pose.EMERGING) ? 1 : 0); +@@ -307,7 +324,7 @@ public class Warden extends Monster implements VibrationSystem { private int behaviorTick = 0; // Pufferfish @Override - protected void customServerAiStep(ServerLevel world) { + protected void customServerAiStep(ServerLevel level) { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Leaf - Plazma - Add missing purpur configurations - this.getBrain().tick(world, this); - super.customServerAiStep(world); + this.getBrain().tick(level, this); + super.customServerAiStep(level); if ((this.tickCount + this.getId()) % 120 == 0) { -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -index 1f4cc08e84a23213bb9786ea09ad77caeec2d336..06d45906924d6470d75445dea23c9b90dac6545f 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -@@ -46,7 +46,7 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto +diff --git a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java +index b230955ae880d84fde40b4feffa5caf3c4449eb7..ff705de4cd2b126c1a6902a7225b6fef7a6b34d6 100644 +--- a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java ++++ b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java +@@ -27,7 +27,7 @@ import net.minecraft.world.level.storage.loot.LootTable; - public AbstractChestBoat(EntityType type, Level world, Supplier itemSupplier) { - super(type, world, itemSupplier); -- this.itemStacks = NonNullList.withSize(27, ItemStack.EMPTY); -+ this.itemStacks = NonNullList.withSize(org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9, ItemStack.EMPTY); // Leaf - Plazma - } - - @Override -@@ -142,7 +142,7 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto + public abstract class AbstractChestBoat extends AbstractBoat implements HasCustomInventoryScreen, ContainerEntity { + private static final int CONTAINER_SIZE = 27; +- private NonNullList itemStacks = NonNullList.withSize(27, ItemStack.EMPTY); ++ private NonNullList itemStacks = NonNullList.withSize(org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9, ItemStack.EMPTY); // Leaf - Plazma + @Nullable + private ResourceKey lootTable; + private long lootTableSeed; +@@ -118,7 +118,7 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto @Override public int getContainerSize() { @@ -210,11 +243,11 @@ index 1f4cc08e84a23213bb9786ea09ad77caeec2d336..06d45906924d6470d75445dea23c9b90 } @Override -diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -index 4401cd919951e92f613e0899f243b0105dd852b7..05d77ccd9bf8ce0534fbac7d4354536e3187229f 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -@@ -321,6 +321,7 @@ public class PurpurConfig { +diff --git a/org/purpurmc/purpur/PurpurConfig.java b/org/purpurmc/purpur/PurpurConfig.java +index aca85686fb0b9ec78d4e83574bbfe6313785f40f..b084fcbe57a1f57e97919aaa0b0c7d4ab5436bdf 100644 +--- a/org/purpurmc/purpur/PurpurConfig.java ++++ b/org/purpurmc/purpur/PurpurConfig.java +@@ -322,6 +322,7 @@ public class PurpurConfig { } public static int barrelRows = 3; @@ -222,7 +255,7 @@ index 4401cd919951e92f613e0899f243b0105dd852b7..05d77ccd9bf8ce0534fbac7d4354536e public static boolean enderChestSixRows = false; public static boolean enderChestPermissionRows = false; public static boolean cryingObsidianValidForPortalFrame = false; -@@ -363,6 +364,7 @@ public class PurpurConfig { +@@ -364,6 +365,7 @@ public class PurpurConfig { case 1 -> 9; default -> 27; }); @@ -230,11 +263,11 @@ index 4401cd919951e92f613e0899f243b0105dd852b7..05d77ccd9bf8ce0534fbac7d4354536e enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696ebcc404614 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -1128,12 +1128,20 @@ public class PurpurWorldConfig { +diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java +index fd5a5b9cb9958e96ecfeb4846e290c756265b300..e45406d6ec6f075c9c2008bd6cb61dba824dc553 100644 +--- a/org/purpurmc/purpur/PurpurWorldConfig.java ++++ b/org/purpurmc/purpur/PurpurWorldConfig.java +@@ -1137,12 +1137,20 @@ public class PurpurWorldConfig { public boolean allayControllable = true; public double allayMaxHealth = 20.0D; public double allayScale = 1.0D; @@ -255,7 +288,7 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb } public boolean armadilloRidable = false; -@@ -1283,6 +1291,10 @@ public class PurpurWorldConfig { +@@ -1292,6 +1300,10 @@ public class PurpurWorldConfig { public double camelMovementSpeedMin = 0.09D; public double camelMovementSpeedMax = 0.09D; public int camelBreedingTicks = 6000; @@ -266,7 +299,7 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb private void camelSettings() { camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater); camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin); -@@ -1292,6 +1304,10 @@ public class PurpurWorldConfig { +@@ -1301,6 +1313,10 @@ public class PurpurWorldConfig { camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin); camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax); camelBreedingTicks = getInt("mobs.camel.breeding-delay-ticks", camelBreedingTicks); @@ -277,7 +310,28 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb } public boolean catRidable = false; -@@ -1750,12 +1766,22 @@ public class PurpurWorldConfig { +@@ -1447,12 +1463,20 @@ public class PurpurWorldConfig { + public boolean creakingControllable = true; + public double creakingMaxHealth = 1.0D; + public double creakingScale = 1.0D; ++ // Leaf start - Plazma - Add missing purpur config options ++ public boolean creakingTakeDamageFromWater = false; ++ public boolean creakingAlwaysDropExp = false; ++ // Leaf end - Plazma - Add missing purpur config options + private void creakingSettings() { + creakingRidable = getBoolean("mobs.creaking.ridable", creakingRidable); + creakingRidableInWater = getBoolean("mobs.creaking.ridable-in-water", creakingRidableInWater); + creakingControllable = getBoolean("mobs.creaking.controllable", creakingControllable); + creakingMaxHealth = getDouble("mobs.creaking.attributes.max_health", creakingMaxHealth); + creakingScale = Mth.clamp(getDouble("mobs.creaking.attributes.scale", creakingScale), 0.0625D, 16.0D); ++ // Leaf start - Plazma - Add missing purpur config options ++ creakingTakeDamageFromWater = getBoolean("mobs.creaking.takes-damage-from-water", creakingTakeDamageFromWater); ++ creakingAlwaysDropExp = getBoolean("mobs.creaking.always-drop-exp", creakingAlwaysDropExp); ++ // Leaf end - Plazma - Add missing purpur config options + } + + public boolean creeperRidable = false; +@@ -1759,12 +1783,22 @@ public class PurpurWorldConfig { public boolean frogControllable = true; public float frogRidableJumpHeight = 0.65F; public int frogBreedingTicks = 6000; @@ -300,7 +354,7 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb } public boolean ghastRidable = false; -@@ -2764,6 +2790,10 @@ public class PurpurWorldConfig { +@@ -2773,6 +2807,10 @@ public class PurpurWorldConfig { public double snifferMaxHealth = 14.0D; public double snifferScale = 1.0D; public int snifferBreedingTicks = 6000; @@ -311,7 +365,7 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb private void snifferSettings() { snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable); snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater); -@@ -2771,6 +2801,10 @@ public class PurpurWorldConfig { +@@ -2780,6 +2818,10 @@ public class PurpurWorldConfig { snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth); snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D); snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", snifferBreedingTicks); @@ -322,7 +376,7 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb } public boolean squidRidable = false; -@@ -2872,10 +2906,20 @@ public class PurpurWorldConfig { +@@ -2881,10 +2923,20 @@ public class PurpurWorldConfig { public boolean tadpoleRidable = false; public boolean tadpoleRidableInWater = true; public boolean tadpoleControllable = true; @@ -343,7 +397,7 @@ index 8c9a6084f09f644bf22ac98edac797eea753277f..0ff935f36f65d2274156e134684696eb } public boolean traderLlamaRidable = false; -@@ -3104,10 +3148,20 @@ public class PurpurWorldConfig { +@@ -3113,10 +3165,20 @@ public class PurpurWorldConfig { public boolean wardenRidable = false; public boolean wardenRidableInWater = true; public boolean wardenControllable = true; diff --git a/Leaf-Server/minecraft-patches/features/0035-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch b/Leaf-Server/minecraft-patches/features/0035-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch new file mode 100644 index 00000000..dbaec233 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0035-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Wed, 15 Nov 2023 23:39:36 -0300 +Subject: [PATCH] SparklyPaper: Skip "distanceToSqr" call in + "ServerEntity#sendChanges" if the delta movement hasn't changed + +Original project: https://github.com/SparklyPower/SparklyPaper + +The "distanceToSqr" call is a bit expensive, so avoiding it is pretty nice, around ~15% calls are skipped with this check + +We could also check if the x,y,z coordinates are equal, but for now, let's just keep the identity check, which also helps us since Minecraft's code does reuse the original delta movement Vec3 object + +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java +index d985555a029d06ffc73dd10115df47b83c9afafd..ddf2a5e2cfeaa666a081dd857d6a6003d65d0e00 100644 +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -201,6 +201,7 @@ public class ServerEntity { + + if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) { + Vec3 deltaMovement = this.entity.getDeltaMovement(); ++ if (deltaMovement != this.lastSentMovement) { // SparklyPaper start - skip distanceToSqr call in ServerEntity#sendChanges if the delta movement hasn't changed + double d = deltaMovement.distanceToSqr(this.lastSentMovement); + if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) { + this.lastSentMovement = deltaMovement; +@@ -218,6 +219,7 @@ public class ServerEntity { + this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement)); + } + } ++ } // SparklyPaper end + } + + if (packet != null) { diff --git a/Leaf-Server/minecraft-patches/features/0036-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch b/Leaf-Server/minecraft-patches/features/0036-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch new file mode 100644 index 00000000..dd6cb21a --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0036-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Fri, 17 Nov 2023 14:22:41 -0300 +Subject: [PATCH] SparklyPaper: Skip "MapItem#update()" if the map does not + have the CraftMapRenderer present + +Original project: https://github.com/SparklyPower/SparklyPaper + +Optimizes "image in map" maps, without requiring the map to be locked, which some old map plugins may not do + +This has the disadvantage that the vanilla map data will never be updated while the CraftMapRenderer is not present, but that's not a huuuge problem for u + +diff --git a/net/minecraft/world/item/MapItem.java b/net/minecraft/world/item/MapItem.java +index 309392d414ecbe60474abd0af534184740951707..fd8418fc1487b0669907569142955887257f81c0 100644 +--- a/net/minecraft/world/item/MapItem.java ++++ b/net/minecraft/world/item/MapItem.java +@@ -278,7 +278,7 @@ public class MapItem extends Item { + savedData.tickCarriedBy(player, stack); + } + +- if (!savedData.locked && (isSelected || entity instanceof Player && ((Player)entity).getOffhandItem() == stack)) { ++ if (!savedData.locked && (!org.dreeam.leaf.config.modules.opt.SkipMapItemDataUpdates.enabled || savedData.mapView.getRenderers().stream().anyMatch(mapRenderer -> mapRenderer.getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class)) && (isSelected || entity instanceof Player && ((Player)entity).getOffhandItem() == stack)) { // SparklyPaper - don't update maps if they don't have the CraftMapRenderer in the render list + this.update(level, entity, savedData); + } + } diff --git a/Leaf-Server/minecraft-patches/features/0037-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch b/Leaf-Server/minecraft-patches/features/0037-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch new file mode 100644 index 00000000..be7696aa --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0037-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Sun, 19 Nov 2023 12:35:16 -0300 +Subject: [PATCH] SparklyPaper: Skip EntityScheduler's executeTick checks if + there isn't any tasks to be run + +Original project: https://github.com/SparklyPower/SparklyPaper + +On each tick, Paper runs EntityScheduler's executeTick of each entity. This is a bit expensive, due to ArrayDeque's size() call because it ain't a simple "get the current queue size" function, due to the thread checks, and because it needs to iterate all entities in all worlds. + +To avoid the hefty ArrayDeque's size() call, we check if we *really* need to execute the executeTick, by adding all entities with scheduled tasks to a global set. + +Most entities won't have any scheduled tasks, so this is a nice performance bonus. These optimizations, however, wouldn't work in a Folia environment, but because in SparklyPaper executeTick is always executed on the main thread, it ain't an issue for us (yay). + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 742835ecfa0cba6dc3114dbe584409183ade9228..2ff6059d6bd3fe7c6814603c2ba0e6ccffe1873a 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -291,6 +291,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1680,6 +1681,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) { + if (entity.isRemoved()) { +@@ -1691,6 +1704,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 23 Nov 2023 14:36:47 -0300 +Subject: [PATCH] SparklyPaper: Optimize "canSee" checks + +Original project: https://github.com/SparklyPower/SparklyPaper + +The "canSee" checks is in a hot path, invoked by each entity for each player on the server if they are in tracking range, so optimizing it is pretty nice + +First, we change the original "HashMap" to fastutil's "Object2ObjectOpenHashMap", because the containsKey throughput is better + +Then, we add a "isEmpty()" check before attempting to check if the map contains something + +This seems stupid, but it does seem that it improves the performance a bit, and it makes sense, "containsKey(...)" does not attempt to check the map size before attempting to check if the map contains the key + +We also create a "canSee" method tailored for "ChunkMap#updatePlayer()", a method without the equals check (the "updatePlayer()" already checks if the entity is the same entity) because the CraftPlayer's `equals()` check is a *bit* expensive compared to only checking the object's identity, and because the identity has already been check, we don't need to check it twice. + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 6a9fdb6329f177aca1274336a8e5be70ca3ce931..5d9d233e3a568aa6297ed9c703fa450f98158602 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -1225,7 +1225,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); + // Paper end - Configurable entity tracking range by Y + // CraftBukkit start - respect vanish API +- if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits ++ if (flag && !player.getBukkitEntity().canSeeChunkMapUpdatePlayer(this.entity.getBukkitEntity())) { // Paper - only consider hits // SparklyPaper - optimize canSee checks + flag = false; + } + // CraftBukkit end diff --git a/Leaf-Server/minecraft-patches/features/0039-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch b/Leaf-Server/minecraft-patches/features/0039-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch new file mode 100644 index 00000000..9649af63 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0039-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Fri, 23 Aug 2024 16:20:45 -0300 +Subject: [PATCH] SparklyPaper: Allow throttling hopper checks if the target + container is full + +Original project: https://github.com/SparklyPower/SparklyPaper + +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 276cb0dffaa253a6c13b4c68d8c703732118d0d1..23848df90e7127b1ec11d2aaac68fad17bda238a 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -419,6 +419,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } else { + Direction opposite = blockEntity.facing.getOpposite(); + if (isFullContainer(attachedContainer, opposite)) { ++ // Leaf start - Throttle hopper when full ++ if (org.dreeam.leaf.config.modules.opt.ThrottleHopperWhenFull.enabled && org.dreeam.leaf.config.modules.opt.ThrottleHopperWhenFull.skipTicks > 0) { ++ blockEntity.setCooldown(org.dreeam.leaf.config.modules.opt.ThrottleHopperWhenFull.skipTicks); ++ } ++ // Leaf end - Throttle hopper when full + return false; + } else { + // Paper start - Perf: Optimize Hoppers diff --git a/Leaf-Server/minecraft-patches/features/0040-Polpot-Make-egg-and-snowball-can-knockback-player.patch b/Leaf-Server/minecraft-patches/features/0040-Polpot-Make-egg-and-snowball-can-knockback-player.patch new file mode 100644 index 00000000..2be28182 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0040-Polpot-Make-egg-and-snowball-can-knockback-player.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lilingfengdev <145678359+lilingfengdev@users.noreply.github.com> +Date: Thu, 18 Jan 2024 13:30:02 +0800 +Subject: [PATCH] Polpot: Make egg and snowball can knockback player + +Original project: https://github.com/HaHaWTH/Polpot + +diff --git a/net/minecraft/world/entity/projectile/Snowball.java b/net/minecraft/world/entity/projectile/Snowball.java +index 1d399532c67c213c95c06837b0c7855384f1a25c..16fc473415872a626c130c90b1fc76c41a6b2856 100644 +--- a/net/minecraft/world/entity/projectile/Snowball.java ++++ b/net/minecraft/world/entity/projectile/Snowball.java +@@ -54,6 +54,12 @@ public class Snowball extends ThrowableItemProjectile { + Entity entity = result.getEntity(); + int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - Add configurable snowball damage + entity.hurt(this.damageSources().thrown(this, this.getOwner()), i); ++ // Leaf start - Polpot - Make snowball can knockback player ++ if (org.dreeam.leaf.config.modules.gameplay.Knockback.snowballCanKnockback && entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0000001F); ++ serverPlayer.knockback(0.4000000059604645D, this.getX() - entity.getX(), this.getZ() - entity.getZ()); ++ } ++ // Leaf end - Polpot - Make snowball can knockback player + } + + // Purpur start - options to extinguish fire blocks with snowballs - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire +diff --git a/net/minecraft/world/entity/projectile/ThrownEgg.java b/net/minecraft/world/entity/projectile/ThrownEgg.java +index 76481c0e77fc3a2e4be8eeb9de8d1e6de5507c64..46628c2cc23488b921f5ce1fa787712c996d9e21 100644 +--- a/net/minecraft/world/entity/projectile/ThrownEgg.java ++++ b/net/minecraft/world/entity/projectile/ThrownEgg.java +@@ -52,7 +52,14 @@ public class ThrownEgg extends ThrowableItemProjectile { + @Override + protected void onHitEntity(EntityHitResult result) { + super.onHitEntity(result); ++ net.minecraft.world.entity.Entity entity = result.getEntity(); // Polpot - make egg can knockback player + result.getEntity().hurt(this.damageSources().thrown(this, this.getOwner()), 0.0F); ++ // Leaf start - Polpot - Make egg can knockback player ++ if (org.dreeam.leaf.config.modules.gameplay.Knockback.eggCanKnockback && entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0000001F); ++ serverPlayer.knockback(0.4000000059604645D, this.getX() - entity.getX(), this.getZ() - entity.getZ()); ++ } ++ // Leaf end - Polpot - Make egg can knockback player + } + + @Override diff --git a/patches/server/0056-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch b/Leaf-Server/minecraft-patches/features/0041-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch similarity index 65% rename from patches/server/0056-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch rename to Leaf-Server/minecraft-patches/features/0041-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch index 8902372d..93e7589f 100644 --- a/patches/server/0056-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch +++ b/Leaf-Server/minecraft-patches/features/0041-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch @@ -4,10 +4,10 @@ Date: Mon, 19 Feb 2024 13:10:16 -0500 Subject: [PATCH] Redirect vanilla getProfiler in PathNavigationRegion -diff --git a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java -index 5375ea8dee8f74c843964824ad1a374e15711b60..82c304cf560e4dfd21e1cf9ba038f9029754d8b3 100644 ---- a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java -+++ b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java +diff --git a/net/minecraft/world/level/PathNavigationRegion.java b/net/minecraft/world/level/PathNavigationRegion.java +index 97a1cb8f30eb1668b1054912789bd100b96bee18..6de84cc2985de45a6375d255f49ddee4bf9776d5 100644 +--- a/net/minecraft/world/level/PathNavigationRegion.java ++++ b/net/minecraft/world/level/PathNavigationRegion.java @@ -150,4 +150,10 @@ public class PathNavigationRegion implements CollisionGetter { public int getHeight() { return this.level.getHeight(); diff --git a/patches/server/0058-Remove-useless-creating-stats-json-bases-on-player-n.patch b/Leaf-Server/minecraft-patches/features/0042-Remove-useless-creating-stats-json-bases-on-player-n.patch similarity index 51% rename from patches/server/0058-Remove-useless-creating-stats-json-bases-on-player-n.patch rename to Leaf-Server/minecraft-patches/features/0042-Remove-useless-creating-stats-json-bases-on-player-n.patch index 48862113..2c58fa4f 100644 --- a/patches/server/0058-Remove-useless-creating-stats-json-bases-on-player-n.patch +++ b/Leaf-Server/minecraft-patches/features/0042-Remove-useless-creating-stats-json-bases-on-player-n.patch @@ -4,25 +4,25 @@ Date: Tue, 27 Feb 2024 03:17:10 -0500 Subject: [PATCH] Remove useless creating stats json bases on player name logic -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index a4e8aaf7f35cb1277fc24c4cd240f224e4eedea3..348c3c6a5d99fece14a9eb987a06934c8e8bac59 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1654,6 +1654,8 @@ public abstract class PlayerList { +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 3b679578858b869425dc60ae73fa12ac19d253ef..015c4480a60acc252342d58aa129350ab36dc29b 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -1549,6 +1549,8 @@ public abstract class PlayerList { + if (serverStatsCounter == null) { File file = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile(); - File file1 = new File(file, String.valueOf(uuid) + ".json"); - + File file1 = new File(file, uuid + ".json"); + // Leaf start - Remove useless creating stats json bases on player name logic + /* if (!file1.exists()) { File file2 = new File(file, displayName + ".json"); // CraftBukkit Path path = file2.toPath(); -@@ -1662,6 +1664,8 @@ public abstract class PlayerList { +@@ -1556,6 +1558,8 @@ public abstract class PlayerList { file2.renameTo(file1); } } -+ */ ++ */ + // Leaf end - Remove useless creating stats json bases on player name logic - serverstatisticmanager = new ServerStatsCounter(this.server, file1); - // this.stats.put(uuid, serverstatisticmanager); // CraftBukkit + serverStatsCounter = new ServerStatsCounter(this.server, file1); + // this.stats.put(uuid, serverStatsCounter); // CraftBukkit diff --git a/patches/server/0060-Improve-Purpur-AFK-system.patch b/Leaf-Server/minecraft-patches/features/0043-Improve-Purpur-AFK-system.patch similarity index 63% rename from patches/server/0060-Improve-Purpur-AFK-system.patch rename to Leaf-Server/minecraft-patches/features/0043-Improve-Purpur-AFK-system.patch index 86ae4392..9fc552f4 100644 --- a/patches/server/0060-Improve-Purpur-AFK-system.patch +++ b/Leaf-Server/minecraft-patches/features/0043-Improve-Purpur-AFK-system.patch @@ -6,25 +6,25 @@ Subject: [PATCH] Improve Purpur AFK system AFK command & command cooldown AFK title message -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 6191930c133758936ffd3cc2588c3f8713145508..11f9ac80dd5e53ae9a43ab5f4e9d3c867ab94b15 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -252,6 +252,7 @@ public class Commands { +diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java +index a5cc373dbe034560865e196ce12a8361932ea085..d03a6dc74877bc08b170be5add061270dc46549b 100644 +--- a/net/minecraft/commands/Commands.java ++++ b/net/minecraft/commands/Commands.java +@@ -243,6 +243,7 @@ public class Commands { StopCommand.register(this.dispatcher); TransferCommand.register(this.dispatcher); WhitelistCommand.register(this.dispatcher); + org.purpurmc.purpur.command.AFKCommand.register(this.dispatcher); // Leaf - Improve Purpur AFK system - org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur - org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur - org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index df6f21a7cc2be3e006814896c4a80ad53f9b02f5..3eb3cd1089ec46c64f82e99f25d19cee9e0cdfbe 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2680,6 +2680,10 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple + org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur - Add credits command + org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur - Add demo command + org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur - Add ping command +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 08578d7398658d226318a28324a1eae0d4453add..09c9ca038f359372bda3a3d8b743d497a530ce97 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -2460,6 +2460,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc - // Purpur Start + // Purpur start - AFK API private boolean isAfk = false; + // Leaf sart - Improve Purpur AFK system + public boolean isCommandAfk = false; @@ -33,7 +33,7 @@ index df6f21a7cc2be3e006814896c4a80ad53f9b02f5..3eb3cd1089ec46c64f82e99f25d19cee @Override public void setAfk(boolean afk) { -@@ -2717,6 +2721,22 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple +@@ -2497,6 +2501,22 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, ""); String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, ""); if (afk) { @@ -56,21 +56,21 @@ index df6f21a7cc2be3e006814896c4a80ad53f9b02f5..3eb3cd1089ec46c64f82e99f25d19cee getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true); } else { getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 68cf6f0f0d2d1eb23de070ea8471111a41a9f9bf..615622f3ce63d2150c57cfd9f664bfa8fa5d6124 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2294,6 +2294,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 465e34eb906ec78f49ff1183b635e77a1fcaa758..39b01b3d96d03bca4c33eaddfc54edef23c3df0c 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2255,6 +2255,8 @@ public class ServerGamePacketListenerImpl } } -+ public static final Map afkCooldown = new java.util.concurrent.ConcurrentHashMap<>(); // Leaf - Improve Purpur AFK system ++ public static final Map afkCooldown = new java.util.concurrent.ConcurrentHashMap<>(); // Leaf - Improve Purpur AFK system + @Override public void handleChatCommand(ServerboundChatCommandPacket packet) { this.tryHandleChat(packet.command(), () -> { -@@ -2314,6 +2316,32 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command1); +@@ -2275,6 +2277,32 @@ public class ServerGamePacketListenerImpl + LOGGER.info("{} issued server command: {}", this.player.getScoreboardName(), prefixedCommand); } + // Leaf start - Improve Purpur AFK system @@ -79,7 +79,7 @@ index 68cf6f0f0d2d1eb23de070ea8471111a41a9f9bf..615622f3ce63d2150c57cfd9f664bfa8 + this.player.isCommandAfk = true; + + if (org.purpurmc.purpur.PurpurConfig.afkCommandCooldown > 0) { -+ UUID uuid = this.player.getUUID(); ++ java.util.UUID uuid = this.player.getUUID(); + Long cooldown = afkCooldown.get(uuid); + long currentTime = System.nanoTime(); + @@ -99,17 +99,17 @@ index 68cf6f0f0d2d1eb23de070ea8471111a41a9f9bf..615622f3ce63d2150c57cfd9f664bfa8 + } + // Leaf end - Improve Purpur AFK system + - PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command1, new LazyPlayerSet(this.server)); + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), prefixedCommand, new LazyPlayerSet(this.server)); this.cserver.getPluginManager().callEvent(event); -@@ -2351,11 +2379,37 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -2312,11 +2340,37 @@ public class ServerGamePacketListenerImpl private void performSignedChatCommand(ServerboundChatCommandSignedPacket packet, LastSeenMessages lastSeenMessages) { // CraftBukkit start - String command = "/" + packet.command(); + String command = "/" + packet.command(); // Leaf start - Improve Purpur AFK system - diff on change if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check - ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); + LOGGER.info("{} issued server command: {}", this.player.getScoreboardName(), command); } // Paper - Add missing SpigotConfig logCommands check + // Leaf start - Improve Purpur AFK system @@ -118,7 +118,7 @@ index 68cf6f0f0d2d1eb23de070ea8471111a41a9f9bf..615622f3ce63d2150c57cfd9f664bfa8 + this.player.isCommandAfk = true; + + if (org.purpurmc.purpur.PurpurConfig.afkCommandCooldown > 0) { -+ UUID uuid = this.player.getUUID(); ++ java.util.UUID uuid = this.player.getUUID(); + Long cooldown = afkCooldown.get(uuid); + long currentTime = System.nanoTime(); + @@ -141,23 +141,23 @@ index 68cf6f0f0d2d1eb23de070ea8471111a41a9f9bf..615622f3ce63d2150c57cfd9f664bfa8 PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); this.cserver.getPluginManager().callEvent(event); command = event.getMessage().substring(1); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 348c3c6a5d99fece14a9eb987a06934c8e8bac59..8efd2183309c285e0a7760922bf1b4766248d5f0 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -706,6 +706,7 @@ public abstract class PlayerList { - org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(entityplayer); // Leaves - protocol +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 015c4480a60acc252342d58aa129350ab36dc29b..e4c3819de8af08f36cd30fafb6ce859005c5cb43 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -667,6 +667,7 @@ public abstract class PlayerList { + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(player); // Leaves - protocol // Paper end - Fix kick event leave message not being sent - org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur -+ net.minecraft.server.network.ServerGamePacketListenerImpl.afkCooldown.remove(entityplayer.getBukkitEntity().getUniqueId()); // Leaf - Improve Purpur AFK system - ServerLevel worldserver = entityplayer.serverLevel(); - - entityplayer.awardStat(Stats.LEAVE_GAME); -diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -index 05d77ccd9bf8ce0534fbac7d4354536e3187229f..aa1f5f69346a75af8e9b8dd185f211682a74db2c 100644 ---- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java -+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -@@ -179,9 +179,14 @@ public class PurpurConfig { + org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar ++ net.minecraft.server.network.ServerGamePacketListenerImpl.afkCooldown.remove(player.getBukkitEntity().getUniqueId()); // Leaf - Improve Purpur AFK system + ServerLevel serverLevel = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it +diff --git a/org/purpurmc/purpur/PurpurConfig.java b/org/purpurmc/purpur/PurpurConfig.java +index b084fcbe57a1f57e97919aaa0b0c7d4ab5436bdf..7da226524f726fefee1dcd4e048661948074b49b 100644 +--- a/org/purpurmc/purpur/PurpurConfig.java ++++ b/org/purpurmc/purpur/PurpurConfig.java +@@ -175,9 +175,14 @@ public class PurpurConfig { public static String cannotRideMob = "You cannot mount that mob"; public static String afkBroadcastAway = "%s is now AFK"; public static String afkBroadcastBack = "%s is no longer AFK"; @@ -172,7 +172,7 @@ index 05d77ccd9bf8ce0534fbac7d4354536e3187229f..aa1f5f69346a75af8e9b8dd185f21168 public static String creditsCommandOutput = "%s has been shown the end credits"; public static String demoCommandOutput = "%s has been shown the demo screen"; public static String pingCommandOutput = "%s's ping is %sms"; -@@ -198,9 +203,14 @@ public class PurpurConfig { +@@ -194,9 +199,14 @@ public class PurpurConfig { cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); @@ -187,46 +187,3 @@ index 05d77ccd9bf8ce0534fbac7d4354536e3187229f..aa1f5f69346a75af8e9b8dd185f21168 creditsCommandOutput = getString("settings.messages.credits-command-output", creditsCommandOutput); demoCommandOutput = getString("settings.messages.demo-command-output", demoCommandOutput); pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); -diff --git a/src/main/java/org/purpurmc/purpur/command/AFKCommand.java b/src/main/java/org/purpurmc/purpur/command/AFKCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1b8eee9cce3ae069be7c97bd0840338e9287ce4e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/AFKCommand.java -@@ -0,0 +1,37 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.server.level.ServerPlayer; -+ -+import java.util.Collection; -+import java.util.Collections; -+ -+public class AFKCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("afk") -+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.afk")) -+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) -+ .then(Commands.argument("targets", EntityArgument.players()) -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.afk.other")) -+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) -+ ) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender, Collection targets) { -+ for (ServerPlayer player : targets) { -+ boolean afk = player.isCommandAfk -+ ? !player.commandAfkStatus -+ : !player.isAfk(); -+ -+ if (afk) player.setAfk(true); -+ -+ player.isCommandAfk = false; -+ } -+ -+ return targets.size(); -+ } -+} diff --git a/Leaf-Server/minecraft-patches/features/0044-Virtual-thread-for-chat-executor.patch b/Leaf-Server/minecraft-patches/features/0044-Virtual-thread-for-chat-executor.patch new file mode 100644 index 00000000..d1d385d2 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0044-Virtual-thread-for-chat-executor.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Fri, 25 Oct 2024 02:27:21 +0800 +Subject: [PATCH] Virtual thread for chat executor + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 2ff6059d6bd3fe7c6814603c2ba0e6ccffe1873a..99fb4faa6e8f099ee216eb44793a1df0e6b8c137 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -2680,7 +2680,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Tue, 31 Dec 2024 00:00:00 -0800 +Subject: [PATCH] Virtual thread for User Authenticator + + +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 780d85f4afe221f8861b248457bfe6462f0b8a2a..b6f44bff24f48419a3a0157bb0ade9aa7c21e35e 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -55,7 +55,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + // CraftBukkit end + private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); + static final Logger LOGGER = LogUtils.getLogger(); +- private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads ++ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setThreadFactory(org.dreeam.leaf.config.modules.opt.VT4UserAuthenticator.enabled ? Thread.ofVirtual().factory() : java.util.concurrent.Executors.defaultThreadFactory()).setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads // Leaf - Virtual thread for User Authenticator + private static final int MAX_TICKS_BEFORE_LOGIN = 600; + private final byte[] challenge; + final MinecraftServer server; diff --git a/Leaf-Server/minecraft-patches/features/0046-Mirai-Configurable-chat-message-signatures.patch b/Leaf-Server/minecraft-patches/features/0046-Mirai-Configurable-chat-message-signatures.patch new file mode 100644 index 00000000..316f240b --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0046-Mirai-Configurable-chat-message-signatures.patch @@ -0,0 +1,195 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz <81570777+etil2jz@users.noreply.github.com> +Date: Tue, 2 Aug 2022 14:48:12 +0200 +Subject: [PATCH] Mirai: Configurable chat message signatures + +Fixed & Updated by Dreeam-qwq +Original license: GPLv3 +Original project: https://github.com/Dreeam-qwq/Mirai + +Original license: GPLv3 +Original project: https://github.com/etil2jz/Mirai + +diff --git a/net/minecraft/network/FriendlyByteBuf.java b/net/minecraft/network/FriendlyByteBuf.java +index e5e5d9bc095ccd9fbf1c8aaa09e5c4ebb1d1c920..6a9373ceb14733504c433b5ab10f1f9da7cbbb37 100644 +--- a/net/minecraft/network/FriendlyByteBuf.java ++++ b/net/minecraft/network/FriendlyByteBuf.java +@@ -114,6 +114,16 @@ public class FriendlyByteBuf extends ByteBuf { + public void writeJsonWithCodec(Codec codec, T value, int maxLength) { + // Paper end - Adventure; add max length parameter + DataResult dataResult = codec.encodeStart(JsonOps.INSTANCE, value); ++ // Leaf start - Configurable chat message signatures ++ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled && codec == net.minecraft.network.protocol.status.ServerStatus.CODEC) { ++ JsonElement element = dataResult.getOrThrow(string -> new EncoderException("Failed to encode: " + string + " " + value)); ++ element.getAsJsonObject().addProperty("preventsChatReports", true); ++ ++ this.writeUtf(GSON.toJson(element)); ++ return; ++ } ++ // Leaf end - Configurable chat message signatures ++ + this.writeUtf(GSON.toJson(dataResult.getOrThrow(exception -> new EncoderException("Failed to encode: " + exception + " " + value))), maxLength); // Paper - Adventure; add max length parameter + } + +diff --git a/net/minecraft/network/protocol/game/ClientboundLoginPacket.java b/net/minecraft/network/protocol/game/ClientboundLoginPacket.java +index 89f06581e2b1f9f0240e7841db2f57dedc5d81b7..ba4ec05a9c9dfa958fdfa4a68ee85bcd9c379883 100644 +--- a/net/minecraft/network/protocol/game/ClientboundLoginPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundLoginPacket.java +@@ -55,7 +55,7 @@ public record ClientboundLoginPacket( + buffer.writeBoolean(this.showDeathScreen); + buffer.writeBoolean(this.doLimitedCrafting); + this.commonPlayerSpawnInfo.write(buffer); +- buffer.writeBoolean(this.enforcesSecureChat); ++ buffer.writeBoolean(!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || this.enforcesSecureChat); // Leaf - Mirai - Configurable chat message signatures + } + + @Override +diff --git a/net/minecraft/network/protocol/game/ServerboundChatPacket.java b/net/minecraft/network/protocol/game/ServerboundChatPacket.java +index b5afc05924ae899e020c303c8b86398e1d4ab8a0..73c2ed488c34cddbafdcbb6f2636264ebcc7286b 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatPacket.java +@@ -23,7 +23,7 @@ public record ServerboundChatPacket(String message, Instant timeStamp, long salt + buffer.writeUtf(this.message, 256); + buffer.writeInstant(this.timeStamp); + buffer.writeLong(this.salt); +- buffer.writeNullable(this.signature, MessageSignature::write); ++ if (org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) buffer.writeNullable(this.signature, MessageSignature::write); // Leaf - Mirai - Configurable chat message signatures + this.lastSeenMessages.write(buffer); + } + +diff --git a/net/minecraft/network/protocol/status/ServerStatus.java b/net/minecraft/network/protocol/status/ServerStatus.java +index 30bd254542d631676494f349ff3f44f52d54ab2f..63e6411d8bac1629e143cc620fe35dbac39346e9 100644 +--- a/net/minecraft/network/protocol/status/ServerStatus.java ++++ b/net/minecraft/network/protocol/status/ServerStatus.java +@@ -23,7 +23,10 @@ public record ServerStatus( + boolean enforcesSecureChat + ) { + public static final Codec CODEC = RecordCodecBuilder.create( +- instance -> instance.group( ++ // Leaf start - Mirai - Configurable chat message signatures ++ instance -> ++ org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled ++ ? instance.group( + ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), + ServerStatus.Players.CODEC.lenientOptionalFieldOf("players").forGetter(ServerStatus::players), + ServerStatus.Version.CODEC.lenientOptionalFieldOf("version").forGetter(ServerStatus::version), +@@ -31,6 +34,15 @@ public record ServerStatus( + Codec.BOOL.lenientOptionalFieldOf("enforcesSecureChat", Boolean.valueOf(false)).forGetter(ServerStatus::enforcesSecureChat) + ) + .apply(instance, ServerStatus::new) ++ : instance.group( ++ ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), ++ ServerStatus.Players.CODEC.lenientOptionalFieldOf("players").forGetter(ServerStatus::players), ++ ServerStatus.Version.CODEC.lenientOptionalFieldOf("version").forGetter(ServerStatus::version), ++ ServerStatus.Favicon.CODEC.lenientOptionalFieldOf("favicon").forGetter(ServerStatus::favicon), ++ Codec.BOOL.lenientOptionalFieldOf("enforcesSecureChat", Boolean.FALSE).forGetter(x -> true) ++ ) ++ .apply(instance, ServerStatus::new) ++ // Leaf end- Mirai - Configurable chat message signatures + ); + + public record Favicon(byte[] iconBytes) { +diff --git a/net/minecraft/network/protocol/status/ServerStatus.java.rej b/net/minecraft/network/protocol/status/ServerStatus.java.rej +new file mode 100644 +index 0000000000000000000000000000000000000000..04a6f2fe6563665d8432789e3cbb3278a9408669 +--- /dev/null ++++ b/net/minecraft/network/protocol/status/ServerStatus.java.rej +@@ -0,0 +1,31 @@ ++diff a/net/minecraft/network/protocol/status/ServerStatus.java b/net/minecraft/network/protocol/status/ServerStatus.java (rejected hunks) ++@@ -22,8 +22,11 @@ public record ServerStatus( ++ Optional favicon, ++ boolean enforcesSecureChat ++ ) { +++ // Leaf start - Mirai - Configurable chat message signatures ++ public static final Codec CODEC = RecordCodecBuilder.create( ++- instance -> instance.group( +++ instance -> +++ org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled +++ ? instance.group( ++ ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), ++ ServerStatus.Players.CODEC.lenientOptionalFieldOf("players").forGetter(ServerStatus::players), ++ ServerStatus.Version.CODEC.lenientOptionalFieldOf("version").forGetter(ServerStatus::version), ++@@ -31,7 +34,16 @@ public record ServerStatus( ++ Codec.BOOL.lenientOptionalFieldOf("enforcesSecureChat", Boolean.valueOf(false)).forGetter(ServerStatus::enforcesSecureChat) ++ ) ++ .apply(instance, ServerStatus::new) +++ : instance.group( +++ ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), +++ ServerStatus.Players.CODEC.lenientOptionalFieldOf("players").forGetter(ServerStatus::players), +++ ServerStatus.Version.CODEC.lenientOptionalFieldOf("version").forGetter(ServerStatus::version), +++ ServerStatus.Favicon.CODEC.lenientOptionalFieldOf("favicon").forGetter(ServerStatus::favicon), +++ Codec.BOOL.lenientOptionalFieldOf("enforcesSecureChat", Boolean.FALSE).forGetter(x -> true) +++ ) +++ .apply(instance, ServerStatus::new) ++ ); +++ // Leaf end- Mirai - Configurable chat message signatures ++ ++ public static record Favicon(byte[] iconBytes) { ++ private static final String PREFIX = "data:image/png;base64,"; +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 22b0f33dc3ef9f51ba2ca3cb665b07a16bd1c9d9..7b1e5addd6a1b815498233ba9032f224494af12a 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -667,6 +667,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public boolean enforceSecureProfile() { ++ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) return false; // Leaf - Mirai - Configurable chat message signatures + DedicatedServerProperties properties = this.getProperties(); + // Paper start - Add setting for proxy online mode status + return properties.enforceSecureProfile +diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index ee8cdd532b73180cb484fcc37c36f09c40faacda..eb388c1c94bc6feda6c8757b1800d158fbf48a12 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -318,10 +318,29 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + public void send(Packet packet) { ++ // Leaf start - Mirai - Configurable chat message signatures ++ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) { ++ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { ++ packet = new net.minecraft.network.protocol.game.ClientboundSystemChatPacket( ++ chat.chatType().decorate(chat.unsignedContent() != null ? chat.unsignedContent() : Component.literal(chat.body().content())), false); ++ ++ this.send(packet); ++ return; ++ } ++ } ++ // Leaf end - Mirai - Configurable chat message signatures + this.send(packet, null); + } + + public void send(Packet packet, @Nullable PacketSendListener listener) { ++ // Leaf start - Mirai - Configurable chat message signatures ++ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) { ++ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat && listener != null) { ++ this.send(chat); ++ return; ++ } ++ } ++ // Leaf end - Mirai - Configurable chat message signatures + // CraftBukkit start + if (packet == null || this.processedDisconnect) { // Spigot + return; +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index e4c3819de8af08f36cd30fafb6ce859005c5cb43..b98e854fb9fad65b176b4915214a0a4c5e424d6c 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -1509,7 +1509,7 @@ public abstract class PlayerList { + public void broadcastChatMessage(PlayerChatMessage message, Predicate shouldFilterMessageTo, @Nullable ServerPlayer sender, ChatType.Bound boundChatType, @Nullable Function unsignedFunction) { + // Paper end + boolean flag = this.verifyChatTrusted(message); +- this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), boundChatType, flag || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker ? null : "Not Secure"); // Paper // Gale - do not log Not Secure marker ++ this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), boundChatType, !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || flag || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker ? null : "Not Secure"); // Paper // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures + OutgoingChatMessage outgoingChatMessage = OutgoingChatMessage.create(message); + boolean flag1 = false; + +@@ -1534,6 +1534,7 @@ public abstract class PlayerList { + } + + public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public ++ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) return true; // Leaf - Mirai - Configurable chat message signatures + return message.hasSignature() && !message.hasExpiredServer(Instant.now()); + } + diff --git a/Leaf-Server/minecraft-patches/features/0047-Cache-player-profileResult.patch b/Leaf-Server/minecraft-patches/features/0047-Cache-player-profileResult.patch new file mode 100644 index 00000000..051ea747 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0047-Cache-player-profileResult.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Thu, 28 Mar 2024 13:36:09 -0400 +Subject: [PATCH] Cache player profileResult + + +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index b6f44bff24f48419a3a0157bb0ade9aa7c21e35e..b049304a80dc29c3087cff56c45c2c83679e47ff 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -71,6 +71,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + private net.minecraft.server.level.ServerPlayer player; // CraftBukkit + public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding + private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support ++ // Leaf start - Cache player profileResult ++ private static final com.github.benmanes.caffeine.cache.Cache playerProfileResultCahce = com.github.benmanes.caffeine.cache.Caffeine.newBuilder() ++ .expireAfterWrite(org.dreeam.leaf.config.modules.misc.Cache.cachePlayerProfileResultTimeout, java.util.concurrent.TimeUnit.MINUTES) ++ .build(); ++ // Leaf end - Cache player profileResult + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { + this.server = server; +@@ -302,9 +307,23 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); + + try { +- ProfileResult profileResult = ServerLoginPacketListenerImpl.this.server +- .getSessionService() +- .hasJoinedServer(string1, string, this.getAddress()); ++ // Leaf start - Cache player profileResult ++ ProfileResult profileResult; ++ if (org.dreeam.leaf.config.modules.misc.Cache.cachePlayerProfileResult) { ++ profileResult = playerProfileResultCahce.getIfPresent(string1); ++ ++ if (profileResult == null) { ++ profileResult = ServerLoginPacketListenerImpl.this.server ++ .getSessionService() ++ .hasJoinedServer(string1, string, this.getAddress()); ++ playerProfileResultCahce.put(string1, profileResult); ++ } ++ } else { ++ profileResult = ServerLoginPacketListenerImpl.this.server ++ .getSessionService() ++ .hasJoinedServer(string1, string, this.getAddress()); ++ } ++ // Leaf end - Cache player profileResult + if (profileResult != null) { + GameProfile gameProfile = profileResult.profile(); + // CraftBukkit start - fire PlayerPreLoginEvent diff --git a/Leaf-Server/minecraft-patches/features/0048-Prevent-change-non-editable-sign-warning-spam-in-con.patch b/Leaf-Server/minecraft-patches/features/0048-Prevent-change-non-editable-sign-warning-spam-in-con.patch new file mode 100644 index 00000000..5126bda3 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0048-Prevent-change-non-editable-sign-warning-spam-in-con.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Thu, 28 Mar 2024 14:04:35 -0400 +Subject: [PATCH] Prevent change non-editable sign warning spam in console + + +diff --git a/net/minecraft/world/level/block/entity/SignBlockEntity.java b/net/minecraft/world/level/block/entity/SignBlockEntity.java +index 662f53ca5826fb5b68eb4d426f1d9c5d83906eaf..e0a519af9a567eb660fad5ae55c95900fc2472f5 100644 +--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -152,7 +152,7 @@ public class SignBlockEntity extends BlockEntity { + this.setAllowedPlayerEditor(null); + this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); + } else { +- LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); ++ if (!org.dreeam.leaf.config.modules.misc.RemoveChangeNonEditableSignWarning.enabled) LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); // Leaf - Remove change non-editable sign warning + if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < Mth.square(32)) // Paper - Don't send far away sign update + ((net.minecraft.server.level.ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit + } diff --git a/Leaf-Server/minecraft-patches/features/0049-Matter-Secure-Seed.patch b/Leaf-Server/minecraft-patches/features/0049-Matter-Secure-Seed.patch new file mode 100644 index 00000000..6dba630b --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0049-Matter-Secure-Seed.patch @@ -0,0 +1,436 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Apehum +Date: Thu, 9 Dec 2021 02:18:17 +0800 +Subject: [PATCH] Matter: Secure Seed + +TODO - Dreeam: +Update to BLAKE3 + +Original license: GPLv3 +Original project: https://github.com/plasmoapp/matter + +Co-authored-by: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> + +diff --git a/net/minecraft/server/dedicated/DedicatedServerProperties.java b/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 5748658abf0b90812005ae9d426df92daf5532f0..bb404b4cfdc364b21cb322bc142f9e72384397d0 100644 +--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -114,7 +114,17 @@ public class DedicatedServerProperties extends Settings GsonHelper.parse(!property.isEmpty() ? property : "{}"), new JsonObject()), + this.get("level-type", property -> property.toLowerCase(Locale.ROOT), WorldPresets.NORMAL.location().toString()) +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 77676b38912e558a4ba0c65a009af4cdc531c5fa..eacebc190a1f598314a8b812629fecd86d87ff7c 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -697,6 +697,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + public ChunkGenerator getGenerator() { ++ su.plo.matter.Globals.setupGlobals(level); // Leaf - Matter - Feature Secure Seed + return this.chunkMap.generator(); + } + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index bf364e3fad2823400ec671bc632ba50751a3f473..b3c388f6108360708baf275121af18f46622494f 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -633,6 +633,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + chunkGenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkGenerator, gen); + } + // CraftBukkit end ++ su.plo.matter.Globals.setupGlobals(this); // Leaf - Matter - Feature Secure Seed + boolean flag = server.forceSynchronousWrites(); + DataFixer fixerUpper = server.getFixerUpper(); + // Paper - rewrite chunk system +diff --git a/net/minecraft/world/entity/monster/Slime.java b/net/minecraft/world/entity/monster/Slime.java +index 240a54b210e23d5b79e6bcaf3806aa454668135d..d075c7afaddc7102eed64ef4bd2b096e4972153a 100644 +--- a/net/minecraft/world/entity/monster/Slime.java ++++ b/net/minecraft/world/entity/monster/Slime.java +@@ -424,7 +424,12 @@ public class Slime extends Mob implements Enemy { + } + + ChunkPos chunkPos = new ChunkPos(pos); +- boolean flag = level.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel) level).getSeed(), level.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper ++ // Leaf start - Matter - Feature Secure Seed ++ boolean isSlimeChunk = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? level.getChunk(chunkPos.x, chunkPos.z).isSlimeChunk() ++ : WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel) level).getSeed(), level.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper ++ boolean flag = level.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || isSlimeChunk; ++ // Leaf end - Matter - Feature Secure Seed + // Paper start - Replace rules for Height in Slime Chunks + final double maxHeightSlimeChunk = level.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; + if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { +diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java +index 6d565b52552534ce9cacfc35ad1bf4adcb69eac3..1e7df840abb7aab466770ff35429e7c300dff4ac 100644 +--- a/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -87,6 +87,10 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); + // CraftBukkit end + public final Registry biomeRegistry; // CraftBukkit ++ // Leaf start - Matter - Feature Secure Seed ++ private boolean slimeChunk; ++ private boolean hasComputedSlimeChunk; ++ // Leaf end - Matter - Feature Secure Seed + + // Paper start - rewrite chunk system + private volatile ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles; +@@ -191,6 +195,17 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + return GameEventListenerRegistry.NOOP; + } + ++ // Leaf start - Matter - Feature Secure Seed ++ public boolean isSlimeChunk() { ++ if (!hasComputedSlimeChunk) { ++ hasComputedSlimeChunk = true; ++ slimeChunk = su.plo.matter.WorldgenCryptoRandom.seedSlimeChunk(chunkPos.x, chunkPos.z).nextInt(10) == 0; ++ } ++ ++ return slimeChunk; ++ } ++ // Leaf end - Matter - Feature Secure Seed ++ + public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper + @Nullable + public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving); +diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java +index 6ed51cf42b5864194d671b5b56f5b9bdf0291dc0..2b19e989b4475e905d77bf9d47403c724b81fccd 100644 +--- a/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -343,7 +343,11 @@ public abstract class ChunkGenerator { + Registry registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Map> map = registry.stream().collect(Collectors.groupingBy(structure1 -> structure1.step().ordinal())); + List list = this.featuresPerStep.get(); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? new su.plo.matter.WorldgenCryptoRandom(blockPos.getX(), blockPos.getZ(), su.plo.matter.Globals.Salt.UNDEFINED, 0) ++ : new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); ++ // Leaf end - Matter - Feature Secure Seed + long l = worldgenRandom.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + Set> set = new ObjectArraySet<>(); + ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach(chunkPos -> { +@@ -556,8 +560,17 @@ public abstract class ChunkGenerator { + } else { + ArrayList list1 = new ArrayList<>(list.size()); + list1.addAll(list); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureSeed(structureState.getLevelSeed(), pos.x, pos.z); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { ++ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( ++ pos.x, pos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, 0 ++ ); ++ } else { ++ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom.setLargeFeatureSeed(structureState.getLevelSeed(), pos.x, pos.z); ++ } ++ // Leaf end - Matter - Feature Secure Seed + int i = 0; + + for (StructureSet.StructureSelectionEntry structureSelectionEntry1 : list1) { +diff --git a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +index 4483d7764ddca635fb6fb841fdc2185357106fc5..19b5d09d4f185a4f1600baade21bd897a6c8b849 100644 +--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java ++++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +@@ -205,7 +205,11 @@ public class ChunkGeneratorStructureState { + List> list = new ArrayList<>(count); + int spread = placement.spread(); + HolderSet holderSet = placement.preferredBiomes(); +- RandomSource randomSource = RandomSource.create(); ++ // Leaf start - Matter - Feature Secure Seed ++ RandomSource randomSource = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.STRONGHOLDS, 0) ++ : RandomSource.create(); ++ if (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { + // Paper start - Add missing structure set seed configs + if (this.conf.strongholdSeed != null && structureSet.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { + randomSource.setSeed(this.conf.strongholdSeed); +@@ -213,6 +217,7 @@ public class ChunkGeneratorStructureState { + // Paper end - Add missing structure set seed configs + randomSource.setSeed(this.concentricRingsSeed); + } // Paper - Add missing structure set seed configs ++ } // Leaf end - Matter - Feature Secure Seed + double d = randomSource.nextDouble() * Math.PI * 2.0; + int i = 0; + int i1 = 0; +diff --git a/net/minecraft/world/level/chunk/status/ChunkStep.java b/net/minecraft/world/level/chunk/status/ChunkStep.java +index b8348976e80578d9eff64eea68c04c603fed49ad..37735784e26651456c32c6b392bce53a8416529d 100644 +--- a/net/minecraft/world/level/chunk/status/ChunkStep.java ++++ b/net/minecraft/world/level/chunk/status/ChunkStep.java +@@ -60,6 +60,7 @@ public final class ChunkStep implements ca.spottedleaf.moonrise.patches.chunk_sy + } + + public CompletableFuture apply(WorldGenContext worldGenContext, StaticCache2D cache, ChunkAccess chunk) { ++ su.plo.matter.Globals.setupGlobals(worldGenContext.level()); // Leaf - Matter - Feature Secure Seed + if (chunk.getPersistedStatus().isBefore(this.targetStatus)) { + ProfiledDuration profiledDuration = JvmProfiler.INSTANCE + .onChunkGenerate(chunk.getPos(), worldGenContext.level().dimension(), this.targetStatus.getName()); +diff --git a/net/minecraft/world/level/levelgen/WorldOptions.java b/net/minecraft/world/level/levelgen/WorldOptions.java +index c92508741439a8d0d833ea02d0104416adb83c92..782769443515fa084c51f2f6a3c711cfd830b926 100644 +--- a/net/minecraft/world/level/levelgen/WorldOptions.java ++++ b/net/minecraft/world/level/levelgen/WorldOptions.java +@@ -9,8 +9,20 @@ import net.minecraft.util.RandomSource; + import org.apache.commons.lang3.StringUtils; + + public class WorldOptions { ++ // Leaf start - Matter - Feature Secure Seed ++ private static final com.google.gson.Gson gson = new com.google.gson.Gson(); ++ private static final boolean isSecureSeedEnabled = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( +- instance -> instance.group( ++ instance -> isSecureSeedEnabled ++ ? instance.group( ++ Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), ++ Codec.STRING.fieldOf("feature_seed").orElse(gson.toJson(su.plo.matter.Globals.createRandomWorldSeed())).stable().forGetter(WorldOptions::featureSeedSerialize), ++ Codec.BOOL.fieldOf("generate_features").orElse(true).stable().forGetter(WorldOptions::generateStructures), ++ Codec.BOOL.fieldOf("bonus_chest").orElse(false).stable().forGetter(WorldOptions::generateBonusChest), ++ Codec.STRING.lenientOptionalFieldOf("legacy_custom_options").stable().forGetter(worldOptions -> worldOptions.legacyCustomOptions) ++ ) ++ .apply(instance, instance.stable(WorldOptions::new)) ++ : instance.group( + Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), + Codec.BOOL.fieldOf("generate_features").orElse(true).stable().forGetter(WorldOptions::generateStructures), + Codec.BOOL.fieldOf("bonus_chest").orElse(false).stable().forGetter(WorldOptions::generateBonusChest), +@@ -18,8 +30,14 @@ public class WorldOptions { + ) + .apply(instance, instance.stable(WorldOptions::new)) + ); +- public static final WorldOptions DEMO_OPTIONS = new WorldOptions("North Carolina".hashCode(), true, true); ++ // Leaf end - Matter - Feature Secure Seed ++ // Leaf start - Matter - Feature Secure Seed ++ public static final WorldOptions DEMO_OPTIONS = isSecureSeedEnabled ++ ? new WorldOptions((long) "North Carolina".hashCode(), su.plo.matter.Globals.createRandomWorldSeed(), true, true) ++ : new WorldOptions("North Carolina".hashCode(), true, true); ++ // Leaf end - Matter - Feature Secure Seed + private final long seed; ++ private long[] featureSeed = su.plo.matter.Globals.createRandomWorldSeed(); // Leaf - Matter - Feature Secure Seed + private final boolean generateStructures; + private final boolean generateBonusChest; + private final Optional legacyCustomOptions; +@@ -28,14 +46,35 @@ public class WorldOptions { + this(seed, generateStructures, generateBonusChest, Optional.empty()); + } + ++ // Leaf start - Matter - Feature Secure Seed ++ public WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest) { ++ this(seed, featureSeed, generateStructures, bonusChest, Optional.empty()); ++ } ++ ++ private WorldOptions(long seed, String featureSeedJson, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { ++ this(seed, gson.fromJson(featureSeedJson, long[].class), generateStructures, bonusChest, legacyCustomOptions); ++ } ++ // Leaf end - Matter - Feature Secure Seed ++ + public static WorldOptions defaultWithRandomSeed() { +- return new WorldOptions(randomSeed(), true, false); ++ // Leaf start - Matter - Feature Secure Seed ++ return isSecureSeedEnabled ++ ? new WorldOptions(randomSeed(), su.plo.matter.Globals.createRandomWorldSeed(), true, false) ++ : new WorldOptions(randomSeed(), true, false); ++ // Leaf end - Matter - Feature Secure Seed + } + + public static WorldOptions testWorldWithRandomSeed() { + return new WorldOptions(randomSeed(), false, false); + } + ++ // Leaf start - Matter - Feature Secure Seed ++ private WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { ++ this(seed, generateStructures, bonusChest, legacyCustomOptions); ++ this.featureSeed = featureSeed; ++ } ++ // Leaf end - Matter - Feature Secure Seed ++ + private WorldOptions(long seed, boolean generateStructures, boolean generateBonusChest, Optional legacyCustomOptions) { + this.seed = seed; + this.generateStructures = generateStructures; +@@ -47,6 +86,16 @@ public class WorldOptions { + return this.seed; + } + ++ // Leaf start - Matter - Feature Secure Seed ++ public long[] featureSeed() { ++ return this.featureSeed; ++ } ++ ++ public String featureSeedSerialize() { ++ return gson.toJson(this.featureSeed); ++ } ++ // Leaf end - Matter - Feature Secure Seed ++ + public boolean generateStructures() { + return this.generateStructures; + } +@@ -59,17 +108,25 @@ public class WorldOptions { + return this.legacyCustomOptions.isPresent(); + } + ++ // Leaf start - Matter - Feature Secure Seed + public WorldOptions withBonusChest(boolean generateBonusChest) { +- return new WorldOptions(this.seed, this.generateStructures, generateBonusChest, this.legacyCustomOptions); ++ return isSecureSeedEnabled ++ ? new WorldOptions(this.seed, this.featureSeed, this.generateStructures, generateBonusChest, this.legacyCustomOptions) ++ : new WorldOptions(this.seed, this.generateStructures, generateBonusChest, this.legacyCustomOptions); + } + + public WorldOptions withStructures(boolean generateStructures) { +- return new WorldOptions(this.seed, generateStructures, this.generateBonusChest, this.legacyCustomOptions); ++ return isSecureSeedEnabled ++ ? new WorldOptions(this.seed, this.featureSeed, generateStructures, this.generateBonusChest, this.legacyCustomOptions) ++ : new WorldOptions(this.seed, generateStructures, this.generateBonusChest, this.legacyCustomOptions); + } + + public WorldOptions withSeed(OptionalLong seed) { +- return new WorldOptions(seed.orElse(randomSeed()), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions); ++ return isSecureSeedEnabled ++ ? new WorldOptions(seed.orElse(randomSeed()), su.plo.matter.Globals.createRandomWorldSeed(), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions) ++ : new WorldOptions(seed.orElse(randomSeed()), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions); + } ++ // Leaf end - Matter - Feature Secure Seed + + public static OptionalLong parseSeed(String seed) { + seed = seed.trim(); +diff --git a/net/minecraft/world/level/levelgen/feature/GeodeFeature.java b/net/minecraft/world/level/levelgen/feature/GeodeFeature.java +index 38475f6975533909924c8d54f438cf43cdfe31a3..e416b27c7f6604349766d44284ee004d5f62d9b2 100644 +--- a/net/minecraft/world/level/levelgen/feature/GeodeFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/GeodeFeature.java +@@ -41,7 +41,11 @@ public class GeodeFeature extends Feature { + int i1 = geodeConfiguration.maxGenOffset; + List> list = Lists.newLinkedList(); + int i2 = geodeConfiguration.distributionPoints.sample(randomSource); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.GEODE_FEATURE, 0) ++ : new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); ++ // Leaf end - Matter - Feature Secure Seed + NormalNoise normalNoise = NormalNoise.create(worldgenRandom, -4, 1.0); + List list1 = Lists.newLinkedList(); + double d = (double)i2 / geodeConfiguration.outerWallDistance.getMaxValue(); +diff --git a/net/minecraft/world/level/levelgen/structure/Structure.java b/net/minecraft/world/level/levelgen/structure/Structure.java +index 8328e864c72b7a358d6bb1f33459b8c4df2ecb1a..533dc888e4c1febe4e4b71bd6e1c1affbeb492a9 100644 +--- a/net/minecraft/world/level/levelgen/structure/Structure.java ++++ b/net/minecraft/world/level/levelgen/structure/Structure.java +@@ -249,6 +249,13 @@ public abstract class Structure { + } + + private static WorldgenRandom makeRandom(long seed, ChunkPos chunkPos) { ++ // Leaf start - Matter - Feature Secure Seed ++ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { ++ return new su.plo.matter.WorldgenCryptoRandom( ++ chunkPos.x, chunkPos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, seed ++ ); ++ } ++ // Leaf end - Matter - Feature Secure Seed + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); + worldgenRandom.setLargeFeatureSeed(seed, chunkPos.x, chunkPos.z); + return worldgenRandom; +diff --git a/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java +index ee0d9dddb36b6879fa113299e24f1aa3b2b151cc..d7040165f2b5bb8c60bf32c7bd57ddc0b49faec8 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java +@@ -67,8 +67,17 @@ public class RandomSpreadStructurePlacement extends StructurePlacement { + public ChunkPos getPotentialStructureChunk(long seed, int regionX, int regionZ) { + int i = Math.floorDiv(regionX, this.spacing); + int i1 = Math.floorDiv(regionZ, this.spacing); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(seed, i, i1, this.salt()); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { ++ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( ++ i, i1, su.plo.matter.Globals.Salt.POTENTIONAL_FEATURE, this.salt ++ ); ++ } else { ++ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom.setLargeFeatureWithSalt(seed, i, i1, this.salt()); ++ } ++ // Leaf end - Matter - Feature Secure Seed + int i2 = this.spacing - this.separation; + int i3 = this.spreadType.evaluate(worldgenRandom, i2); + int i4 = this.spreadType.evaluate(worldgenRandom, i2); +diff --git a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +index 670335a7bbfbc9da64c389977498c22dfcd03251..03247c55e0448cabc24ff281e2d1c7df527161da 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -118,8 +118,18 @@ public abstract class StructurePlacement { + public abstract StructurePlacementType type(); + + private static boolean probabilityReducer(long levelSeed, int regionX, int regionZ, int salt, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, salt); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { ++ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( ++ regionX, regionZ, su.plo.matter.Globals.Salt.UNDEFINED, salt ++ ); ++ } else { ++ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom.setLargeFeatureWithSalt(levelSeed, salt, regionX, regionZ); ++ } ++ // Leaf end - Matter - Feature Secure Seed ++ + return worldgenRandom.nextFloat() < probability; + } + +diff --git a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +index eb85edaa3b7fab4f11545b0fa8bfea882dedb67d..4633408a710f6e3a9a6545f181ee37f305daa0e4 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +@@ -64,7 +64,11 @@ public class JigsawPlacement { + ChunkGenerator chunkGenerator = context.chunkGenerator(); + StructureTemplateManager structureTemplateManager = context.structureTemplateManager(); + LevelHeightAccessor levelHeightAccessor = context.heightAccessor(); +- WorldgenRandom worldgenRandom = context.random(); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? new su.plo.matter.WorldgenCryptoRandom(context.chunkPos().x, context.chunkPos().z, su.plo.matter.Globals.Salt.JIGSAW_PLACEMENT, 0) ++ : context.random(); ++ // Leaf end - Matter - Feature Secure Seed + Registry registry = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL); + Rotation random = Rotation.getRandom(worldgenRandom); + StructureTemplatePool structureTemplatePool = startPool.unwrapKey() +diff --git a/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java b/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java +index 653c03d214d2e690852adc4d697e2b24c39ea3d0..81b9f0a07cc2b06770c23f04de5d087c041bd9da 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java +@@ -10,6 +10,7 @@ import net.minecraft.world.level.levelgen.structure.Structure; + import net.minecraft.world.level.levelgen.structure.StructurePiece; + import net.minecraft.world.level.levelgen.structure.StructureType; + import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; ++//import su.plo.matter.WorldgenCryptoRandom; // Leaf - Matter - Feature Secure Seed + + public class EndCityStructure extends Structure { + public static final MapCodec CODEC = simpleCodec(EndCityStructure::new); +diff --git a/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java b/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java +index 5f2118f664c1013b99137c6d34a11c40c2559156..9f80eff88f540e54eb792cc2899cfcb7d75ca0ff 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java +@@ -20,6 +20,7 @@ import net.minecraft.world.level.levelgen.WorldgenRandom; + import net.minecraft.world.level.levelgen.structure.Structure; + import net.minecraft.world.level.levelgen.structure.StructureType; + import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; ++//import su.plo.matter.WorldgenCryptoRandom; // Leaf - Matter - Feature Secure Seed + + public class MineshaftStructure extends Structure { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( diff --git a/patches/server/0068-Matter-Seed-Command.patch b/Leaf-Server/minecraft-patches/features/0050-Matter-Seed-Command.patch similarity index 70% rename from patches/server/0068-Matter-Seed-Command.patch rename to Leaf-Server/minecraft-patches/features/0050-Matter-Seed-Command.patch index 874048ac..69c044bd 100644 --- a/patches/server/0068-Matter-Seed-Command.patch +++ b/Leaf-Server/minecraft-patches/features/0050-Matter-Seed-Command.patch @@ -6,13 +6,13 @@ Subject: [PATCH] Matter: Seed Command Original license: GPLv3 Original project: https://github.com/plasmoapp/matter -diff --git a/src/main/java/net/minecraft/server/commands/SeedCommand.java b/src/main/java/net/minecraft/server/commands/SeedCommand.java -index 0b500b19a99fa6c2740c0db350a166462668df9c..a34f06460ac8eb2ee05cd0e6facc3f08418aff70 100644 ---- a/src/main/java/net/minecraft/server/commands/SeedCommand.java -+++ b/src/main/java/net/minecraft/server/commands/SeedCommand.java +diff --git a/net/minecraft/server/commands/SeedCommand.java b/net/minecraft/server/commands/SeedCommand.java +index a65affc41a4fc299bc2281f0f53f2e075633899d..d67afc17ccaa01a32aa4d810739cc4161e4bede6 100644 +--- a/net/minecraft/server/commands/SeedCommand.java ++++ b/net/minecraft/server/commands/SeedCommand.java @@ -12,6 +12,17 @@ public class SeedCommand { - long l = context.getSource().getLevel().getSeed(); - Component component = ComponentUtils.copyOnClickText(String.valueOf(l)); + long seed = context.getSource().getLevel().getSeed(); + Component component = ComponentUtils.copyOnClickText(String.valueOf(seed)); context.getSource().sendSuccess(() -> Component.translatable("commands.seed.success", component), false); + + // Leaf start - Matter - SecureSeed Command @@ -25,6 +25,6 @@ index 0b500b19a99fa6c2740c0db350a166462668df9c..a34f06460ac8eb2ee05cd0e6facc3f08 + } + // Leaf end - Matter - SecureSeed Command + - return (int)l; + return (int)seed; })); } diff --git a/Leaf-Server/minecraft-patches/features/0051-Faster-Random-Generator.patch b/Leaf-Server/minecraft-patches/features/0051-Faster-Random-Generator.patch new file mode 100644 index 00000000..63c24489 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0051-Faster-Random-Generator.patch @@ -0,0 +1,290 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Faster Random Generator + +This patch replaces LegacyRandomSource with FasterRandomSource by default, +which is faster in general. + +Benchmark results (10,000,000 iterations) (GraalVM 21) +SimpleRandom (Moonrise): 80ms +FasterRandomSource (Leaf) (Backed by Xoroshiro128PlusPlus): 35ms +LegacyRandomSource (Vanilla): 200ms +XoroshiroRandomSource (Vanilla): 47ms + +diff --git a/net/minecraft/util/RandomSource.java b/net/minecraft/util/RandomSource.java +index 98a54bc4de251014342cda6d0951b7fea79ce553..6d56134cc9ed9d73104ae77b1a0baa5a0a45759c 100644 +--- a/net/minecraft/util/RandomSource.java ++++ b/net/minecraft/util/RandomSource.java +@@ -15,18 +15,32 @@ public interface RandomSource { + return create(RandomSupport.generateUniqueSeed()); + } + ++ // Leaf start - Faster random generator + @Deprecated + static RandomSource createThreadSafe() { +- return new ThreadSafeLegacyRandomSource(RandomSupport.generateUniqueSeed()); ++ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled ++ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) ++ : new ThreadSafeLegacyRandomSource(RandomSupport.generateUniqueSeed()); + } + + static RandomSource create(long seed) { +- return new LegacyRandomSource(seed); ++ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled ++ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) ++ : new LegacyRandomSource(seed); ++ } ++ ++ static RandomSource createForSlimeChunk(long seed) { ++ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled && !org.dreeam.leaf.config.modules.opt.FastRNG.useLegacyForSlimeChunk ++ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) ++ : new LegacyRandomSource(seed); + } + + static RandomSource createNewThreadLocalInstance() { +- return new SingleThreadedRandomSource(ThreadLocalRandom.current().nextLong()); ++ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled ++ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) ++ : new SingleThreadedRandomSource(ThreadLocalRandom.current().nextLong()); + } ++ // Leaf end - Faster random generator + + RandomSource fork(); + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 3e1a5ef63d97e2ad43d98c5736a185ade7afb4bd..f7f330758dce7c38e0e0e8ba420df4d83f765cf1 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -143,7 +143,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + // Paper start - Share random for entities to make them more random +- public static RandomSource SHARED_RANDOM = new RandomRandomSource(); ++ public static RandomSource SHARED_RANDOM = org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? org.dreeam.leaf.util.math.random.FasterRandomSource.SHARED_INSTANCE : new RandomRandomSource(); // Leaf - Faster random generator + // Paper start - replace random + private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom { + public RandomRandomSource() { +diff --git a/net/minecraft/world/level/biome/Biome.java b/net/minecraft/world/level/biome/Biome.java +index f44461f92a10cbfdb8fcdbc3a2442e526b9d3d33..b4c5eea26e87ee6f466c53a6dd0867909df7e848 100644 +--- a/net/minecraft/world/level/biome/Biome.java ++++ b/net/minecraft/world/level/biome/Biome.java +@@ -54,14 +54,14 @@ public final class Biome { + ); + public static final Codec> CODEC = RegistryFileCodec.create(Registries.BIOME, DIRECT_CODEC); + public static final Codec> LIST_CODEC = RegistryCodecs.homogeneousList(Registries.BIOME, DIRECT_CODEC); +- private static final PerlinSimplexNoise TEMPERATURE_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(1234L)), ImmutableList.of(0)); ++ private static final PerlinSimplexNoise TEMPERATURE_NOISE = new PerlinSimplexNoise(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(1234L) : new LegacyRandomSource(1234L)), ImmutableList.of(0)); // Leaf - Faster random generator + static final PerlinSimplexNoise FROZEN_TEMPERATURE_NOISE = new PerlinSimplexNoise( +- new WorldgenRandom(new LegacyRandomSource(3456L)), ImmutableList.of(-2, -1, 0) ++ new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(3456L) : new LegacyRandomSource(3456L)), ImmutableList.of(-2, -1, 0) // Leaf - Faster random generator + ); + @Deprecated( + forRemoval = true + ) +- public static final PerlinSimplexNoise BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(2345L)), ImmutableList.of(0)); ++ public static final PerlinSimplexNoise BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(2345L) : new LegacyRandomSource(2345L)), ImmutableList.of(0)); // Leaf - Faster random generator + private static final int TEMPERATURE_CACHE_SIZE = 1024; + public final Biome.ClimateSettings climateSettings; + private final BiomeGenerationSettings generationSettings; +diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java +index 2b19e989b4475e905d77bf9d47403c724b81fccd..472105a1e392f2e1b080483f3038de4f303919ce 100644 +--- a/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -458,7 +458,7 @@ public abstract class ChunkGenerator { + int x = chunk.getPos().x; + int z = chunk.getPos().z; + for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { +- WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(level.getSeed())); ++ WorldgenRandom seededrandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(level.getSeed()) : new net.minecraft.world.level.levelgen.LegacyRandomSource(level.getSeed())); // Leaf - Faster random generator + seededrandom.setDecorationSeed(level.getSeed(), x, z); + populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion); + } +@@ -567,7 +567,7 @@ public abstract class ChunkGenerator { + pos.x, pos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, 0 + ); + } else { +- worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + worldgenRandom.setLargeFeatureSeed(structureState.getLevelSeed(), pos.x, pos.z); + } + // Leaf end - Matter - Feature Secure Seed +diff --git a/net/minecraft/world/level/levelgen/DensityFunctions.java b/net/minecraft/world/level/levelgen/DensityFunctions.java +index 04527a5c65ad630f794fed9071d485aedd02257a..15fc39f9c77fdd03a0ca4a39d173c851b9454f08 100644 +--- a/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -518,7 +518,7 @@ public final class DensityFunctions { + // Paper end - Perf: Optimize end generation + + public EndIslandDensityFunction(long seed) { +- RandomSource randomSource = new LegacyRandomSource(seed); ++ RandomSource randomSource = org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) : new LegacyRandomSource(seed); // Leaf - Faster random generator + randomSource.consumeCount(17292); + this.islandNoise = new SimplexNoise(randomSource); + } +diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index 65728ef17e63d71833677fdcbd5bb90794b4822b..57ae4aaf1431021daf77c5638038d4910a358155 100644 +--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -254,7 +254,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + WorldGenRegion level, long seed, RandomState random, BiomeManager biomeManager, StructureManager structureManager, ChunkAccess chunk + ) { + BiomeManager biomeManager1 = biomeManager.withDifferentSource((x, y, z) -> this.biomeSource.getNoiseBiome(x, y, z, random.sampler())); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed())); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) : new LegacyRandomSource(RandomSupport.generateUniqueSeed())); // Leaf - Faster random generator + int i = 8; + ChunkPos pos = chunk.getPos(); + NoiseChunk noiseChunk = chunk.getOrCreateNoiseChunk(chunkAccess -> this.createNoiseChunk(chunkAccess, structureManager, Blender.of(level), random)); +@@ -420,7 +420,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + if (!this.settings.value().disableMobGeneration()) { + ChunkPos center = level.getCenter(); + Holder biome = level.getBiome(center.getWorldPosition().atY(level.getMaxY())); +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed())); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) : new LegacyRandomSource(RandomSupport.generateUniqueSeed())); // Leaf - Faster random generator + worldgenRandom.setDecorationSeed(level.getSeed(), center.getMinBlockX(), center.getMinBlockZ()); + NaturalSpawner.spawnMobsForChunkGeneration(level, biome, center, worldgenRandom); + } +diff --git a/net/minecraft/world/level/levelgen/WorldgenRandom.java b/net/minecraft/world/level/levelgen/WorldgenRandom.java +index c2d7cd788071e25b8ba2503c30ae80c7a9f353ed..08cf526ae87dd2560fcb50d5786701701e34ec00 100644 +--- a/net/minecraft/world/level/levelgen/WorldgenRandom.java ++++ b/net/minecraft/world/level/levelgen/WorldgenRandom.java +@@ -69,7 +69,7 @@ public class WorldgenRandom extends LegacyRandomSource { + } + + public static RandomSource seedSlimeChunk(int chunkX, int chunkZ, long levelSeed, long salt) { +- return RandomSource.create(levelSeed + chunkX * chunkX * 4987142 + chunkX * 5947611 + chunkZ * chunkZ * 4392871L + chunkZ * 389711 ^ salt); ++ return RandomSource.createForSlimeChunk(levelSeed + chunkX * chunkX * 4987142 + chunkX * 5947611 + chunkZ * chunkZ * 4392871L + chunkZ * 389711 ^ salt); // Leaf - Faster RNG + } + + public static enum Algorithm { +diff --git a/net/minecraft/world/level/levelgen/feature/GeodeFeature.java b/net/minecraft/world/level/levelgen/feature/GeodeFeature.java +index e416b27c7f6604349766d44284ee004d5f62d9b2..1d27550bb5f29b6bfe8e0ad4fc4c9d39a9d7b29e 100644 +--- a/net/minecraft/world/level/levelgen/feature/GeodeFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/GeodeFeature.java +@@ -44,7 +44,7 @@ public class GeodeFeature extends Feature { + // Leaf start - Matter - Feature Secure Seed + WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled + ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.GEODE_FEATURE, 0) +- : new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); ++ : new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(worldGenLevel.getSeed()) : new LegacyRandomSource(worldGenLevel.getSeed())); // Leaf - Faster random generator + // Leaf end - Matter - Feature Secure Seed + NormalNoise normalNoise = NormalNoise.create(worldgenRandom, -4, 1.0); + List list1 = Lists.newLinkedList(); +diff --git a/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java b/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java +index 48ab8a568d97052fe205e6a1f89862ee23d65abb..a190b5e890cf34dd1aa46cb9e283f05154fbe3e5 100644 +--- a/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java ++++ b/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java +@@ -43,7 +43,7 @@ public class DualNoiseProvider extends NoiseProvider { + this.variety = variety; + this.slowNoiseParameters = slowNoiseParameters; + this.slowScale = slowScale; +- this.slowNoise = NormalNoise.create(new WorldgenRandom(new LegacyRandomSource(seed)), slowNoiseParameters); ++ this.slowNoise = NormalNoise.create(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) : new LegacyRandomSource(seed)), slowNoiseParameters); // Leaf - Faster random generator + } + + @Override +diff --git a/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java b/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java +index f685372a39976f823202f2d9015c14f835b94a0c..bdd1b4ab758fc653df4adad7633ef430ebb89dbe 100644 +--- a/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java ++++ b/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java +@@ -28,7 +28,7 @@ public abstract class NoiseBasedStateProvider extends BlockStateProvider { + this.seed = seed; + this.parameters = parameters; + this.scale = scale; +- this.noise = NormalNoise.create(new WorldgenRandom(new LegacyRandomSource(seed)), parameters); ++ this.noise = NormalNoise.create(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) : new LegacyRandomSource(seed)), parameters); // Leaf - Faster random generator + } + + protected double getNoiseValue(BlockPos pos, double delta) { +diff --git a/net/minecraft/world/level/levelgen/structure/Structure.java b/net/minecraft/world/level/levelgen/structure/Structure.java +index 533dc888e4c1febe4e4b71bd6e1c1affbeb492a9..7f8293497912532fd6b83a0962a722b0e6267721 100644 +--- a/net/minecraft/world/level/levelgen/structure/Structure.java ++++ b/net/minecraft/world/level/levelgen/structure/Structure.java +@@ -256,7 +256,7 @@ public abstract class Structure { + ); + } + // Leaf end - Matter - Feature Secure Seed +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + worldgenRandom.setLargeFeatureSeed(seed, chunkPos.x, chunkPos.z); + return worldgenRandom; + } +diff --git a/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java +index d7040165f2b5bb8c60bf32c7bd57ddc0b49faec8..ebba2592726356eb837b733c36148f444c2d7f13 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java +@@ -74,7 +74,7 @@ public class RandomSpreadStructurePlacement extends StructurePlacement { + i, i1, su.plo.matter.Globals.Salt.POTENTIONAL_FEATURE, this.salt + ); + } else { +- worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + worldgenRandom.setLargeFeatureWithSalt(seed, i, i1, this.salt()); + } + // Leaf end - Matter - Feature Secure Seed +diff --git a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +index 03247c55e0448cabc24ff281e2d1c7df527161da..2a9f66ac0fe8f664d404c68df7414be6b0396082 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -125,7 +125,7 @@ public abstract class StructurePlacement { + regionX, regionZ, su.plo.matter.Globals.Salt.UNDEFINED, salt + ); + } else { +- worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + worldgenRandom.setLargeFeatureWithSalt(levelSeed, salt, regionX, regionZ); + } + // Leaf end - Matter - Feature Secure Seed +@@ -134,7 +134,7 @@ public abstract class StructurePlacement { + } + + private static boolean legacyProbabilityReducerWithDouble(long baseSeed, int salt, int chunkX, int chunkZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + if (saltOverride == null) { // Paper - Add missing structure set seed configs + worldgenRandom.setLargeFeatureSeed(baseSeed, chunkX, chunkZ); + // Paper start - Add missing structure set seed configs +@@ -146,7 +146,7 @@ public abstract class StructurePlacement { + } + + private static boolean legacyArbitrarySaltProbabilityReducer(long levelSeed, int salt, int regionX, int regionZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs + return worldgenRandom.nextFloat() < probability; + } +@@ -154,7 +154,7 @@ public abstract class StructurePlacement { + private static boolean legacyPillagerOutpostReducer(long levelSeed, int salt, int regionX, int regionZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + int i = regionX >> 4; + int i1 = regionZ >> 4; +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator + worldgenRandom.setSeed(i ^ i1 << 4 ^ levelSeed); + worldgenRandom.nextInt(); + return worldgenRandom.nextInt((int)(1.0F / probability)) == 0; +diff --git a/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java b/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java +index 6941b2c89df8a7c77166e3fb76150cbc852371d9..661c26c4b981d504988c7498be45a5ddacaf90d8 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java +@@ -56,7 +56,7 @@ public class OceanMonumentStructure extends Structure { + if (piecesContainer.isEmpty()) { + return piecesContainer; + } else { +- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed())); ++ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) : new LegacyRandomSource(RandomSupport.generateUniqueSeed())); // Leaf - Faster random generator + worldgenRandom.setLargeFeatureSeed(seed, chunkPos.x, chunkPos.z); + StructurePiece structurePiece = piecesContainer.pieces().get(0); + BoundingBox boundingBox = structurePiece.getBoundingBox(); +diff --git a/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java b/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java +index 434e72fd770a259d67e5e7f110f49b09bab6c54e..720098d50ecefeff25e8f032e33742ad6bd6ab21 100644 +--- a/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java +@@ -43,7 +43,7 @@ public class PerlinSimplexNoise { + + if (i1 > 0) { + long l = (long)(simplexNoise.getValue(simplexNoise.xo, simplexNoise.yo, simplexNoise.zo) * 9.223372E18F); +- RandomSource randomSource = new WorldgenRandom(new LegacyRandomSource(l)); ++ RandomSource randomSource = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(l) : new LegacyRandomSource(l)); // Leaf - Faster random generator + + for (int i5 = i3 - 1; i5 >= 0; i5--) { + if (i5 < i2 && octaves.contains(i3 - i5)) { diff --git a/Leaf-Server/minecraft-patches/features/0052-Don-t-save-primed-tnt-entity.patch b/Leaf-Server/minecraft-patches/features/0052-Don-t-save-primed-tnt-entity.patch new file mode 100644 index 00000000..7086a23b --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0052-Don-t-save-primed-tnt-entity.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nostalfinals +Date: Mon, 29 Apr 2024 23:30:21 +0800 +Subject: [PATCH] Don't save primed tnt entity + + +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index 40f5534b425ef57c435b365f156d3b988b74f911..c96f458994818392857642282ec3d492124885da 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -279,4 +279,11 @@ public class PrimedTnt extends Entity implements TraceableEntity { + return super.interact(player, hand); + } + // Purpur end - Shears can defuse TNT ++ ++ // Leaf start - PMC - Don't save primed tnt entity ++ @Override ++ public boolean shouldBeSaved() { ++ return !org.dreeam.leaf.config.modules.opt.DontSaveEntity.dontSavePrimedTNT && super.shouldBeSaved(); ++ } ++ // Leaf - PMC - Don't save primed tnt entity + } diff --git a/Leaf-Server/minecraft-patches/features/0053-Don-t-save-falling-block-entity.patch b/Leaf-Server/minecraft-patches/features/0053-Don-t-save-falling-block-entity.patch new file mode 100644 index 00000000..602ff53e --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0053-Don-t-save-falling-block-entity.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nostalfinals +Date: Mon, 29 Apr 2024 23:31:25 +0800 +Subject: [PATCH] Don't save falling block entity + + +diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java +index 5746587666c7cb788764aab2f6ccf0f3ac5c282f..fd2f93b070f96d28a8c694a6d943d92d257d0c9e 100644 +--- a/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -404,4 +404,11 @@ public class FallingBlockEntity extends Entity { + this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper + return entity; + } ++ ++ // Leaf start - PMC - Don't save falling block entity ++ @Override ++ public boolean shouldBeSaved() { ++ return !org.dreeam.leaf.config.modules.opt.DontSaveEntity.dontSaveFallingBlock && super.shouldBeSaved(); ++ } ++ // Leaf end - PMC - Don't save falling block entity + } diff --git a/Leaf-Server/minecraft-patches/features/0054-Configurable-connection-message.patch b/Leaf-Server/minecraft-patches/features/0054-Configurable-connection-message.patch new file mode 100644 index 00000000..09d650eb --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0054-Configurable-connection-message.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sun, 2 Jun 2024 01:21:36 +0800 +Subject: [PATCH] Configurable connection message + + +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index b98e854fb9fad65b176b4915214a0a4c5e424d6c..5fe6a295f5b5285a20c535a026e1342f89896fd7 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -433,7 +433,7 @@ public abstract class PlayerList { + // Ensure that player inventory is populated with its viewer + player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); + +- org.bukkit.event.player.PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(bukkitPlayer, io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); // Paper - Adventure ++ org.bukkit.event.player.PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(bukkitPlayer, getJoinMsg(mutableComponent, bukkitPlayer)); // Paper - Adventure + this.cserver.getPluginManager().callEvent(playerJoinEvent); + + if (!player.connection.isAcceptingMessages()) { +@@ -446,7 +446,7 @@ public abstract class PlayerList { + + final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); + +- if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure ++ if (org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinEnabled && jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure // Leaf - Configurable connection message - join message + joinMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(jm); // Paper - Adventure + this.server.getPlayerList().broadcastSystemMessage(joinMessage, false); // Paper - Adventure + } +@@ -676,7 +676,7 @@ public abstract class PlayerList { + player.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason + } + +- org.bukkit.event.player.PlayerQuitEvent playerQuitEvent = new org.bukkit.event.player.PlayerQuitEvent(player.getBukkitEntity(), leaveMessage, player.quitReason); // Paper - Adventure & Add API for quit reason ++ org.bukkit.event.player.PlayerQuitEvent playerQuitEvent = new org.bukkit.event.player.PlayerQuitEvent(player.getBukkitEntity(), getQuitMsg(leaveMessage, player.getBukkitEntity()), player.quitReason); // Paper - Adventure & Add API for quit reason + this.cserver.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +@@ -1661,4 +1661,33 @@ public abstract class PlayerList { + public boolean isAllowCommandsForAllPlayers() { + return this.allowCommandsForAllPlayers; + } ++ ++ // Leaf start - Configurable connection message ++ private net.kyori.adventure.text.Component getJoinMsg(MutableComponent defaultJoinMsg, org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer) { ++ if (org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinEnabled) { ++ if ("default".equals(org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage)) { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(defaultJoinMsg); ++ } ++ ++ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage) ++ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_name%").replacement(craftPlayer.getName()).build()) ++ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_displayname%").replacement(craftPlayer.displayName()).build()); ++ } ++ ++ return net.kyori.adventure.text.Component.empty(); ++ } ++ private net.kyori.adventure.text.Component getQuitMsg(net.kyori.adventure.text.Component defaultJoinMsg, org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer) { ++ if (org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitEnabled) { ++ if ("default".equals(org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage)) { ++ return defaultJoinMsg; ++ } ++ ++ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage) ++ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_name%").replacement(craftPlayer.getName()).build()) ++ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_displayname%").replacement(craftPlayer.displayName()).build()); ++ } ++ ++ return net.kyori.adventure.text.Component.empty(); ++ } ++ // Leaf end - Configurable connection message + } diff --git a/Leaf-Server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch b/Leaf-Server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch new file mode 100644 index 00000000..3619beb0 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 7 Aug 2024 18:54:01 +0800 +Subject: [PATCH] Configurable unknown command message + + +diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java +index d03a6dc74877bc08b170be5add061270dc46549b..76f26f4fb93dd84a9b15ea662eeeadbc44a99ac5 100644 +--- a/net/minecraft/commands/Commands.java ++++ b/net/minecraft/commands/Commands.java +@@ -390,31 +390,9 @@ public class Commands { + // Paper start - Add UnknownCommandEvent + final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); + // source.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage())); +- builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(var7.getRawMessage())); ++ final net.kyori.adventure.text.TextComponent message = getUnknownCommandMessage(builder, var7, label); // Leaf - Configurable unknown command message + // Paper end - Add UnknownCommandEvent +- if (var7.getInput() != null && var7.getCursor() >= 0) { +- int min = Math.min(var7.getInput().length(), var7.getCursor()); +- MutableComponent mutableComponent = Component.empty() +- .withStyle(ChatFormatting.GRAY) +- .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label))); // CraftBukkit // Paper +- if (min > 10) { +- mutableComponent.append(CommonComponents.ELLIPSIS); +- } +- +- mutableComponent.append(var7.getInput().substring(Math.max(0, min - 10), min)); +- if (min < var7.getInput().length()) { +- Component component = Component.literal(var7.getInput().substring(min)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE); +- mutableComponent.append(component); +- } +- +- mutableComponent.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); +- // Paper start - Add UnknownCommandEvent +- // source.sendFailure(mutableComponent); +- builder +- .append(net.kyori.adventure.text.Component.newline()) +- .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); +- } +- org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(source.getBukkitSender(), command, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build()); ++ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(source.getBukkitSender(), command, message); // Leaf - Configurable unknown command message + org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); + if (event.message() != null) { + source.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); +@@ -677,6 +655,86 @@ public class Commands { + }; + } + ++ // Leaf start - Configurable unknown command message ++ private static net.kyori.adventure.text.TextComponent getUnknownCommandMessage( ++ net.kyori.adventure.text.TextComponent.Builder builder, CommandSyntaxException commandSyntaxException, String label ++ ) { ++ String rawMessage = org.dreeam.leaf.config.modules.misc.UnknownCommandMessage.unknownCommandMessage; ++ ++ if (!"default".equals(rawMessage)) { ++ final String input = commandSyntaxException.getInput(); ++ final int cursor = commandSyntaxException.getCursor(); ++ ++ if (rawMessage.contains("") && input != null && cursor >= 0) { ++ final int min = Math.min(input.length(), cursor); ++ final net.kyori.adventure.text.TextComponent.Builder detail = net.kyori.adventure.text.Component.text(); ++ final net.kyori.adventure.text.Component context = net.kyori.adventure.text.Component.translatable("command.context.here") ++ .color(net.kyori.adventure.text.format.NamedTextColor.RED) ++ .decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); ++ final net.kyori.adventure.text.event.ClickEvent event = net.kyori.adventure.text.event.ClickEvent.suggestCommand("/" + label); ++ ++ detail.color(net.kyori.adventure.text.format.NamedTextColor.GRAY); ++ ++ if (min > 10) { ++ detail.append(net.kyori.adventure.text.Component.text("...")); ++ } ++ ++ detail.append(net.kyori.adventure.text.Component.text(input.substring(Math.max(0, min - 10), min))); ++ if (min < input.length()) { ++ net.kyori.adventure.text.Component commandInput = net.kyori.adventure.text.Component.text(input.substring(min)) ++ .color(net.kyori.adventure.text.format.NamedTextColor.RED) ++ .decorate(net.kyori.adventure.text.format.TextDecoration.UNDERLINED); ++ ++ detail.append(commandInput); ++ } ++ ++ detail.append(context); ++ detail.clickEvent(event); ++ ++ builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("detail", detail.build()))); ++ } else { ++ rawMessage = rawMessage.replace("", ""); ++ builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage)); ++ } ++ ++ return builder.build(); ++ } ++ ++ return getVanillaUnknownCommandMessage(builder, commandSyntaxException, label); ++ } ++ ++ private static net.kyori.adventure.text.TextComponent getVanillaUnknownCommandMessage( ++ net.kyori.adventure.text.TextComponent.Builder builder, CommandSyntaxException var7, String label ++ ) { ++ builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(var7.getRawMessage())); ++ ++ if (var7.getInput() != null && var7.getCursor() >= 0) { ++ int min = Math.min(var7.getInput().length(), var7.getCursor()); ++ MutableComponent mutableComponent = Component.empty() ++ .withStyle(ChatFormatting.GRAY) ++ .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label))); // CraftBukkit // Paper ++ if (min > 10) { ++ mutableComponent.append(CommonComponents.ELLIPSIS); ++ } ++ ++ mutableComponent.append(var7.getInput().substring(Math.max(0, min - 10), min)); ++ if (min < var7.getInput().length()) { ++ Component component = Component.literal(var7.getInput().substring(min)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE); ++ mutableComponent.append(component); ++ } ++ ++ mutableComponent.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); ++ // Paper start - Add UnknownCommandEvent ++ // source.sendFailure(mutableComponent); ++ builder ++ .append(net.kyori.adventure.text.Component.newline()) ++ .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); ++ } ++ ++ return builder.build(); ++ } ++ // Leaf end - Configurable unknown command message ++ + public static void validate() { + CommandBuildContext commandBuildContext = createValidationContext(VanillaRegistries.createLookup()); + CommandDispatcher dispatcher = new Commands(Commands.CommandSelection.ALL, commandBuildContext).getDispatcher(); diff --git a/Leaf-Server/minecraft-patches/features/0056-Remove-stream-in-BlockBehaviour-cache-blockstate.patch b/Leaf-Server/minecraft-patches/features/0056-Remove-stream-in-BlockBehaviour-cache-blockstate.patch new file mode 100644 index 00000000..38159be8 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0056-Remove-stream-in-BlockBehaviour-cache-blockstate.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Fri, 7 Jun 2024 17:43:43 +0800 +Subject: [PATCH] Remove stream in BlockBehaviour cache blockstate + + +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index b631e35e965b1914cdeeddab8bd6bdbfd2465079..c13c8c82bf7bd0a9a33fd4027884ad852a7c64b6 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -1051,7 +1051,7 @@ public abstract class BlockBehaviour implements FeatureElement { + private static final Direction[] DIRECTIONS = Direction.values(); + private static final int SUPPORT_TYPE_COUNT = SupportType.values().length; + protected final VoxelShape collisionShape; +- protected final boolean largeCollisionShape; ++ protected boolean largeCollisionShape; // Leaf - not final + private final boolean[] faceSturdy; + protected final boolean isCollisionShapeFullBlock; + +@@ -1067,8 +1067,14 @@ public abstract class BlockBehaviour implements FeatureElement { + ) + ); + } else { +- this.largeCollisionShape = Arrays.stream(Direction.Axis.values()) +- .anyMatch(dir -> this.collisionShape.min(dir) < 0.0 || this.collisionShape.max(dir) > 1.0); ++ // Leaf start - Remove stream ++ for (Direction.Axis axis : Direction.Axis.values()) { ++ if (this.collisionShape.min(axis) < 0.0 || this.collisionShape.max(axis) > 1.0) { ++ this.largeCollisionShape = true; ++ break; ++ } ++ } ++ // Leaf end - Remove stream + this.faceSturdy = new boolean[DIRECTIONS.length * SUPPORT_TYPE_COUNT]; + + for (Direction direction : DIRECTIONS) { diff --git a/Leaf-Server/minecraft-patches/features/0057-Remove-stream-in-entity-visible-effects-filter.patch b/Leaf-Server/minecraft-patches/features/0057-Remove-stream-in-entity-visible-effects-filter.patch new file mode 100644 index 00000000..f2e98fdb --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0057-Remove-stream-in-entity-visible-effects-filter.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Tue, 17 Sep 2024 02:26:44 -0400 +Subject: [PATCH] Remove stream in entity visible effects filter + + +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index c041cba43b4687e2f2f057edfae448a42f6d8753..8b096a0f9f23a9b90ef88414e8749a514e3910cf 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -988,12 +988,15 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + private void updateSynchronizedMobEffectParticles() { +- List list = this.activeEffects +- .values() +- .stream() +- .filter(MobEffectInstance::isVisible) +- .map(MobEffectInstance::getParticleOptions) +- .toList(); ++ // Leaf start - Remove stream in entity visible effects filter ++ List list = new ArrayList<>(); ++ ++ for (MobEffectInstance effect : this.activeEffects.values()) { ++ if (effect.isVisible()) { ++ list.add(effect.getParticleOptions()); ++ } ++ } ++ // Leaf end - Remove stream in entity visible effects filter + this.entityData.set(DATA_EFFECT_PARTICLES, list); + this.entityData.set(DATA_EFFECT_AMBIENCE_ID, areAllEffectsAmbient(this.activeEffects.values())); + } diff --git a/patches/server/0077-Remove-stream-and-double-iteration-in-enough-deep-sl.patch b/Leaf-Server/minecraft-patches/features/0058-Remove-stream-and-double-iteration-in-enough-deep-sl.patch similarity index 50% rename from patches/server/0077-Remove-stream-and-double-iteration-in-enough-deep-sl.patch rename to Leaf-Server/minecraft-patches/features/0058-Remove-stream-and-double-iteration-in-enough-deep-sl.patch index 75acb662..f180fce3 100644 --- a/patches/server/0077-Remove-stream-and-double-iteration-in-enough-deep-sl.patch +++ b/Leaf-Server/minecraft-patches/features/0058-Remove-stream-and-double-iteration-in-enough-deep-sl.patch @@ -5,34 +5,34 @@ Subject: [PATCH] Remove stream and double iteration in enough deep sleeping player check -diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java -index caa8a69bde0c212c36dd990a67836ac2f95548c0..5ae435dcc078a5b9af90f01fa70f1a9d6f34e2be 100644 ---- a/src/main/java/net/minecraft/server/players/SleepStatus.java -+++ b/src/main/java/net/minecraft/server/players/SleepStatus.java -@@ -19,10 +19,24 @@ public class SleepStatus { +diff --git a/net/minecraft/server/players/SleepStatus.java b/net/minecraft/server/players/SleepStatus.java +index 3a3e6992563236141db687084aeec9684437a7db..e6827e90b685f88d945010f2c8c5aead52b0856e 100644 +--- a/net/minecraft/server/players/SleepStatus.java ++++ b/net/minecraft/server/players/SleepStatus.java +@@ -15,9 +15,24 @@ public class SleepStatus { - public boolean areEnoughDeepSleeping(int percentage, List players) { + public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List sleepingPlayers) { // CraftBukkit start -- int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur -- boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); +- int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())).count(); // Purpur - AFK API +- boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough); +- return anyDeepSleep && i >= this.sleepersNeeded(requiredSleepPercentage); + // Leaf start - Remove stream and double iteration in enough deep sleeping player check + int count = 0; + boolean anyPlayerSleeping = false; - -- return anyDeepSleep && j >= this.sleepersNeeded(percentage); -+ for (ServerPlayer player : players) { ++ ++ for (ServerPlayer player : sleepingPlayers) { + final boolean isSleepingLongEnough = player.isSleepingLongEnough(); + + if (isSleepingLongEnough) { + anyPlayerSleeping = true; + } + -+ if (isSleepingLongEnough || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())) { // Purpur ++ if (isSleepingLongEnough || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())) { // Purpur - AFK API + count++; + } + } + -+ return anyPlayerSleeping && count >= this.sleepersNeeded(percentage); ++ return anyPlayerSleeping && count >= this.sleepersNeeded(requiredSleepPercentage); + // Leaf end - Remove stream and double iteration in enough deep sleeping player check // CraftBukkit end } diff --git a/Leaf-Server/minecraft-patches/features/0059-Remove-stream-in-trial-spawner-ticking.patch b/Leaf-Server/minecraft-patches/features/0059-Remove-stream-in-trial-spawner-ticking.patch new file mode 100644 index 00000000..f6677c25 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0059-Remove-stream-in-trial-spawner-ticking.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sat, 5 Oct 2024 15:39:15 -0400 +Subject: [PATCH] Remove stream in trial spawner ticking + + +diff --git a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java +index 02723e61684fcf36094d7b9c81f6ab87ad6f620a..fc340def138824c7f922788e3dce48adfc1258dd 100644 +--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java ++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java +@@ -173,17 +173,21 @@ public enum TrialSpawnerState implements StringRepresentable { + } + + private static Optional calculatePositionToSpawnSpawner(ServerLevel level, BlockPos pos, TrialSpawner spawner, TrialSpawnerData spawnerData) { +- List list = spawnerData.detectedPlayers +- .stream() +- .map(level::getPlayerByUUID) +- .filter(Objects::nonNull) +- .filter( +- player -> !player.isCreative() +- && !player.isSpectator() +- && player.isAlive() +- && player.distanceToSqr(pos.getCenter()) <= Mth.square(spawner.getRequiredPlayerRange()) +- ) +- .toList(); ++ // Leaf start - Remove stream in trial spawner ticking ++ List list = new java.util.ArrayList<>(); ++ ++ for (UUID uuid : spawnerData.detectedPlayers) { ++ Player player = level.getPlayerByUUID(uuid); ++ ++ if (player != null ++ && !player.isCreative() ++ && !player.isSpectator() ++ && player.isAlive() ++ && player.distanceToSqr(pos.getCenter()) <= Mth.square(spawner.getRequiredPlayerRange())) { ++ list.add(player); ++ } ++ } ++ // Leaf end - Remove stream in trial spawner ticking + if (list.isEmpty()) { + return Optional.empty(); + } else { +@@ -203,16 +207,29 @@ public enum TrialSpawnerState implements StringRepresentable { + + @Nullable + private static Entity selectEntityToSpawnItemAbove(List player, Set currentMobs, TrialSpawner spawner, BlockPos pos, ServerLevel level) { +- Stream stream = currentMobs.stream() +- .map(level::getEntity) +- .filter(Objects::nonNull) +- .filter(entity -> entity.isAlive() && entity.distanceToSqr(pos.getCenter()) <= Mth.square(spawner.getRequiredPlayerRange())); +- List list = level.random.nextBoolean() ? stream.toList() : player; +- if (list.isEmpty()) { +- return null; ++ // Leaf start - Remove stream in trial spawner ticking ++ if (level.random.nextBoolean()) { ++ List list = new java.util.ArrayList<>(); ++ for (UUID uuid : currentMobs) { ++ Entity entity = level.getEntity(uuid); ++ if (entity != null && entity.isAlive() && entity.distanceToSqr(pos.getCenter()) <= Mth.square(spawner.getRequiredPlayerRange())) { ++ list.add(entity); ++ } ++ } ++ ++ if (list.isEmpty()) { ++ return null; ++ } else { ++ return list.size() == 1 ? list.getFirst() : Util.getRandom(list, level.random); ++ } + } else { +- return list.size() == 1 ? list.getFirst() : Util.getRandom(list, level.random); ++ if (player.isEmpty()) { ++ return null; ++ } else { ++ return player.size() == 1 ? player.getFirst() : Util.getRandom(player, level.random); ++ } + } ++ // Leaf end - Remove stream in trial spawner ticking + } + + private boolean timeToSpawnItemSpawner(ServerLevel level, TrialSpawnerData spawnerData) { diff --git a/Leaf-Server/minecraft-patches/features/0060-Remove-stream-in-Brain.patch b/Leaf-Server/minecraft-patches/features/0060-Remove-stream-in-Brain.patch new file mode 100644 index 00000000..eafa4e1f --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0060-Remove-stream-in-Brain.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Sat, 26 Oct 2024 00:06:04 +0800 +Subject: [PATCH] Remove stream in Brain + + +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index 450396468b23fd90cb8036dbbdd0927051f907af..402279ff78f9e9eb74c63d35ec1968d239228d70 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java +@@ -70,13 +70,22 @@ public class Brain { + (new MapCodec>() { + @Override + public Stream keys(DynamicOps ops) { +- return memoryTypes.stream() +- .flatMap( +- memoryModuleType -> memoryModuleType.getCodec() +- .map(codec -> BuiltInRegistries.MEMORY_MODULE_TYPE.getKey((MemoryModuleType)memoryModuleType)) +- .stream() +- ) +- .map(resourceLocation -> ops.createString(resourceLocation.toString())); ++ // Leaf start - Remove stream in Brain ++ List results = new java.util.ArrayList<>(); ++ ++ for (MemoryModuleType memoryModuleType : memoryTypes) { ++ final Optional codec = memoryModuleType.getCodec(); ++ ++ if (codec.isPresent()) { ++ final net.minecraft.resources.ResourceLocation resourceLocation = BuiltInRegistries.MEMORY_MODULE_TYPE.getKey(memoryModuleType); ++ final T opsResult = ops.createString(resourceLocation.toString()); ++ ++ results.add(opsResult); ++ } ++ } ++ ++ return results.stream(); ++ // Leaf end - Remove stream in Brain + } + + @Override +@@ -111,7 +120,7 @@ public class Brain { + + @Override + public RecordBuilder encode(Brain input, DynamicOps ops, RecordBuilder prefix) { +- input.memories().forEach(memoryValue -> memoryValue.serialize(ops, prefix)); ++ input.serializeMemories(ops, prefix); // Leaf - Remove stream in Brain + return prefix; + } + }) +@@ -153,8 +162,28 @@ public class Brain { + } + + Stream> memories() { +- return this.memories.entrySet().stream().map(memory -> Brain.MemoryValue.createUnchecked(memory.getKey(), memory.getValue())); ++ // Leaf start - Remove stream in Brain ++ return memoriesList().stream(); ++ } ++ ++ List> memoriesList() { ++ List> result = new java.util.ArrayList<>(); ++ ++ for (Entry, Optional>> memory : this.memories.entrySet()) { ++ result.add(Brain.MemoryValue.createUnchecked(memory.getKey(), memory.getValue())); ++ } ++ ++ return result; ++ } ++ ++ void serializeMemories(DynamicOps dynamicOps, RecordBuilder recordBuilder) { ++ for (Entry, Optional>> memory : this.memories.entrySet()) { ++ final Brain.MemoryValue result = Brain.MemoryValue.createUnchecked(memory.getKey(), memory.getValue()); ++ ++ result.serialize(dynamicOps, recordBuilder); ++ } + } ++ // Leaf end - Remove stream in Brain + + public boolean hasMemoryValue(MemoryModuleType type) { + return this.checkMemory(type, MemoryStatus.VALUE_PRESENT); diff --git a/Leaf-Server/minecraft-patches/features/0061-Remove-stream-in-BehaviorUtils.patch b/Leaf-Server/minecraft-patches/features/0061-Remove-stream-in-BehaviorUtils.patch new file mode 100644 index 00000000..73940100 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0061-Remove-stream-in-BehaviorUtils.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Sat, 26 Oct 2024 00:56:24 +0800 +Subject: [PATCH] Remove stream in BehaviorUtils + +Dreeam TODO: Check this + +diff --git a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java +index 800bc29502ed46bd77cb04c0a79143898f109a48..77fa472d970c0f187f228b06e0b459ebdcaeb7fd 100644 +--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java ++++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java +@@ -109,10 +109,20 @@ public class BehaviorUtils { + + public static SectionPos findSectionClosestToVillage(ServerLevel serverLevel, SectionPos sectionPos, int radius) { + int i = serverLevel.sectionsToVillage(sectionPos); +- return SectionPos.cube(sectionPos, radius) +- .filter(pos -> serverLevel.sectionsToVillage(pos) < i) +- .min(Comparator.comparingInt(serverLevel::sectionsToVillage)) +- .orElse(sectionPos); ++ // Leaf start - Remove stream in BehaviorUtils ++ SectionPos closestSection = sectionPos; ++ int closestDistance = i; ++ ++ for (SectionPos pos : SectionPos.cube(sectionPos, radius).toList()) { ++ int distance = serverLevel.sectionsToVillage(pos); ++ if (distance < closestDistance) { ++ closestDistance = distance; ++ closestSection = pos; ++ } ++ } ++ ++ return closestSection; ++ // Leaf end - Remove stream in BehaviorUtils + } + + public static boolean isWithinAttackRange(Mob mob, LivingEntity target, int cooldown) { diff --git a/Leaf-Server/minecraft-patches/features/0062-Remove-stream-in-YieldJobSite.patch b/Leaf-Server/minecraft-patches/features/0062-Remove-stream-in-YieldJobSite.patch new file mode 100644 index 00000000..60f502b7 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0062-Remove-stream-in-YieldJobSite.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Sat, 26 Oct 2024 14:04:54 +0800 +Subject: [PATCH] Remove stream in YieldJobSite + + +diff --git a/net/minecraft/world/entity/ai/behavior/YieldJobSite.java b/net/minecraft/world/entity/ai/behavior/YieldJobSite.java +index 37ad79e201e36a1a9520219e3faa4dcffa7b4dfd..ccfbbdfe6860d6a211b07d384eba03f857ebf6b0 100644 +--- a/net/minecraft/world/entity/ai/behavior/YieldJobSite.java ++++ b/net/minecraft/world/entity/ai/behavior/YieldJobSite.java +@@ -38,23 +38,27 @@ public class YieldJobSite { + if (type.isEmpty()) { + return true; + } else { +- instance.>get(nearestLivingEntities) +- .stream() +- .filter(nearEntity -> nearEntity instanceof Villager && nearEntity != villager) +- .map(nearEntity -> (Villager)nearEntity) +- .filter(LivingEntity::isAlive) +- .filter(nearVillager -> nearbyWantsJobsite(type.get(), nearVillager, blockPos)) +- .findFirst() +- .ifPresent(nearVillager -> { +- walkTarget.erase(); +- lookTarget.erase(); +- potentialJobSite.erase(); +- if (nearVillager.getBrain().getMemory(MemoryModuleType.JOB_SITE).isEmpty()) { +- BehaviorUtils.setWalkAndLookTargetMemories(nearVillager, blockPos, speedModifier, 1); +- nearVillager.getBrain().setMemory(MemoryModuleType.POTENTIAL_JOB_SITE, GlobalPos.of(level.dimension(), blockPos)); +- DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ // Leaf start - Remove stream in YieldJobSite ++ List mobsList = instance.get(nearestLivingEntities); ++ for (LivingEntity nearEntity : mobsList) { ++ if (nearEntity instanceof Villager nearVillager && nearEntity != villager && nearEntity.isAlive()) { ++ if (nearbyWantsJobsite(type.get(), nearVillager, blockPos)) { ++ walkTarget.erase(); ++ lookTarget.erase(); ++ potentialJobSite.erase(); ++ ++ if (nearVillager.getBrain().getMemory(MemoryModuleType.JOB_SITE).isEmpty()) { ++ BehaviorUtils.setWalkAndLookTargetMemories(nearVillager, blockPos, speedModifier, 1); ++ nearVillager.getBrain().setMemory(MemoryModuleType.POTENTIAL_JOB_SITE, GlobalPos.of(level.dimension(), blockPos)); ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ ++ } ++ ++ break; + } +- }); ++ } ++ } ++ // Leaf end - Remove stream in YieldJobSite + return true; + } + } diff --git a/patches/server/0082-Remove-stream-in-PlayerSensor.patch b/Leaf-Server/minecraft-patches/features/0063-Remove-stream-in-PlayerSensor.patch similarity index 53% rename from patches/server/0082-Remove-stream-in-PlayerSensor.patch rename to Leaf-Server/minecraft-patches/features/0063-Remove-stream-in-PlayerSensor.patch index 0a331142..f6c2b118 100644 --- a/patches/server/0082-Remove-stream-in-PlayerSensor.patch +++ b/Leaf-Server/minecraft-patches/features/0063-Remove-stream-in-PlayerSensor.patch @@ -8,50 +8,51 @@ while ticking Villager farms, so just replace it with for loop =-= Before: 164ms After: 18ms -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 e1ff702e56adef6c8a572b078b49de2143c4ce7e..aa1a59bdaad5ac7b735524c3595a8589a92618cd 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 -@@ -22,17 +22,39 @@ public class PlayerSensor extends Sensor { +diff --git a/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index 6233e6b48aaa69ba9f577d0b480b1cdf2f55d16e..996c93f2b7ffd83134535f75c0ead45cc34ef13c 100644 +--- a/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -22,17 +22,40 @@ public class PlayerSensor extends Sensor { @Override - protected void doTick(ServerLevel world, LivingEntity entity) { -- List list = world.players() + protected void doTick(ServerLevel level, LivingEntity entity) { +- List list = level.players() - .stream() - .filter(EntitySelector.NO_SPECTATORS) -- .filter(player -> entity.closerThan(player, this.getFollowDistance(entity))) +- .filter(serverPlayer -> entity.closerThan(serverPlayer, this.getFollowDistance(entity))) - .sorted(Comparator.comparingDouble(entity::distanceToSqr)) - .collect(Collectors.toList()); + // Leaf start - Remove stream in PlayerSensor + List list = new java.util.ArrayList<>(); -+ for (Player player : world.players()) { -+ if (!EntitySelector.NO_SPECTATORS.test(player)) { ++ for (Player serverPlayer : level.players()) { ++ if (!EntitySelector.NO_SPECTATORS.test(serverPlayer)) { + continue; + } -+ if (!entity.closerThan(player, this.getFollowDistance(entity))) { ++ if (!entity.closerThan(serverPlayer, this.getFollowDistance(entity))) { + continue; + } -+ list.add(player); ++ ++ list.add(serverPlayer); + } + list.sort(Comparator.comparingDouble(entity::distanceToSqr)); + // Leaf end - Remove stream in PlayerSensor Brain brain = entity.getBrain(); brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, list); -- List list2 = list.stream().filter(player -> isEntityTargetable(world, entity, player)).collect(Collectors.toList()); +- List list1 = list.stream().filter(player -> isEntityTargetable(level, entity, player)).collect(Collectors.toList()); + // Leaf start - Remove stream in PlayerSensor -+ List list2 = new java.util.ArrayList<>(); ++ List list1 = new java.util.ArrayList<>(); + for (Player player : list) { -+ if (isEntityTargetable(world, entity, player)) { -+ list2.add(player); ++ if (isEntityTargetable(level, entity, player)) { ++ list1.add(player); + } + } + // Leaf end - Remove stream in PlayerSensor - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, list2.isEmpty() ? null : list2.get(0)); -- Optional optional = list2.stream().filter(player -> isEntityAttackable(world, entity, player)).findFirst(); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, list1.isEmpty() ? null : list1.get(0)); +- Optional optional = list1.stream().filter(player -> isEntityAttackable(level, entity, player)).findFirst(); + // Leaf start - Remove stream in PlayerSensor + Optional optional = Optional.empty(); -+ for (Player player : list2) { -+ if (isEntityAttackable(world, entity, player)) { ++ for (Player player : list1) { ++ if (isEntityAttackable(level, entity, player)) { + optional = Optional.of(player); + break; + } diff --git a/Leaf-Server/minecraft-patches/features/0064-Remove-stream-in-GolemSensor.patch b/Leaf-Server/minecraft-patches/features/0064-Remove-stream-in-GolemSensor.patch new file mode 100644 index 00000000..e6c21417 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0064-Remove-stream-in-GolemSensor.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Remove stream in GolemSensor + +Stream operations in GolemSensor is really expensive and takes +up 80% time per method call. +Before: 192ms +After: 17ms + +diff --git a/net/minecraft/world/entity/ai/sensing/GolemSensor.java b/net/minecraft/world/entity/ai/sensing/GolemSensor.java +index 84d9e2a43adbabda8401e8ad8dd8d87f7dbeeea7..ed277d93254a30a817dd8246539c292240dc9669 100644 +--- a/net/minecraft/world/entity/ai/sensing/GolemSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/GolemSensor.java +@@ -34,7 +34,15 @@ public class GolemSensor extends Sensor { + public static void checkForNearbyGolem(LivingEntity livingEntity) { + Optional> memory = livingEntity.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES); + if (!memory.isEmpty()) { +- boolean flag = memory.get().stream().anyMatch(entity -> entity.getType().equals(EntityType.IRON_GOLEM)); ++ // Leaf start - Remove stream in GolemSensor ++ boolean flag = false; ++ for (LivingEntity entity : memory.get()) { ++ if (entity.getType().equals(EntityType.IRON_GOLEM)) { ++ flag = true; ++ break; ++ } ++ } ++ // Leaf end - Remove stream in GolemSensor + if (flag) { + golemDetected(livingEntity); + } diff --git a/Leaf-Server/minecraft-patches/features/0065-Remove-stream-in-GateBehavior.patch b/Leaf-Server/minecraft-patches/features/0065-Remove-stream-in-GateBehavior.patch new file mode 100644 index 00000000..28b006b8 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0065-Remove-stream-in-GateBehavior.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Remove stream in GateBehavior + + +diff --git a/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/net/minecraft/world/entity/ai/behavior/GateBehavior.java +index bd31d1cac0d022a72bd536c41d1ef811886e7068..2830792cd98c0849280aa1e2116fa89f3c8d2c85 100644 +--- a/net/minecraft/world/entity/ai/behavior/GateBehavior.java ++++ b/net/minecraft/world/entity/ai/behavior/GateBehavior.java +@@ -73,9 +73,19 @@ public class GateBehavior implements BehaviorControl + } + } + // Paper end - Perf: Remove streams from hot code +- if (this.behaviors.stream().noneMatch(behavior -> behavior.getStatus() == Behavior.Status.RUNNING)) { ++ // Leaf start - Remove more streams in GateBehavior ++ boolean hasRunningTask = false; ++ for (final BehaviorControl behavior : this.behaviors) { ++ if (behavior.getStatus() == Behavior.Status.RUNNING) { ++ hasRunningTask = true; ++ break; ++ } ++ } ++ ++ if (!hasRunningTask) { + this.doStop(level, entity, gameTime); + } ++ // Leaf end - Remove more streams in GateBehavior + } + + @Override diff --git a/Leaf-Server/minecraft-patches/features/0066-Remove-stream-in-updateFluidOnEyes.patch b/Leaf-Server/minecraft-patches/features/0066-Remove-stream-in-updateFluidOnEyes.patch new file mode 100644 index 00000000..d06899ee --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0066-Remove-stream-in-updateFluidOnEyes.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 11 Nov 2024 16:55:50 -0500 +Subject: [PATCH] Remove stream in updateFluidOnEyes + + +diff --git a/net/minecraft/core/Holder.java b/net/minecraft/core/Holder.java +index 6c7edbbf3935c40ccb78bee680ea75431718b9bd..a1b4dc70d555cce5e06c0298736d8b89e04a96be 100644 +--- a/net/minecraft/core/Holder.java ++++ b/net/minecraft/core/Holder.java +@@ -29,6 +29,8 @@ public interface Holder { + + Stream> tags(); + ++ Set> tagsAsSet(); // Leaf - Remove stream in updateFluidOnEyes ++ + Either, T> unwrap(); + + Optional> unwrapKey(); +@@ -105,6 +107,13 @@ public interface Holder { + public Stream> tags() { + return Stream.of(); + } ++ ++ // Leaf start - Remove stream in updateFluidOnEyes ++ @Override ++ public Set> tagsAsSet() { ++ return Set.of(); ++ } ++ // Leaf end - Remove stream in updateFluidOnEyes + } + + public static enum Kind { +@@ -238,6 +247,13 @@ public interface Holder { + return this.boundTags().stream(); + } + ++ // Leaf start - Remove stream in updateFluidOnEyes ++ @Override ++ public Set> tagsAsSet() { ++ return this.boundTags(); ++ } ++ // Leaf end - Remove stream in updateFluidOnEyes ++ + @Override + public String toString() { + return "Reference{" + this.key + "=" + this.value + "}"; +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index f7f330758dce7c38e0e0e8ba420df4d83f765cf1..42a6b0b6fe13b448583988b3d2f840540b5670e7 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -1982,7 +1982,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + FluidState fluidState = this.level().getFluidState(blockPos); + double d = blockPos.getY() + fluidState.getHeight(this.level(), blockPos); + if (d > eyeY) { +- fluidState.getTags().forEach(this.fluidOnEyes::add); ++ this.fluidOnEyes.addAll(fluidState.getTagsAsSet()); // Leaf - Remove stream in updateFluidOnEyes ++ + } + } + } +diff --git a/net/minecraft/world/level/material/FluidState.java b/net/minecraft/world/level/material/FluidState.java +index 481cb46973acb9785fdee5732e98aac560c6ec08..06581fe010ca722d62d0b6d3c44d845f9db0231f 100644 +--- a/net/minecraft/world/level/material/FluidState.java ++++ b/net/minecraft/world/level/material/FluidState.java +@@ -158,4 +158,10 @@ public final class FluidState extends StateHolder implements + public Stream> getTags() { + return this.owner.builtInRegistryHolder().tags(); + } ++ ++ // Leaf start - Remove stream in updateFluidOnEyes ++ public java.util.Set> getTagsAsSet() { ++ return this.owner.builtInRegistryHolder().tagsAsSet(); ++ } ++ // Leaf end - Remove stream in updateFluidOnEyes + } diff --git a/patches/server/0086-Remove-stream-in-matchingSlot.patch b/Leaf-Server/minecraft-patches/features/0067-Remove-stream-in-matchingSlot.patch similarity index 51% rename from patches/server/0086-Remove-stream-in-matchingSlot.patch rename to Leaf-Server/minecraft-patches/features/0067-Remove-stream-in-matchingSlot.patch index b8d83ad9..b63b7c81 100644 --- a/patches/server/0086-Remove-stream-in-matchingSlot.patch +++ b/Leaf-Server/minecraft-patches/features/0067-Remove-stream-in-matchingSlot.patch @@ -4,18 +4,18 @@ Date: Tue, 26 Nov 2024 19:58:29 -0500 Subject: [PATCH] Remove stream in matchingSlot -diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java -index e2a928b0271a280debe684f09c6ed8c7655aa4a2..bcec7370b875e403ffd4cec179ba07d80b1bc3a0 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantment.java +diff --git a/net/minecraft/world/item/enchantment/Enchantment.java b/net/minecraft/world/item/enchantment/Enchantment.java +index 7a620eb92b1e672cedd72ec4d986c01eba337686..183874d90d576d740c5d924accc5c0d7fdb8450c 100644 +--- a/net/minecraft/world/item/enchantment/Enchantment.java ++++ b/net/minecraft/world/item/enchantment/Enchantment.java @@ -126,7 +126,15 @@ public record Enchantment(Component description, Enchantment.EnchantmentDefiniti } public boolean matchingSlot(EquipmentSlot slot) { -- return this.definition.slots().stream().anyMatch(slotx -> slotx.test(slot)); +- return this.definition.slots().stream().anyMatch(equipmentSlotGroup -> equipmentSlotGroup.test(slot)); + // Leaf start - Remove stream in matchingSlot -+ for (EquipmentSlotGroup slotx : this.definition.slots()) { -+ if (slotx.test(slot)) { ++ for (EquipmentSlotGroup equipmentSlotGroup : this.definition.slots()) { ++ if (equipmentSlotGroup.test(slot)) { + return true; + } + } diff --git a/patches/server/0087-Replace-Entity-active-effects-map-with-optimized-col.patch b/Leaf-Server/minecraft-patches/features/0068-Replace-Entity-active-effects-map-with-optimized-col.patch similarity index 62% rename from patches/server/0087-Replace-Entity-active-effects-map-with-optimized-col.patch rename to Leaf-Server/minecraft-patches/features/0068-Replace-Entity-active-effects-map-with-optimized-col.patch index c90570f5..1c62be1c 100644 --- a/patches/server/0087-Replace-Entity-active-effects-map-with-optimized-col.patch +++ b/Leaf-Server/minecraft-patches/features/0068-Replace-Entity-active-effects-map-with-optimized-col.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Replace Entity active effects map with optimized collection Dreeam TODO: check this -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 39c898e0b5ec7203491982ef56cfdcf507d040d7..7aab6970bf73108435e79a6ef39896e9fca8659e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -208,6 +208,10 @@ public abstract class LivingEntity extends Entity implements Attackable { +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 8b096a0f9f23a9b90ef88414e8749a514e3910cf..d8a93bd85580c4bdc52d6f368b78ad210c265ae5 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -211,6 +211,10 @@ public abstract class LivingEntity extends Entity implements Attackable { }; private final AttributeMap attributes; public CombatTracker combatTracker = new CombatTracker(this); @@ -18,9 +18,9 @@ index 39c898e0b5ec7203491982ef56cfdcf507d040d7..7aab6970bf73108435e79a6ef39896e9 + // Also need to check whether call from out of main using bukkit api + //public final Map, MobEffectInstance> activeEffects = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(0); // Leaf - Replace Entity active effects map with optimized collection public final Map, MobEffectInstance> activeEffects = Maps.newHashMap(); - private final NonNullList lastHandItemStacks; - private final NonNullList lastArmorItemStacks; -@@ -1031,8 +1035,9 @@ public abstract class LivingEntity extends Entity implements Attackable { + private final NonNullList lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY); + private final NonNullList lastArmorItemStacks = NonNullList.withSize(4, ItemStack.EMPTY); +@@ -990,15 +994,16 @@ public abstract class LivingEntity extends Entity implements Attackable { private void updateSynchronizedMobEffectParticles() { // Leaf start - Remove stream in entity visible effects filter List list = new ArrayList<>(); @@ -31,12 +31,11 @@ index 39c898e0b5ec7203491982ef56cfdcf507d040d7..7aab6970bf73108435e79a6ef39896e9 if (effect.isVisible()) { list.add(effect.getParticleOptions()); } -@@ -1040,7 +1045,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } // Leaf end - Remove stream in entity visible effects filter - - this.entityData.set(LivingEntity.DATA_EFFECT_PARTICLES, list); -- this.entityData.set(LivingEntity.DATA_EFFECT_AMBIENCE_ID, LivingEntity.areAllEffectsAmbient(this.activeEffects.values())); -+ this.entityData.set(LivingEntity.DATA_EFFECT_AMBIENCE_ID, LivingEntity.areAllEffectsAmbient(effectsValues)); // Leaf - Replace Entity active effects map with optimized collection + this.entityData.set(DATA_EFFECT_PARTICLES, list); +- this.entityData.set(DATA_EFFECT_AMBIENCE_ID, areAllEffectsAmbient(this.activeEffects.values())); ++ this.entityData.set(DATA_EFFECT_AMBIENCE_ID, areAllEffectsAmbient(effectsValues)); // Leaf - Replace Entity active effects map with optimized collection } private void updateGlowingStatus() { diff --git a/patches/server/0088-Replace-criterion-map-with-optimized-collection.patch b/Leaf-Server/minecraft-patches/features/0069-Replace-criterion-map-with-optimized-collection.patch similarity index 64% rename from patches/server/0088-Replace-criterion-map-with-optimized-collection.patch rename to Leaf-Server/minecraft-patches/features/0069-Replace-criterion-map-with-optimized-collection.patch index 0cd5fccd..dd032de4 100644 --- a/patches/server/0088-Replace-criterion-map-with-optimized-collection.patch +++ b/Leaf-Server/minecraft-patches/features/0069-Replace-criterion-map-with-optimized-collection.patch @@ -4,16 +4,16 @@ Date: Sat, 7 Sep 2024 02:12:55 -0400 Subject: [PATCH] Replace criterion map with optimized collection -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index d92fa5893fa030cedf63cab9cc5f2b941af02290..134946144b60a3f400652302c90e3ec0ed035a40 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -63,7 +63,7 @@ public class PlayerAdvancements { +diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java +index e4ea26ae84efde7ce54e08a246a6ea2ae2a17151..26de042df58bcf486ed538f66edf2d37caab6ed6 100644 +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -60,7 +60,7 @@ public class PlayerAdvancements { private AdvancementHolder lastSelectedTab; private boolean isFirstPacket = true; private final Codec codec; - public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage + public final Map, Set>> criterionData = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(); // Paper - fix advancement data player leakage // Leaf - Replace criterion map with optimized collection - public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, Path filePath, ServerPlayer owner) { - this.playerList = playerManager; + public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) { + this.playerList = playerList; diff --git a/patches/server/0090-Replace-brain-maps-with-optimized-collection.patch b/Leaf-Server/minecraft-patches/features/0070-Replace-brain-maps-with-optimized-collection.patch similarity index 87% rename from patches/server/0090-Replace-brain-maps-with-optimized-collection.patch rename to Leaf-Server/minecraft-patches/features/0070-Replace-brain-maps-with-optimized-collection.patch index ec8bfade..5c09f5a6 100644 --- a/patches/server/0090-Replace-brain-maps-with-optimized-collection.patch +++ b/Leaf-Server/minecraft-patches/features/0070-Replace-brain-maps-with-optimized-collection.patch @@ -4,10 +4,10 @@ Date: Sat, 26 Oct 2024 00:06:04 +0800 Subject: [PATCH] Replace brain maps with optimized collection -diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java -index 88ba505fc5ca084317aaf6be402472ccd42413d8..65bd8c2cccd0a4a68984ea8ff4cd3cf365330630 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/Brain.java -+++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index 402279ff78f9e9eb74c63d35ec1968d239228d70..5eaa1dec9fbe72a22c34331a28f7b7115fb3aeba 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java @@ -45,14 +45,18 @@ public class Brain { static final Logger LOGGER = LogUtils.getLogger(); private final Supplier>> codec; diff --git a/Leaf-Server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch b/Leaf-Server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch new file mode 100644 index 00000000..b43a69f7 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Fri, 14 Jun 2024 23:19:55 +0800 +Subject: [PATCH] Reduce worldgen allocations + +This change optimizes the way SurfaceRules update their biome supplier,avoiding unnecessary object creations and thus reducing memory allocations +during world generation. The update method now reuses the existing PositionalBiomeGetter object if it's already present, otherwise it +initializes a new one. +Additionally, the tryApply method in SurfaceRules now avoids iterator +allocation by directly accessing the rules list, which further contributes +to reducing garbage collection pressure during world generation. + +diff --git a/net/minecraft/world/level/levelgen/NoiseChunk.java b/net/minecraft/world/level/levelgen/NoiseChunk.java +index f861f9e087182470a3bbb22678dbdacb8a73e943..18304ff64a3b06713deac192cf0555bc651ec2b8 100644 +--- a/net/minecraft/world/level/levelgen/NoiseChunk.java ++++ b/net/minecraft/world/level/levelgen/NoiseChunk.java +@@ -362,7 +362,16 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + + protected DensityFunction wrap(DensityFunction densityFunction) { +- return this.wrapped.computeIfAbsent(densityFunction, this::wrapNew); ++ // Leaf start - Avoid lambda allocation ++ DensityFunction func = this.wrapped.get(densityFunction); ++ ++ if (func == null) { ++ func = this.wrapNew(densityFunction); ++ this.wrapped.put(densityFunction, func); ++ } ++ ++ return func; ++ // Leaf end - Avoid lambda allocation + } + + private DensityFunction wrapNew(DensityFunction densityFunction) { +diff --git a/net/minecraft/world/level/levelgen/SurfaceRules.java b/net/minecraft/world/level/levelgen/SurfaceRules.java +index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..1d3dc17a5ba48ae03681d769c78238732f5f56bf 100644 +--- a/net/minecraft/world/level/levelgen/SurfaceRules.java ++++ b/net/minecraft/world/level/levelgen/SurfaceRules.java +@@ -313,8 +313,14 @@ public class SurfaceRules { + } + + protected void updateY(int stoneDepthAbove, int stoneDepthBelow, int waterHeight, int blockX, int blockY, int blockZ) { +- this.lastUpdateY++; +- this.biome = Suppliers.memoize(() -> this.biomeGetter.apply(this.pos.set(blockX, blockY, blockZ))); ++ // Leaf start - Reuse supplier object instead of creating new ones every time ++ ++this.lastUpdateY; ++ Supplier> getter = this.biome; ++ if (getter == null) { ++ this.biome = getter = new org.dreeam.leaf.util.biome.PositionalBiomeGetter(this.biomeGetter, this.pos); ++ } ++ ((org.dreeam.leaf.util.biome.PositionalBiomeGetter) getter).update(blockX, blockY, blockZ); ++ // Leaf end - Reuse supplier object instead of creating new ones every time + this.blockY = blockY; + this.waterHeight = waterHeight; + this.stoneDepthBelow = stoneDepthBelow; +@@ -582,8 +588,12 @@ public class SurfaceRules { + @Nullable + @Override + public BlockState tryApply(int x, int y, int z) { +- for (SurfaceRules.SurfaceRule surfaceRule : this.rules) { +- BlockState blockState = surfaceRule.tryApply(x, y, z); ++ // Leaf start - Avoid iterator allocation ++ int size = this.rules.size(); ++ //noinspection ForLoopReplaceableByForEach ++ for (int i = 0; i < size; i++) { ++ BlockState blockState = this.rules.get(i).tryApply(x, y, z); ++ // Leaf end - Avoid iterator allocation + if (blockState != null) { + return blockState; + } +diff --git a/net/minecraft/world/level/levelgen/material/MaterialRuleList.java b/net/minecraft/world/level/levelgen/material/MaterialRuleList.java +index 1605cc013d5a89a5d3cb68365bdcc18e2dd0a921..923f1d2058d66822e28cf3aca2b0dba6150c24fb 100644 +--- a/net/minecraft/world/level/levelgen/material/MaterialRuleList.java ++++ b/net/minecraft/world/level/levelgen/material/MaterialRuleList.java +@@ -9,13 +9,16 @@ public record MaterialRuleList(NoiseChunk.BlockStateFiller[] materialRuleList) i + @Nullable + @Override + public BlockState calculate(DensityFunction.FunctionContext context) { +- for (NoiseChunk.BlockStateFiller blockStateFiller : this.materialRuleList) { +- BlockState blockState = blockStateFiller.calculate(context); +- if (blockState != null) { +- return blockState; +- } ++ // Leaf start - Avoid iterator allocation ++ BlockState blockState = null; ++ int length = this.materialRuleList.length; ++ ++ for (int i = 0; blockState == null && i < length; i++) { ++ NoiseChunk.BlockStateFiller blockStateFiller = this.materialRuleList[i]; ++ blockState = blockStateFiller.calculate(context); + } + +- return null; ++ return blockState; ++ // Leaf end - Avoid iterator allocation + } + } diff --git a/patches/server/0092-Use-caffeine-cache-kickPermission-instead-of-using-g.patch b/Leaf-Server/minecraft-patches/features/0072-Use-caffeine-cache-kickPermission-instead-of-using-g.patch similarity index 51% rename from patches/server/0092-Use-caffeine-cache-kickPermission-instead-of-using-g.patch rename to Leaf-Server/minecraft-patches/features/0072-Use-caffeine-cache-kickPermission-instead-of-using-g.patch index fe150b13..b3d3f722 100644 --- a/patches/server/0092-Use-caffeine-cache-kickPermission-instead-of-using-g.patch +++ b/Leaf-Server/minecraft-patches/features/0072-Use-caffeine-cache-kickPermission-instead-of-using-g.patch @@ -5,38 +5,38 @@ Subject: [PATCH] Use caffeine cache kickPermission instead of using google.common.cache -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 615622f3ce63d2150c57cfd9f664bfa8fa5d6124..4ecefd90defffeac792d4cb2375ee2d68513b170 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -344,17 +344,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit end +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 39b01b3d96d03bca4c33eaddfc54edef23c3df0c..14c75c85c062fb9d2b576bb1cd24f942642fbf8c 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -328,17 +328,12 @@ public class ServerGamePacketListenerImpl + } - // Purpur start -- private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() + // Purpur start - AFK API +- private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() + // Leaf start - Use caffeine cache kickPermission instead of using google.common.cache -+ private final com.github.benmanes.caffeine.cache.LoadingCache kickPermissionCache = com.github.benmanes.caffeine.cache.Caffeine.newBuilder() ++ private final com.github.benmanes.caffeine.cache.LoadingCache kickPermissionCache = com.github.benmanes.caffeine.cache.Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES) - .build( - new com.google.common.cache.CacheLoader<>() { - @Override -- public Boolean load(CraftPlayer player) { +- public Boolean load(org.bukkit.craftbukkit.entity.CraftPlayer player) { - return player.hasPermission("purpur.bypassIdleKick"); - } - } - ); -+ .build(player -> player.hasPermission("purpur.bypassIdleKick")); ++ .build(player -> player.hasPermission("purpur.bypassIdleKick")); + // Leaf - Use caffeine cache kickPermission instead of using google.common.cache - // Purpur end + // Purpur end - AFK API public final org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget exchangeTarget; // Leaves - Syncmatica Protocol -@@ -417,7 +412,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits - // Purpur start +@@ -403,7 +398,7 @@ public class ServerGamePacketListenerImpl + && Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits + // Purpur start - AFK API this.player.setAfk(true); - if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) { + if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.get(this.player.getBukkitEntity()))) { // Leaf - Use caffeine cache kickPermission instead of using google.common.cache return; } - // Purpur end + // Purpur end - AFK API diff --git a/Leaf-Server/minecraft-patches/features/0073-Do-not-place-player-if-the-server-is-full.patch b/Leaf-Server/minecraft-patches/features/0073-Do-not-place-player-if-the-server-is-full.patch new file mode 100644 index 00000000..4ef57305 --- /dev/null +++ b/Leaf-Server/minecraft-patches/features/0073-Do-not-place-player-if-the-server-is-full.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 24 Jun 2024 10:49:04 +0800 +Subject: [PATCH] Do not place player if the server is full + +Fix https://github.com/PaperMC/Paper/issues/10668 + +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 5fe6a295f5b5285a20c535a026e1342f89896fd7..8233b250a8d0e9f26302f6756931d2edfe017504 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -340,6 +340,13 @@ public abstract class PlayerList { + return; + } + // Gale end - MultiPaper - do not place player in world if kicked before being spawned in ++ // Leaf start - Do not place player if the server is full - copied from canPlayerLogin ++ if (org.dreeam.leaf.config.modules.fixes.DontPlacePlayerIfFull.enabled && this.realPlayers.size() >= this.maxPlayers && !(player.getBukkitEntity().hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission // Leaves - only real player ++ connection.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage))); ++ //playerconnection.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ return; ++ } ++ // Leaf end - Do not place player if the server is full - copied from canPlayerLogin + + org.bukkit.Location loc = ev.getSpawnLocation(); + serverLevel = ((org.bukkit.craftbukkit.CraftWorld) loc.getWorld()).getHandle(); +@@ -834,7 +841,7 @@ public abstract class PlayerList { + // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile) + // ? Component.translatable("multiplayer.disconnect.server_full") + // : null; +- if (this.realPlayers.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission // Leaves - only real player ++ if (this.realPlayers.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission // Leaves - only real player // Leaf - Do not place player if the server is full - diff on change + event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure + } + } diff --git a/Leaf-Server/paper-patches/features/0001-Rebrand.patch b/Leaf-Server/paper-patches/features/0001-Rebrand.patch new file mode 100644 index 00000000..e04e5423 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0001-Rebrand.patch @@ -0,0 +1,199 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Thu, 16 Sep 2021 20:39:45 -0400 +Subject: [PATCH] Rebrand + + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +index 3d8ed4ff9a5a30d123508aeb485250271b528a6e..aa7c5d765114a008e815fcece8c108e95f02293d 100644 +--- a/src/main/java/com/destroystokyo/paper/Metrics.java ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -592,7 +592,7 @@ public class Metrics { + boolean logFailedRequests = config.getBoolean("logFailedRequests", false); + // Only start Metrics, if it's enabled in the config + if (config.getBoolean("enabled", true)) { +- Metrics metrics = new Metrics("Gale", serverUUID, logFailedRequests, Bukkit.getLogger()); // Gale - branding changes - metrics ++ Metrics metrics = new Metrics("Leaf", serverUUID, logFailedRequests, Bukkit.getLogger()); // Gale - branding changes - metrics // Leaf + + metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { + String minecraftVersion = Bukkit.getVersion(); +@@ -602,15 +602,15 @@ public class Metrics { + + metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); +- final String galeVersion; // Gale - branding changes - metrics ++ final String leafVersion; // Gale - branding changes - metrics // Leaf + final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion(); + if (implVersion != null) { + final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1); +- galeVersion = "git-Gale-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Gale - branding changes - metrics ++ leafVersion = "git-Leaf-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Gale - branding changes - metrics // Leaf + } else { +- galeVersion = "unknown"; // Gale - branding changes - metrics ++ leafVersion = "unknown"; // Gale - branding changes - metrics // Leaf + } +- metrics.addCustomChart(new Metrics.SimplePie("gale_version", () -> galeVersion)); // Gale - branding changes - metrics ++ metrics.addCustomChart(new Metrics.SimplePie("leaf_version", () -> leafVersion)); // Gale - branding changes - metrics // Leaf + + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(2); // Gale - metrics - reduce HashMap capacity +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index b78828e83d8128eace986aeb73213da3b3f905e4..9869bec65f4d0fbd5ed5aff896a8956e7ea2747f 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -20,7 +20,7 @@ public final class PaperConsole extends SimpleTerminalConsole { + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + builder +- .appName("Gale") // Gale - branding changes ++ .appName("Leaf") // Gale - branding changes // Leaf + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) + .option(LineReader.Option.COMPLETE_IN_WORD, true); +diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +index 2596e0ee4df5b96f181e28a742ef345981fc97e3..ea2bca6e0bbc56156c2f744769abd677e6fb0c18 100644 +--- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java ++++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +@@ -32,6 +32,7 @@ public record ServerBuildInfoImpl( + + private static final String BRAND_PAPER_NAME = "Paper"; + private static final String BRAND_GALE_NAME = "Gale"; // Gale - branding changes ++ private static final String BRAND_LEAF_NAME = "Leaf"; // Leaf + + private static final String BUILD_DEV = "DEV"; + +@@ -43,9 +44,9 @@ public record ServerBuildInfoImpl( + this( + getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) + .map(Key::key) +- .orElse(BRAND_GALE_ID), // Gale - branding changes ++ .orElse(BRAND_LEAF_ID), // Gale - branding changes // Leaf + getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) +- .orElse(BRAND_GALE_NAME), // Gale - branding changes ++ .orElse(BRAND_LEAF_NAME), // Gale - branding changes // Leaf + SharedConstants.getCurrentVersion().getId(), + SharedConstants.getCurrentVersion().getName(), + getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) +@@ -62,7 +63,9 @@ public record ServerBuildInfoImpl( + + @Override + public boolean isBrandCompatible(final @NotNull Key brandId) { +- return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID); // Gale - branding changes ++ return brandId.equals(this.brandId) ++ || brandId.equals(BRAND_PAPER_ID) ++ || brandId.equals(BRAND_GALE_ID); // Gale - branding changes // Leaf + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index f6bc955c3496b52cda1a20aabd78769803ef471f..bbeb271fb19091897e99970198a8a110a4aa0858 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -491,7 +491,7 @@ public class CraftScheduler implements BukkitScheduler { + this.parsePending(); + } else { + // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper +- task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Gale"); // Paper // Gale - branding changes ++ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Leaf"); // Paper // Gale - branding changes // Leaf + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ebe1136e2487d0d13e5a924a644f2237900a86a6..1b54fb1a321f0538c0ecded9fa14b2bc588416b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -508,7 +508,7 @@ public final class CraftMagicNumbers implements UnsafeValues { + // Paper start + @Override + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { +- return new org.galemc.gale.version.GaleVersionFetcher(); // Gale - branding changes - version fetcher ++ return new org.dreeam.leaf.version.LeafVersionFetcher(); // Gale - branding changes - version fetcher // Leaf + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 0b5979723bb30f9011ac64c36d894aa41713ec9b..e220f5601f6b92b7b280ce8ebe64117d30192b0e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +@@ -11,7 +11,7 @@ public final class Versioning { + public static String getBukkitVersion() { + String result = "Unknown-Version"; + +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.galemc.gale/gale-api/pom.properties"); // Gale - branding changes ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/cn.dreeam.leaf/leaf-api/pom.properties"); // Gale - branding changes // Leaf + Properties properties = new Properties(); + + if (stream != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.rej b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.rej +new file mode 100644 +index 0000000000000000000000000000000000000000..0d8c2c5d1c88f635a1f0b6a9fc311d9921a0a3d7 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.rej +@@ -0,0 +1,10 @@ ++diff a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java (rejected hunks) ++@@ -11,7 +11,7 @@ public final class Versioning { ++ public static String getBukkitVersion() { ++ String result = "Unknown-Version"; ++ ++- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.galemc.gale/gale-api/pom.properties"); // Gale - branding changes +++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/cn.dreeam.leaf/leaf-api/pom.properties"); // Gale - branding changes // Leaf ++ Properties properties = new Properties(); ++ ++ if (stream != null) { +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 64e3c7bd0a1ff93dd87e688f9e49e213c8f6670e..6bc6a318286cd6a1d5464a9b139fb9b8ca4a219e 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -25,7 +25,7 @@ public class WatchdogThread extends Thread { + private volatile boolean stopping; + + private WatchdogThread(long timeoutTime, boolean restart) { +- super("Paper Watchdog Thread"); ++ super("Watchdog Thread"); // Leaf - Purpur - use a generic name + this.timeoutTime = timeoutTime; + this.restart = restart; + this.earlyWarningEvery = Math.min(GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper +@@ -82,15 +82,15 @@ public class WatchdogThread extends Thread { + We do not want people to report thread issues to Paper, + but we do want people to report thread issues to Gale. + */ +- logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug. This could be a Gale bug."); // Paper ++ logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug. This could be a Leaf bug."); // Paper // Leaf + // Gale end - branding changes + logger.log(Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author"); + logger.log(Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring"); + logger.log(Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once"); + logger.log(Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes"); +- logger.log(Level.SEVERE, "If you are unsure or think this is a Gale bug, please report this to https://github.com/GaleMC/Gale/issues - and if you think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues"); // Gale - branding changes ++ logger.log(Level.SEVERE, "If you are unsure or think this is a Leaf bug, please report this to https://github.com/Winds-Studio/Leaf/issues - and if you think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues"); // Gale - branding changes // Leaf + logger.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports"); +- logger.log(Level.SEVERE, "Gale version: " + Bukkit.getServer().getVersion()); // Gale - branding changes ++ logger.log(Level.SEVERE, "Leaf version: " + Bukkit.getServer().getVersion()); // Gale - branding changes // Leaf + + if (net.minecraft.world.level.Level.lastPhysicsProblem != null) { + logger.log(Level.SEVERE, "------------------------------"); +@@ -115,13 +115,13 @@ public class WatchdogThread extends Thread { + We do not want people to report thread issues to Paper, + but we do want people to report thread issues to Gale. + */ +- logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues - THIS IS NOT A PAPER BUG OR CRASH - " + Bukkit.getServer().getVersion() + " ---"); ++ logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues - THIS IS NOT A PAPER BUG OR CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Leaf + // Gale end - branding changes + logger.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); + } + // Paper end - Different message for short timeout + logger.log(Level.SEVERE, "------------------------------"); +- logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Gale!):"); // Paper // Gale - branding changes ++ logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Leaf!):"); // Paper // Gale - branding changes // Leaf + FeatureHooks.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - log detailed tick information + WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger); + logger.log(Level.SEVERE, "------------------------------"); +@@ -139,7 +139,7 @@ public class WatchdogThread extends Thread { + We do not want people to report thread issues to Paper, + but we do want people to report thread issues to Gale. + */ +- logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues - THIS IS NOT A PAPER BUG OR CRASH ---"); ++ logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues - THIS IS NOT A PAPER BUG OR CRASH ---"); // Leaf + // Gale end - branding changes + } + diff --git a/patches/server/0004-Leaf-Bootstrap.patch b/Leaf-Server/paper-patches/features/0002-Leaf-Bootstrap.patch similarity index 61% rename from patches/server/0004-Leaf-Bootstrap.patch rename to Leaf-Server/paper-patches/features/0002-Leaf-Bootstrap.patch index acbc7cf2..cea76d2e 100644 --- a/patches/server/0004-Leaf-Bootstrap.patch +++ b/Leaf-Server/paper-patches/features/0002-Leaf-Bootstrap.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Leaf Bootstrap org.bukkit.craftbukkit.Main#main -> LeafBootstrap -> PaperBootstrap -> ... diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index be0d38544395a9b3befb898bb961f34e32fe9509..50a2b6e73ba4d8ad65582b2544fa409fd44f36d5 100644 +index ecb0fcd1f3b3f3d7751eded3cdf0977c1889c9ed..1df9bed6d3dc1e28898af8d5ad6a854dd5ccab1b 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -278,7 +278,8 @@ public class Main { @@ -19,25 +19,3 @@ index be0d38544395a9b3befb898bb961f34e32fe9509..50a2b6e73ba4d8ad65582b2544fa409f } catch (Throwable t) { t.printStackTrace(); } -diff --git a/src/main/java/org/dreeam/leaf/LeafBootstrap.java b/src/main/java/org/dreeam/leaf/LeafBootstrap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..316654051b80ac0fd62cf3b7a0e1b91010ec24b7 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/LeafBootstrap.java -@@ -0,0 +1,16 @@ -+package org.dreeam.leaf; -+ -+import io.papermc.paper.PaperBootstrap; -+import joptsimple.OptionSet; -+ -+public class LeafBootstrap { -+ -+ public static void boot(final OptionSet options) { -+ runPreBootTasks(); -+ -+ PaperBootstrap.boot(options); -+ } -+ -+ private static void runPreBootTasks() { -+ } -+} diff --git a/Leaf-Server/paper-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch b/Leaf-Server/paper-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch new file mode 100644 index 00000000..b7143462 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Raneri +Date: Wed, 10 Nov 2021 00:37:03 -0500 +Subject: [PATCH] Pufferfish: Optimize mob spawning + +Original license: GPL v3 +Original project: https://github.com/pufferfish-gg/Pufferfish + +Co-authored-by: booky10 + +This patch aims to reduce the main-thread impact of mob spawning by +offloading as much work as possible to other threads. It is possible for +inconsistencies to come up, but when they happen they never interfere +with the server's operation (they don't produce errors), and side +effects are limited to more or less mobs being spawned in any particular +tick. + +It is possible to disable this optimization if it is not required or if +it interferes with any plugins. On servers with thousands of entities, +this can result in performance gains of up to 15%, which is significant +and, in my opinion, worth the low risk of minor mob-spawning-related +inconsistencies. + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +index c21e00812f1aaa1279834a0562d360d6b89e146c..877d2095a066854939f260ca4b0b8c7b5abb620f 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +@@ -18,7 +18,7 @@ public final class IteratorSafeOrderedReferenceSet { + + private final double maxFragFactor; + +- private int iteratorCount; ++ private final java.util.concurrent.atomic.AtomicInteger iteratorCount = new java.util.concurrent.atomic.AtomicInteger(); // Pufferfish - async mob spawning + + public IteratorSafeOrderedReferenceSet() { + this(16, 0.75f, 16, 0.2); +@@ -79,7 +79,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + public int createRawIterator() { +- ++this.iteratorCount; ++ this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning + if (this.indexMap.isEmpty()) { + return -1; + } else { +@@ -100,7 +100,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + public void finishRawIterator() { +- if (--this.iteratorCount == 0) { ++ if (this.iteratorCount.decrementAndGet() == 0) { // Pufferfish - async mob spawning + if (this.getFragFactor() >= this.maxFragFactor) { + this.defrag(); + } +@@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet { + throw new IllegalStateException(); + } + this.listElements[index] = null; +- if (this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { ++ if (this.iteratorCount.get() == 0 && this.getFragFactor() >= this.maxFragFactor) { // Pufferfish - async mob spawning + this.defrag(); + } + //this.check(); +@@ -219,7 +219,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + + public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { +- ++this.iteratorCount; ++ this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning + return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); + } + diff --git a/Leaf-Server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch b/Leaf-Server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch new file mode 100644 index 00000000..84f1b8b7 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch @@ -0,0 +1,2033 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Github Actions +Date: Thu, 16 Jan 2025 11:21:12 +0000 +Subject: [PATCH] Purpur Server Paper Changes + +Original license: MIT +Original project: https://github.com/PurpurMC/Purpur + +Commit: dd4143984219cea8440913b7918322b5ba59265a + +Patches listed below are removed in this patch, They exists in Gale or Leaf: +* "Rebrand.patch" +* "com/destroystokyo/paper/Metrics.java.patch" + - Rebrand +* "com/destroystokyo/paper/PaperVersionFetcher.java.patch" + - Rebrand +* "com/destroystokyo/paper/console/PaperConsole.java.patch" + - Rebrand +* "com/destroystokyo/paper/gui/RAMDetails.java.patch" + - Add 5 second tps average in /tps +* "io/papermc/paper/ServerBuildInfoImpl.java.patch" + - Rebrand +* "org/bukkit/craftbukkit/CraftServer.java.patch" + - Add 5 second tps average in /tps +* "org/bukkit/craftbukkit/legacy/CraftLegacy.java.patch" + - Logger settings (suppressing pointless logs) +* "org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch" + - Rebrand +* "org/bukkit/craftbukkit/util/CraftMagicNumbers.java.patch" + - Rebrand +* "org/bukkit/craftbukkit/util/Versioning.java.patch" + - Rebrand +* "org/spigotmc/TicksPerSecondCommand.java.patch" + - Add 5 second tps average in /tps +* "org/spigotmc/WatchdogThread.java.patch" + - Rebrand + +diff --git a/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java b/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ede7cb766079031a2c75f3846aa654e28daa5b76 +--- /dev/null ++++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java +@@ -0,0 +1,85 @@ ++package org.purpurmc.purpur.gui.util; ++ ++import org.apache.logging.log4j.Level; ++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 java.util.List; ++ ++@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY) ++@ConverterKeys({"highlightGUIError"}) ++@PerformanceSensitive("allocation") ++public final class HighlightErrorConverter extends LogEventPatternConverter { ++ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red ++ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow ++ ++ private final List formatters; ++ ++ private HighlightErrorConverter(List formatters) { ++ super("highlightGUIError", null); ++ this.formatters = formatters; ++ } ++ ++ @Override ++ public void format(LogEvent event, StringBuilder toAppendTo) { ++ Level level = event.getLevel(); ++ if (level.isMoreSpecificThan(Level.ERROR)) { ++ format(ERROR, event, toAppendTo); ++ return; ++ } else if (level.isMoreSpecificThan(Level.WARN)) { ++ format(WARN, event, toAppendTo); ++ return; ++ } ++ for (PatternFormatter formatter : formatters) { ++ formatter.format(event, toAppendTo); ++ } ++ } ++ ++ private void format(String style, LogEvent event, StringBuilder toAppendTo) { ++ int start = toAppendTo.length(); ++ toAppendTo.append(style); ++ int end = toAppendTo.length(); ++ ++ for (PatternFormatter formatter : formatters) { ++ formatter.format(event, toAppendTo); ++ } ++ ++ if (toAppendTo.length() == end) { ++ toAppendTo.setLength(start); ++ } ++ } ++ ++ @Override ++ public boolean handlesThrowable() { ++ for (final PatternFormatter formatter : formatters) { ++ if (formatter.handlesThrowable()) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public static HighlightErrorConverter newInstance(Configuration config, String[] options) { ++ if (options.length != 1) { ++ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length); ++ return null; ++ } ++ ++ if (options[0] == null) { ++ LOGGER.error("No pattern supplied on highlightGUIError"); ++ return null; ++ } ++ ++ PatternParser parser = PatternLayout.createPatternParser(config); ++ List formatters = parser.parse(options[0]); ++ return new HighlightErrorConverter(formatters); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +index 6bdc683b5ade408ee27f1d6636b4d60c8c89cb7c..bc6d3898d8784e50a0e2264bbb5bde63add61f4e 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -136,6 +136,10 @@ public class MobGoalHelper { + static { + // TODO these kinda should be checked on each release, in case obfuscation changes + deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); ++ // Purpur start - Add option to disable zombie aggressiveness towards villagers ++ deobfuscationMap.put("zombie_1", "zombie_attack_villager"); ++ deobfuscationMap.put("drowned_1", "drowned_attack_villager"); ++ // Purpur end - Add option to disable zombie aggressiveness towards villagers + + ignored.add("goal_selector_1"); + ignored.add("goal_selector_2"); +diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +index f0fce4113fb07c64adbec029d177c236cbdcbae8..da9ba821362605ca86df0fc6cb4f3cabfc4d5809 100644 +--- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +@@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand { + this.setAliases(Arrays.asList("pl")); + } + +- private static List formatProviders(TreeMap> plugins) { ++ private static List formatProviders(TreeMap> plugins, @NotNull CommandSender sender) { // Purpur - Improve output of plugins command + List components = new ArrayList<>(plugins.size()); + for (PluginProvider entry : plugins.values()) { +- components.add(formatProvider(entry)); ++ components.add(formatProvider(entry, sender)); // Purpur - Improve output of plugins command + } + + boolean isFirst = true; +@@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand { + return formattedSublists; + } + +- private static Component formatProvider(PluginProvider provider) { ++ private static Component formatProvider(PluginProvider provider, @NotNull CommandSender sender) { // Purpur - Improve output of plugins command + TextComponent.Builder builder = Component.text(); + if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) { + builder.append(LEGACY_PLUGIN_STAR); +@@ -117,13 +117,65 @@ public class PaperPluginsCommand extends BukkitCommand { + + String name = provider.getMeta().getName(); + Component pluginName = Component.text(name, fromStatus(provider)) +- .clickEvent(ClickEvent.runCommand("/version " + name)); ++ // Purpur start - Improve output of plugins command ++ .clickEvent(ClickEvent.suggestCommand("/version " + name)); ++ ++ if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) { ++ // Event components ++ String description = provider.getMeta().getDescription(); ++ TextComponent.Builder hover = Component.text(); ++ hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN)); ++ ++ if (description != null) { ++ hover.append(Component.newline()) ++ .append(Component.text("Description: ", NamedTextColor.WHITE)) ++ .append(Component.text(description, NamedTextColor.GREEN)); ++ } ++ ++ if (provider.getMeta().getWebsite() != null) { ++ hover.append(Component.newline()) ++ .append(Component.text("Website: ", NamedTextColor.WHITE)) ++ .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN)); ++ } ++ ++ if (!provider.getMeta().getAuthors().isEmpty()) { ++ hover.append(Component.newline()); ++ if (provider.getMeta().getAuthors().size() == 1) { ++ hover.append(Component.text("Author: ")); ++ } else { ++ hover.append(Component.text("Authors: ")); ++ } ++ ++ hover.append(getAuthors(provider.getMeta())); ++ } ++ ++ pluginName.hoverEvent(hover.build()); ++ } ++ // Purpur end - Improve output of plugins command + + builder.append(pluginName); + + return builder.build(); + } + ++ // Purpur start - Improve output of plugins command ++ @NotNull ++ private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) { ++ TextComponent.Builder builder = Component.text(); ++ List authors = pluginMeta.getAuthors(); ++ ++ for (int i = 0; i < authors.size(); i++) { ++ if (i > 0) { ++ builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE)); ++ } ++ ++ builder.append(Component.text(authors.get(i), NamedTextColor.GREEN)); ++ } ++ ++ return builder.build(); ++ } ++ // Purpur end - Improve output of plugins command ++ + private static Component asPlainComponents(String strings) { + net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); + for (String string : strings.split("\n")) { +@@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand { + } + } + +- Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); ++ //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); // Purpur - Improve output of plugins command + //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs + +- sender.sendMessage(infoMessage); ++ //sender.sendMessage(infoMessage); // Purpur - Improve output of plugins command + +- if (!paperPlugins.isEmpty()) { +- sender.sendMessage(PAPER_HEADER); +- } ++ //if (!paperPlugins.isEmpty()) { // Purpur - Improve output of plugins command ++ sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur - Improve output of plugins command ++ //} // Purpur - Improve output of plugins command + +- for (Component component : formatProviders(paperPlugins)) { ++ for (Component component : formatProviders(paperPlugins, sender)) { // Purpur - Improve output of plugins command + sender.sendMessage(component); + } + +- if (!spigotPlugins.isEmpty()) { +- sender.sendMessage(BUKKIT_HEADER); +- } ++ //if (!spigotPlugins.isEmpty()) { // Purpur - Improve output of plugins command ++ sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur - Improve output of plugins command ++ //} // Purpur - Improve output of plugins command + +- for (Component component : formatProviders(spigotPlugins)) { ++ for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur - Improve output of plugins command + sender.sendMessage(component); + } + +diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +index 352d62385e56d5805510596ec9424e5d14336861..b4d4ad2dc7d719d72c0786791f803fbcf0982d1f 100644 +--- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java ++++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +@@ -267,6 +267,7 @@ public class PaperConfigurations extends Configurations 0 || SysoutCatcher.NAG_TIMEOUT > 0) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 94ca0407303c4493ab4928b12ec6ecc75aaca549..f2d87c12dd19210ce7e2147fada5c10191008632 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -363,14 +363,26 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + + @Override + public Location getLocation() { ++ // Purpur start - OfflinePlayer API ++ if (this.isOnline()) { ++ return this.getPlayer().getLocation(); ++ } ++ // Purpur end - OfflinePlayer API ++ + CompoundTag data = this.getData(); + if (data == null) { + return null; + } + +- if (data.contains("Pos") && data.contains("Rotation")) { +- ListTag position = (ListTag) data.get("Pos"); +- ListTag rotation = (ListTag) data.get("Rotation"); ++ // Purpur start - OfflinePlayer API ++ //if (data.contains("Pos") && data.contains("Rotation")) { ++ ListTag position = data.getList("Pos", net.minecraft.nbt.Tag.TAG_DOUBLE); ++ ListTag rotation = data.getList("Rotation", net.minecraft.nbt.Tag.TAG_FLOAT); ++ ++ if (position.isEmpty() && rotation.isEmpty()) { ++ return null; ++ } ++ // Purpur end - OfflinePlayer API + + UUID uuid = new UUID(data.getLong("WorldUUIDMost"), data.getLong("WorldUUIDLeast")); + +@@ -381,9 +393,9 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + rotation.getFloat(0), + rotation.getFloat(1) + ); +- } ++ //} // Purpur - OfflinePlayer API + +- return null; ++ //return null; // Purpur - OfflinePlayer API + } + + @Override +@@ -626,4 +638,191 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + manager.save(); + } + } ++ ++ // Purpur start - OfflinePlayer API ++ @Override ++ public boolean getAllowFlight() { ++ if (this.isOnline()) { ++ return this.getPlayer().getAllowFlight(); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return false; ++ if (!data.contains("abilities")) return false; ++ CompoundTag abilities = data.getCompound("abilities"); ++ return abilities.getByte("mayfly") == (byte) 1; ++ } ++ } ++ ++ @Override ++ public void setAllowFlight(boolean flight) { ++ if (this.isOnline()) { ++ this.getPlayer().setAllowFlight(flight); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return; ++ if (!data.contains("abilities")) return; ++ CompoundTag abilities = data.getCompound("abilities"); ++ abilities.putByte("mayfly", (byte) (flight ? 1 : 0)); ++ data.put("abilities", abilities); ++ save(data); ++ } ++ } ++ ++ @Override ++ public boolean isFlying() { ++ if (this.isOnline()) { ++ return this.isFlying(); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return false; ++ if (!data.contains("abilities")) return false; ++ CompoundTag abilities = data.getCompound("abilities"); ++ return abilities.getByte("flying") == (byte) 1; ++ } ++ } ++ ++ @Override ++ public void setFlying(boolean value) { ++ if (this.isOnline()) { ++ this.getPlayer().setFlying(value); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return; ++ if (!data.contains("abilities")) return; ++ CompoundTag abilities = data.getCompound("abilities"); ++ abilities.putByte("mayfly", (byte) (value ? 1 : 0)); ++ data.put("abilities", abilities); ++ save(data); ++ } ++ } ++ ++ @Override ++ public void setFlySpeed(float value) throws IllegalArgumentException { ++ if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1"); ++ if (this.isOnline()) { ++ this.getPlayer().setFlySpeed(value); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return; ++ if (!data.contains("abilities")) return; ++ CompoundTag abilities = data.getCompound("abilities"); ++ abilities.putFloat("flySpeed", value); ++ data.put("abilities", abilities); ++ save(data); ++ } ++ } ++ ++ @Override ++ public float getFlySpeed() { ++ if (this.isOnline()) { ++ return this.getPlayer().getFlySpeed(); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return 0; ++ if (!data.contains("abilities")) return 0; ++ CompoundTag abilities = data.getCompound("abilities"); ++ return abilities.getFloat("flySpeed"); ++ } ++ } ++ ++ @Override ++ public void setWalkSpeed(float value) throws IllegalArgumentException { ++ if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1"); ++ if (this.isOnline()) { ++ this.getPlayer().setWalkSpeed(value); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return; ++ if (!data.contains("abilities")) return; ++ CompoundTag abilities = data.getCompound("abilities"); ++ abilities.putFloat("walkSpeed", value); ++ data.put("abilities", abilities); ++ save(data); ++ } ++ } ++ ++ @Override ++ public float getWalkSpeed() { ++ if (this.isOnline()) { ++ return this.getPlayer().getWalkSpeed(); ++ } else { ++ CompoundTag data = this.getData(); ++ if (data == null) return 0; ++ if (!data.contains("abilities")) return 0; ++ CompoundTag abilities = data.getCompound("abilities"); ++ return abilities.getFloat("walkSpeed"); ++ } ++ } ++ ++ @Override ++ public boolean teleportOffline(Location destination) { ++ if (this.isOnline()) { ++ return this.getPlayer().teleport(destination); ++ } else { ++ return setLocation(destination); ++ } ++ } ++ ++ @Override ++ public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){ ++ if (this.isOnline()) { ++ return this.getPlayer().teleport(destination, cause); ++ } else { ++ return setLocation(destination); ++ } ++ } ++ ++ @Override ++ public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination) { ++ if (this.isOnline()) { ++ return this.getPlayer().teleportAsync(destination); ++ } else { ++ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); ++ } ++ } ++ ++ @Override ++ public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { ++ if (this.isOnline()) { ++ return this.getPlayer().teleportAsync(destination, cause); ++ } else { ++ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); ++ } ++ } ++ ++ private boolean setLocation(Location location) { ++ CompoundTag data = this.getData(); ++ if (data == null) return false; ++ data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits()); ++ data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits()); ++ net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag(); ++ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX())); ++ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY())); ++ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ())); ++ data.put("Pos", position); ++ net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag(); ++ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw())); ++ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch())); ++ data.put("Rotation", rotation); ++ save(data); ++ return true; ++ } ++ ++ /** ++ * Safely replaces player's .dat file with provided CompoundTag ++ * @param compoundTag ++ */ ++ private void save(CompoundTag compoundTag) { ++ File playerDir = server.console.playerDataStorage.getPlayerDir(); ++ try { ++ File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); ++ net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.toPath()); ++ File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); ++ File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); ++ net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); ++ } catch (java.io.IOException e) { ++ e.printStackTrace(); ++ } ++ } ++ // Purpur end - OfflinePlayer API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index aaa68b6756397df3eec5c0b08e9c19fa8d2e58b6..de2c10a18869d3fcfc1ae1329489155d7bdc4dd8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -428,6 +428,20 @@ public final class CraftServer implements Server { + this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); + this.pluginManager.paperPluginManager = this.paperPluginManager; + // Paper end ++ // Purpur start - Language API ++ org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() { ++ private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance(); ++ @Override ++ public boolean has(@org.jetbrains.annotations.NotNull String key) { ++ return language.has(key); ++ } ++ ++ @Override ++ public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) { ++ return language.getOrDefault(key); ++ } ++ }); ++ // Purpur end - Language API + + CraftRegistry.setMinecraftRegistry(console.registryAccess()); + +@@ -1089,6 +1103,7 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); + this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration ++ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - Purpur config files + for (ServerLevel world : this.console.getAllLevels()) { + // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty + world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) +@@ -1104,6 +1119,7 @@ public final class CraftServer implements Server { + } + } + world.spigotConfig.init(); // Spigot ++ world.purpurConfig.init(); // Purpur - Purpur config files + } + + Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper +@@ -1121,6 +1137,7 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.registerCommands(); // Spigot + io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper + this.spark.registerCommandBeforePlugins(this); // Paper - spark ++ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files + this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); + this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); + +@@ -1652,6 +1669,60 @@ public final class CraftServer implements Server { + return true; + } + ++ // Purpur start - Added the ability to add combustible items ++ @Override ++ public void addFuel(org.bukkit.Material material, int burnTime) { ++ Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0"); ++ ++ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); ++ MinecraftServer.getServer().fuelValues().values.put(itemStack.getItem(), burnTime); ++ } ++ ++ @Override ++ public void removeFuel(org.bukkit.Material material) { ++ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); ++ MinecraftServer.getServer().fuelValues().values.keySet().removeIf(itemStack::is); ++ } ++ // Purpur end - Added the ability to add combustible items ++ ++ // Purpur start - Debug Marker API ++ @Override ++ public void sendBlockHighlight(Location location, int duration) { ++ sendBlockHighlight(location, duration, "", 0x6400FF00); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, int argb) { ++ sendBlockHighlight(location, duration, "", argb); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text) { ++ sendBlockHighlight(location, duration, text, 0x6400FF00); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text, int argb) { ++ this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb)); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { ++ sendBlockHighlight(location, duration, "", color, transparency); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { ++ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); ++ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); ++ } ++ ++ @Override ++ public void clearBlockHighlights() { ++ this.worlds.forEach((name, world) -> clearBlockHighlights()); ++ } ++ // Purpur end - Debug Marker API ++ + @Override + public List getRecipesFor(ItemStack result) { + Preconditions.checkArgument(result != null, "ItemStack cannot be null"); +@@ -3069,6 +3140,18 @@ public final class CraftServer implements Server { + } + // Gale end - Gale configuration - API + ++ // Purpur start - Purpur config files ++ @Override ++ public YamlConfiguration getPurpurConfig() { ++ return org.purpurmc.purpur.PurpurConfig.config; ++ } ++ ++ @Override ++ public java.util.Properties getServerProperties() { ++ return getProperties().properties; ++ } ++ // Purpur end - Purpur config files ++ + @Override + public void restart() { + org.spigotmc.RestartCommand.restart(); +@@ -3362,4 +3445,18 @@ public final class CraftServer implements Server { + return MinecraftServer.lastTickOversleepTime; + } + // Gale end - YAPFA - last tick time - API ++ ++ // Purpur start - Bring back server name ++ @Override ++ public String getServerName() { ++ return this.getProperties().serverName; ++ } ++ // Purpur end - Bring back server name ++ ++ // Purpur start - Lagging threshold ++ @Override ++ public boolean isLagging() { ++ return getServer().lagging; ++ } ++ // Purpur end - Lagging threshold + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 91156797d0ffed5227851b398d8b896aef71d614..ca5897526ecb0323ac18842048fcc2a28cc5faff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2349,6 +2349,50 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight()); + } + ++ // Purpur start - Add local difficulty api ++ public float getLocalDifficultyAt(Location location) { ++ return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty(); ++ } ++ // Purpur end - Add local difficulty api ++ ++ // Purpur start - Debug Marker API ++ @Override ++ public void sendBlockHighlight(Location location, int duration) { ++ sendBlockHighlight(location, duration, "", 0x6400FF00); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, int argb) { ++ sendBlockHighlight(location, duration, "", argb); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text) { ++ sendBlockHighlight(location, duration, text, 0x6400FF00); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text, int argb) { ++ net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), io.papermc.paper.util.MCUtil.toBlockPosition(location), text, argb, duration); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { ++ sendBlockHighlight(location, duration, "", color, transparency); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { ++ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); ++ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); ++ } ++ ++ @Override ++ public void clearBlockHighlights() { ++ net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle()); ++ } ++ // Purpur end - Debug Marker API ++ + @Override + public Collection getStructures(int x, int z) { + return this.getStructures(x, z, struct -> true); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 1df9bed6d3dc1e28898af8d5ad6a854dd5ccab1b..251922d1ee5ef93ef0383f4360e9a7ea17dfb195 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -176,6 +176,13 @@ public class Main { + .describedAs("Jar file"); + // Paper end + ++ // Purpur start - Purpur config files ++ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File("purpur.yml")) ++ .describedAs("Yml file"); ++ // Purpur end - Purpur config files + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() +@@ -259,7 +266,7 @@ public class Main { + System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper + } + +- if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { ++ if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur - Disable outdated build check + Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper + + Calendar deadline = Calendar.getInstance(); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +index 1a2a05160ba51d9c75f1ae6ae61d944d81428722..a86b026f2f420637d125cf697bcd07bf314c98aa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +@@ -16,8 +16,15 @@ import org.bukkit.entity.Bee; + + public class CraftBeehive extends CraftBlockEntityState implements Beehive { + ++ private final List> storage = new ArrayList<>(); // Purpur - Stored Bee API ++ + public CraftBeehive(World world, BeehiveBlockEntity tileEntity) { + super(world, tileEntity); ++ // Purpur start - load bees to be able to modify them individually - Stored Bee API ++ for(BeehiveBlockEntity.BeeData data : tileEntity.getStored()) { ++ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this)); ++ } ++ // Purpur end - Stored Bee API + } + + protected CraftBeehive(CraftBeehive state, Location location) { +@@ -76,14 +83,54 @@ public class CraftBeehive extends CraftBlockEntityState impl + } + } + ++ storage.clear(); // Purpur - Stored Bee API + return bees; + } + ++ // Purpur start - Stored Bee API ++ @Override ++ public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity entity) { ++ ensureNoWorldGeneration(); ++ ++ if(!getEntities().contains(entity)) { ++ return null; ++ } ++ ++ if(isPlaced()) { ++ BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getTileEntityFromWorld()); ++ BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle(); ++ ++ List list = beehive.releaseBee(getHandle(), data, BeeReleaseStatus.BEE_RELEASED, true); ++ ++ if (list.size() == 1) { ++ storage.remove(entity); ++ ++ return (Bee) list.get(0).getBukkitEntity(); ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public List> getEntities() { ++ return new ArrayList<>(storage); ++ } ++ // Purpur end - Stored Bee API ++ + @Override + public void addEntity(Bee entity) { + Preconditions.checkArgument(entity != null, "Entity must not be null"); + ++ int length = this.getSnapshot().getStored().size(); // Purpur - Stored Bee API + this.getSnapshot().addOccupant(((CraftBee) entity).getHandle()); ++ ++ // Purpur start - check if new bee was added, and if yes, add to stored bees - Stored Bee API ++ List storedBeeData = this.getSnapshot().getStored(); ++ if(length < storedBeeData.size()) { ++ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(storedBeeData.getLast(), this)); ++ } ++ // Purpur end - Stored Bee API + } + + @Override +@@ -100,6 +147,7 @@ public class CraftBeehive extends CraftBlockEntityState impl + @Override + public void clearEntities() { + getSnapshot().clearBees(); ++ storage.clear(); // Purpur - Stored Bee API + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java +index c1759aeb3e6ad0e4eb66cba3da1b120dd1dce812..a663962e5181e89286caa18f537c1f5758b41623 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java +@@ -73,7 +73,7 @@ public class CraftConduit extends CraftBlockEntityState impl + public int getRange() { + this.ensureNoWorldGeneration(); + ConduitBlockEntity conduit = (ConduitBlockEntity) this.getTileEntityFromWorld(); +- return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks) : 0; ++ return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks, this.world.getHandle()) : 0; // Purpur - Conduit behavior configuration + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +index 4e56018b64d11f76c8da43fd8f85c6de72204e36..36cec3ed39807e85013e4e3b98c979d7af37ce58 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +@@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co + + @Override + public void sendMessage(String message) { +- this.sendRawMessage(message); ++ // Purpur start - Rebrand ++ String[] parts = message.split("\n"); ++ for (String part : parts) { ++ this.sendRawMessage(part); ++ } ++ // Purpur end - Rebrand + } + + @Override +@@ -91,7 +96,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co + // Paper start + @Override + public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { +- this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); ++ this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur - Rebrand + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +index d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..3604d92c122b5c8be823098ce7b91e57e976589c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +@@ -21,12 +21,12 @@ public class CraftEndermite extends CraftMonster implements Endermite { + + @Override + public boolean isPlayerSpawned() { +- return false; ++ return getHandle().isPlayerSpawned(); // Purpur - Add back player spawned endermite API + } + + @Override + public void setPlayerSpawned(boolean playerSpawned) { +- // Nop ++ getHandle().setPlayerSpawned(playerSpawned); // Purpur - Add back player spawned endermite API + } + // Paper start + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 6d81a19741868983c54aff6c2c4c0e2bf690ba0d..dca2761fe4765c6e95b5db0d0cb5c818eb8697b4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -90,6 +90,25 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); + } + ++ // Purpur start - Fire Immunity API ++ @Override ++ public boolean isImmuneToFire() { ++ return getHandle().fireImmune(); ++ } ++ ++ @Override ++ public void setImmuneToFire(Boolean fireImmune) { ++ getHandle().immuneToFire = fireImmune; ++ } ++ // Purpur end - Fire Immunity API ++ ++ // Purpur start - API for any mob to burn daylight ++ @Override ++ public boolean isInDaylight() { ++ return getHandle().isSunBurnTick(); ++ } ++ // Purpur end - API for any mob to burn daylight ++ + public static CraftEntity getEntity(CraftServer server, T entity) { + Preconditions.checkArgument(entity != null, "Unknown entity"); + +@@ -249,6 +268,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + boolean retainPassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); + // Don't allow teleporting between worlds while keeping passengers + if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - Add EntityTeleportHinderedEvent + return false; + } + +@@ -1333,4 +1353,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + } + // Paper end - broadcast hurt animation ++ ++ // Purpur start - Ridables ++ @Override ++ public org.bukkit.entity.Player getRider() { ++ net.minecraft.world.entity.player.Player rider = getHandle().getRider(); ++ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null; ++ } ++ ++ @Override ++ public boolean hasRider() { ++ return getHandle().getRider() != null; ++ } ++ ++ @Override ++ public boolean isRidable() { ++ return getHandle().isRidable(); ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return !getHandle().dismountsUnderwater(); ++ } ++ // Purpur end - Ridables + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index a396157548a5b3c3e86206c35789bb40346c701c..469b99bede578b03a1b711b4404a88bf85d7e7ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -281,6 +281,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + @Override + public void recalculatePermissions() { + this.perm.recalculatePermissions(); ++ getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur - Add portal permission bypass + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java +index 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..464a3713845548473a357ea66c6147b10ff2cb16 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java +@@ -27,4 +27,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem { + public void setPlayerCreated(boolean playerCreated) { + this.getHandle().setPlayerCreated(playerCreated); + } ++ ++ // Purpur start - Summoner API ++ @Override ++ @org.jetbrains.annotations.Nullable ++ public java.util.UUID getSummoner() { ++ return getHandle().getSummoner(); ++ } ++ ++ @Override ++ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { ++ getHandle().setSummoner(summoner); ++ } ++ // Purpur end - Summoner API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 7a3d982b133f8cdaeb936cf40f92565f0f7f6dd0..8b0faa08411ee1f336641b161acd3412c886dc2b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -151,4 +151,53 @@ public class CraftItem extends CraftEntity implements Item { + public String toString() { + return "CraftItem"; + } ++ ++ // Purpur start - Item entity immunities ++ @Override ++ public void setImmuneToCactus(boolean immuneToCactus) { ++ this.getHandle().immuneToCactus = immuneToCactus; ++ } ++ ++ @Override ++ public boolean isImmuneToCactus() { ++ return this.getHandle().immuneToCactus; ++ } ++ ++ @Override ++ public void setImmuneToExplosion(boolean immuneToExplosion) { ++ this.getHandle().immuneToExplosion = immuneToExplosion; ++ } ++ ++ @Override ++ public boolean isImmuneToExplosion() { ++ return this.getHandle().immuneToExplosion; ++ } ++ ++ // Purpur start - Fire Immunity API ++ @Override ++ public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) { ++ this.getHandle().immuneToFire = (immuneToFire != null && immuneToFire); ++ } ++ // Purpur end - Fire Immunity API ++ ++ @Override ++ public void setImmuneToFire(boolean immuneToFire) { ++ this.setImmuneToFire((Boolean) immuneToFire); // Purpur - Fire Immunity API ++ } ++ ++ @Override ++ public boolean isImmuneToFire() { ++ return this.getHandle().immuneToFire; ++ } ++ ++ @Override ++ public void setImmuneToLightning(boolean immuneToLightning) { ++ this.getHandle().immuneToLightning = immuneToLightning; ++ } ++ ++ @Override ++ public boolean isImmuneToLightning() { ++ return this.getHandle().immuneToLightning; ++ } ++ // Purpur end - Item entity immunities + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 4f98d138a275a6c34528b7a5148ef265bc38d6b5..3a9d9b7526b2bf0fbd4e0d7886b3d849a6dcfee9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -523,7 +523,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + 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 ++ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : getHandle().level().purpurConfig.mobLastHurtByPlayerTime; // 100 value taken from EntityLiving#damageEntity // Purpur - Config for mob last hurt by player time + } + // Paper end + +@@ -1211,4 +1211,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); + } + // Paper end - Expose canUseSlot ++ ++ // Purpur start - API for any mob to burn daylight ++ @Override ++ public boolean shouldBurnInDay() { ++ return this.getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(final boolean shouldBurnInDay) { ++ this.getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Purpur end - API for any mob to burn daylight + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index 351f42842b780d053cd2e5bad9ae299449141b10..054d2c2b93c43faeeaf56f482eb7b9431a6190df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -90,4 +90,16 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys + return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); + } + // Paper end ++ ++ // Purpur start - Llama API ++ @Override ++ public boolean shouldJoinCaravan() { ++ return getHandle().shouldJoinCaravan; ++ } ++ ++ @Override ++ public void setShouldJoinCaravan(boolean shouldJoinCaravan) { ++ getHandle().shouldJoinCaravan = shouldJoinCaravan; ++ } ++ // Purpur end - Llama API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 98fc89cc7a715d35b62e13f8ecbe56c05605ca64..9fbbdc9664353fd2be8eae112e5cfe8880d51d08 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -585,10 +585,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setPlayerListName(String name) { ++ // Purpur start - AFK API ++ setPlayerListName(name, false); ++ } ++ public void setPlayerListName(String name, boolean useMM) { ++ // Purpur end - AFK API + if (name == null) { + name = this.getName(); + } +- this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name); ++ this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur - AFK API + if (this.getHandle().connection == null) return; // Paper - Updates are possible before the player has fully joined + for (ServerPlayer player : (List) this.server.getHandle().players) { + if (player.getBukkitEntity().canSee(this)) { +@@ -1426,6 +1431,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper start - Teleport passenger API + // Don't allow teleporting between worlds while keeping passengers + if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - Add EntityTeleportHinderedEvent + return false; + } + +@@ -1447,6 +1453,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API ++ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - Add EntityTeleportHinderedEvent + return false; + } + +@@ -2745,6 +2752,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this.getHandle().getAbilities().walkingSpeed * 2f; + } + ++ // Purpur start - OfflinePlayer API ++ @Override ++ public boolean teleportOffline(@NotNull Location destination) { ++ return this.teleport(destination); ++ } ++ ++ @Override ++ public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) { ++ return this.teleport(destination, cause); ++ } ++ ++ @Override ++ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination) { ++ return this.teleportAsync(destination); ++ } ++ ++ @Override ++ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination, PlayerTeleportEvent.TeleportCause cause) { ++ return this.teleportAsync(destination, cause); ++ } ++ // Purpur end - OfflinePlayer API ++ + private void validateSpeed(float value) { + Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value); + } +@@ -3541,4 +3570,74 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData())); + } + // Paper end - entity effect API ++ // Purpur start - Purpur client support ++ @Override ++ public boolean usesPurpurClient() { ++ return getHandle().purpurClient; ++ } ++ // Purpur end - Purpur client support ++ // Purpur start - AFK API ++ @Override ++ public boolean isAfk() { ++ return getHandle().isAfk(); ++ } ++ ++ @Override ++ public void setAfk(boolean setAfk) { ++ getHandle().setAfk(setAfk); ++ } ++ ++ @Override ++ public void resetIdleTimer() { ++ getHandle().resetLastActionTime(); ++ } ++ // Purpur end - AFK API ++ ++ // Purpur start - Debug Marker API ++ @Override ++ public void sendBlockHighlight(Location location, int duration) { ++ sendBlockHighlight(location, duration, "", 0x6400FF00); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, int argb) { ++ sendBlockHighlight(location, duration, "", argb); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text) { ++ sendBlockHighlight(location, duration, text, 0x6400FF00); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text, int argb) { ++ if (this.getHandle().connection == null) return; ++ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload(io.papermc.paper.util.MCUtil.toBlockPosition(location), argb, text, duration))); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { ++ sendBlockHighlight(location, duration, "", color, transparency); ++ } ++ ++ @Override ++ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { ++ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); ++ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); ++ } ++ ++ @Override ++ public void clearBlockHighlights() { ++ if (this.getHandle().connection == null) return; ++ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload())); ++ } ++ // Purpur end - Debug Marker API ++ ++ // Purpur start - Death screen API ++ @Override ++ public void sendDeathScreen(net.kyori.adventure.text.Component message) { ++ if (this.getHandle().connection == null) return; ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); ++ } ++ // Purpur end - Death screen API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +index 4ce2373ff71c3c1b8951646e057587a3ab09e145..997b8e5059569de4ee8e70127c5d6019ce53afe3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +@@ -28,4 +28,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok + public String toString() { + return "CraftSnowman"; + } ++ ++ // Purpur start - Summoner API ++ @Override ++ @org.jetbrains.annotations.Nullable ++ public java.util.UUID getSummoner() { ++ return getHandle().getSummoner(); ++ } ++ ++ @Override ++ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { ++ getHandle().setSummoner(summoner); ++ } ++ // Purpur end - Summoner API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index aaddce10e1d41531939d1e7f3d717b458ec1b7ab..d65a3bee4671e9e21769ba03f5e65c7312b23580 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -373,4 +373,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + getHandle().getGossips().gossips.clear(); + } + // Paper end ++ ++ // Purpur start - Lobotomize stuck villagers ++ @Override ++ public boolean isLobotomized() { ++ return getHandle().isLobotomized(); ++ } ++ // Purpur end - Lobotomize stuck villagers + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e..fe8be71121324f64346174922c7bc7f5d3a9de69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -99,4 +99,17 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + this.getHandle().makeInvulnerable(); + } + // Paper end ++ ++ // Purpur start - Summoner API ++ @Override ++ @org.jetbrains.annotations.Nullable ++ public java.util.UUID getSummoner() { ++ return getHandle().getSummoner(); ++ } ++ ++ @Override ++ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { ++ getHandle().setSummoner(summoner); ++ } ++ // Purpur end - Summoner API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +index c1b7f1281fbd41e765d2c1881763ca25b20e924d..53c620e717d39ef16f44c9697ac4809ac1fdfa6a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +@@ -145,4 +145,15 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { + return this.getKey().hashCode(); + } + } ++ // Purpur start - Configurable chance for wolves to spawn rabid ++ @Override ++ public boolean isRabid() { ++ return getHandle().isRabid(); ++ } ++ ++ @Override ++ public void setRabid(boolean isRabid) { ++ getHandle().setRabid(isRabid); ++ } ++ // Purpur end - Configurable chance for wolves to spawn rabid + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index d7a52220e9525502163f5ee6afbadf2baaae6190..1b7fdbecf9c28732d5196236980e87fa737a0769 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -602,6 +602,15 @@ public class CraftEventFactory { + // Paper end + craftServer.getPluginManager().callEvent(event); + ++ // Purpur start - Ridables ++ if (who != null) { ++ switch (action) { ++ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND); ++ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND); ++ } ++ } ++ // Purpur end - Ridables ++ + return event; + } + +@@ -1131,7 +1140,7 @@ public class CraftEventFactory { + return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.LAVA, bukkitDamageSource, modifiers, modifierFunctions, cancelled); + } else if (source.getDirectBlock() != null) { + DamageCause cause; +- if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) { ++ if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL) || source.isStonecutter()) { // Purpur - Stonecutter damage + cause = DamageCause.CONTACT; + } else if (source.is(DamageTypes.HOT_FLOOR)) { + cause = DamageCause.HOT_FLOOR; +@@ -1191,6 +1200,7 @@ public class CraftEventFactory { + EntityDamageEvent event; + if (damager != null) { + event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); ++ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - Ridables + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +index 1ce328bed5cf3d087a3f7dc9236153381d758493..364afc994443f6c64af4f9ebbe210da63e18681c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +@@ -145,8 +145,19 @@ public class CraftContainer extends AbstractContainerMenu { + case PLAYER: + case CHEST: + case ENDER_CHEST: ++ // Purpur start - Barrels and enderchests 6 rows ++ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? net.minecraft.world.inventory.MenuType.GENERIC_9x6 : net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); ++ break; + case BARREL: +- this.delegate = new ChestMenu(net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); ++ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { ++ case 6 -> net.minecraft.world.inventory.MenuType.GENERIC_9x6; ++ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5; ++ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4; ++ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2; ++ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1; ++ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x3; ++ }, windowId, bottom, top, top.getContainerSize() / 9); ++ // Purpur end - Barrels and enderchests 6 rows + break; + case DISPENSER: + case DROPPER: +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index c6159c70f7a37b9bffe268b91905ce848d1d2927..8b4f8a475faafe3b8a479160888145c4aa603a27 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -84,7 +84,7 @@ public class CraftInventory implements Inventory { + + @Override + public void setContents(ItemStack[] items) { +- Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); ++ // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur - Barrels and enderchests 6 rows + + for (int i = 0; i < this.getSize(); i++) { + if (i >= items.length) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +index 792cb6adf0c7a6335cc5985fce8bed2e0f1149af..5734c5caffda79383ae30df20c3defb51b87f39e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +@@ -19,6 +19,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn + private int repairCost; + private int repairCostAmount; + private int maximumRepairCost; ++ // Purpur start - Anvil API ++ private boolean bypassCost; ++ private boolean canDoUnsafeEnchants; ++ // Purpur end - Anvil API + + public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory) { + super(inventory, resultInventory); +@@ -27,6 +31,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn + this.repairCost = CraftInventoryAnvil.DEFAULT_REPAIR_COST; + this.repairCostAmount = CraftInventoryAnvil.DEFAULT_REPAIR_COST_AMOUNT; + this.maximumRepairCost = CraftInventoryAnvil.DEFAULT_MAXIMUM_REPAIR_COST; ++ // Purpur start - Anvil API ++ this.bypassCost = false; ++ this.canDoUnsafeEnchants = false; ++ // Purpur end - Anvil API + } + + @Override +@@ -113,4 +121,30 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn + consumer.accept(cav); + } + } ++ ++ // Purpur start - Anvil API ++ @Override ++ public boolean canBypassCost() { ++ this.syncWithArbitraryViewValue((cav) -> this.bypassCost = cav.canBypassCost()); ++ return this.bypassCost; ++ } ++ ++ @Override ++ public void setBypassCost(boolean bypassCost) { ++ this.bypassCost = bypassCost; ++ this.syncViews((cav) -> cav.setBypassCost(bypassCost)); ++ } ++ ++ @Override ++ public boolean canDoUnsafeEnchants() { ++ this.syncWithArbitraryViewValue((cav) -> this.canDoUnsafeEnchants = cav.canDoUnsafeEnchants()); ++ return this.canDoUnsafeEnchants; ++ } ++ ++ @Override ++ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { ++ this.canDoUnsafeEnchants = canDoUnsafeEnchants; ++ this.syncViews((cav) -> cav.setDoUnsafeEnchants(canDoUnsafeEnchants)); ++ } ++ // Purpur end - Anvil API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 3799973696eabbdc992bee4ff24175fc28ec8d7c..ae68b1d72be45503acc8c7a52b20d95d7b651f06 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -666,4 +666,285 @@ public final class CraftItemStack extends ItemStack { + } + + // Paper end - data component API ++ ++ // Purpur start - ItemStack convenience methods ++ @Override ++ public String getDisplayName() { ++ return getItemMeta().getDisplayName(); ++ } ++ ++ @Override ++ public void setDisplayName(String name) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setDisplayName(name); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public boolean hasDisplayName() { ++ return hasItemMeta() && getItemMeta().hasDisplayName(); ++ } ++ ++ @Override ++ public String getLocalizedName() { ++ return getItemMeta().getLocalizedName(); ++ } ++ ++ @Override ++ public void setLocalizedName(String name) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setLocalizedName(name); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public boolean hasLocalizedName() { ++ return hasItemMeta() && getItemMeta().hasLocalizedName(); ++ } ++ ++ @Override ++ public boolean hasLore() { ++ return hasItemMeta() && getItemMeta().hasLore(); ++ } ++ ++ @Override ++ public boolean hasEnchant(Enchantment ench) { ++ return hasItemMeta() && getItemMeta().hasEnchant(ench); ++ } ++ ++ @Override ++ public int getEnchantLevel(Enchantment ench) { ++ return getItemMeta().getEnchantLevel(ench); ++ } ++ ++ @Override ++ public Map getEnchants() { ++ return getItemMeta().getEnchants(); ++ } ++ ++ @Override ++ public boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ @Override ++ public boolean removeEnchant(Enchantment ench) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeEnchant(ench); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ @Override ++ public boolean hasEnchants() { ++ return hasItemMeta() && getItemMeta().hasEnchants(); ++ } ++ ++ @Override ++ public boolean hasConflictingEnchant(Enchantment ench) { ++ return hasItemMeta() && getItemMeta().hasConflictingEnchant(ench); ++ } ++ ++ @Override ++ public void setCustomModelData(Integer data) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setCustomModelData(data); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public int getCustomModelData() { ++ return getItemMeta().getCustomModelData(); ++ } ++ ++ @Override ++ public boolean hasCustomModelData() { ++ return hasItemMeta() && getItemMeta().hasCustomModelData(); ++ } ++ ++ @Override ++ public boolean hasBlockData() { ++ return hasItemMeta() && ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).hasBlockData(); ++ } ++ ++ @Override ++ public org.bukkit.block.data.BlockData getBlockData(Material material) { ++ return ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).getBlockData(material); ++ } ++ ++ @Override ++ public void setBlockData(org.bukkit.block.data.BlockData blockData) { ++ ItemMeta itemMeta = getItemMeta(); ++ ((org.bukkit.inventory.meta.BlockDataMeta) itemMeta).setBlockData(blockData); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public int getRepairCost() { ++ return ((org.bukkit.inventory.meta.Repairable) getItemMeta()).getRepairCost(); ++ } ++ ++ @Override ++ public void setRepairCost(int cost) { ++ ItemMeta itemMeta = getItemMeta(); ++ ((org.bukkit.inventory.meta.Repairable) itemMeta).setRepairCost(cost); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public boolean hasRepairCost() { ++ return hasItemMeta() && ((org.bukkit.inventory.meta.Repairable) getItemMeta()).hasRepairCost(); ++ } ++ ++ @Override ++ public boolean isUnbreakable() { ++ return hasItemMeta() && getItemMeta().isUnbreakable(); ++ } ++ ++ @Override ++ public void setUnbreakable(boolean unbreakable) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setUnbreakable(unbreakable); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public boolean hasAttributeModifiers() { ++ return hasItemMeta() && getItemMeta().hasAttributeModifiers(); ++ } ++ ++ @Override ++ public com.google.common.collect.Multimap getAttributeModifiers() { ++ return getItemMeta().getAttributeModifiers(); ++ } ++ ++ @Override ++ public com.google.common.collect.Multimap getAttributeModifiers(org.bukkit.inventory.EquipmentSlot slot) { ++ return getItemMeta().getAttributeModifiers(slot); ++ } ++ ++ @Override ++ public java.util.Collection getAttributeModifiers(org.bukkit.attribute.Attribute attribute) { ++ return getItemMeta().getAttributeModifiers(attribute); ++ } ++ ++ @Override ++ public boolean addAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.addAttributeModifier(attribute, modifier); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ @Override ++ public void setAttributeModifiers(com.google.common.collect.Multimap attributeModifiers) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setAttributeModifiers(attributeModifiers); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeAttributeModifier(attribute); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ @Override ++ public boolean removeAttributeModifier(org.bukkit.inventory.EquipmentSlot slot) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeAttributeModifier(slot); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ @Override ++ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeAttributeModifier(attribute, modifier); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ @Override ++ public boolean hasDamage() { ++ return hasItemMeta() && ((org.bukkit.inventory.meta.Damageable) getItemMeta()).hasDamage(); ++ } ++ ++ @Override ++ public int getDamage() { ++ return ((org.bukkit.inventory.meta.Damageable) getItemMeta()).getDamage(); ++ } ++ ++ @Override ++ public void setDamage(int damage) { ++ ItemMeta itemMeta = getItemMeta(); ++ ((org.bukkit.inventory.meta.Damageable) itemMeta).setDamage(damage); ++ setItemMeta(itemMeta); ++ } ++ ++ @Override ++ public void repair() { ++ repair(1); ++ } ++ ++ @Override ++ public boolean damage() { ++ return damage(1); ++ } ++ ++ @Override ++ public void repair(int amount) { ++ damage(-amount); ++ } ++ ++ @Override ++ public boolean damage(int amount) { ++ return damage(amount, false); ++ } ++ ++ @Override ++ public boolean damage(int amount, boolean ignoreUnbreaking) { ++ org.bukkit.inventory.meta.Damageable damageable = (org.bukkit.inventory.meta.Damageable) getItemMeta(); ++ if (amount > 0) { ++ int unbreaking = getEnchantLevel(Enchantment.UNBREAKING); ++ int reduce = 0; ++ for (int i = 0; unbreaking > 0 && i < amount; ++i) { ++ if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) { ++ ++reduce; ++ } ++ } ++ amount -= reduce; ++ if (amount <= 0) { ++ return isBroke(damageable.getDamage()); ++ } ++ } ++ int damage = damageable.getDamage() + amount; ++ damageable.setDamage(damage); ++ setItemMeta((ItemMeta) damageable); ++ return isBroke(damage); ++ } ++ ++ private boolean isBroke(int damage) { ++ if (damage > getType().getMaxDurability()) { ++ if (getAmount() > 0) { ++ // ensure it "breaks" ++ setAmount(0); ++ } ++ return true; ++ } ++ return false; ++ } ++ ++ private boolean reduceDamage(java.util.Random random, int unbreaking) { ++ if (getType().isArmor()) { ++ return random.nextFloat() < 0.6F; ++ } ++ return random.nextInt(unbreaking + 1) > 0; ++ } ++ // Purpur end - ItemStack convenience methods + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +index 4864e2016cb1d377425297fd1c52b383632cb59e..953d64d128a53eb9ec2ff55140dde5cb80326044 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +@@ -36,6 +36,7 @@ public interface CraftRecipe extends Recipe { + stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat))); + } else if (bukkit instanceof RecipeChoice.ExactChoice) { + stack = Ingredient.ofStacks(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> CraftItemStack.asNMSCopy(mat)).toList()); ++ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur - Add predicate to recipe's ExactChoice ingredient + // Paper start - support "empty" choices - legacy method that spigot might incorrectly call + // Their impl of Ingredient.of() will error, ingredients need at least one entry. + // Callers running into this exception may have passed an incorrect empty() recipe choice to a non-empty slot or +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java +index f86c95a13dff012de5db3e41ac261e9e8d44d9f3..1db0b790d824e419bb5fb6ab1f3003e120f9763b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java +@@ -75,4 +75,26 @@ public class CraftAnvilView extends CraftInventoryView experience + 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); +diff --git a/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a04d23bd98075cd65a24d4de8d18281d1668480f +--- /dev/null ++++ b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java +@@ -0,0 +1,67 @@ ++package org.purpurmc.purpur.configuration.transformation; ++ ++import io.papermc.paper.configuration.Configurations; ++import io.papermc.paper.configuration.PaperConfigurations; ++import io.papermc.paper.configuration.type.number.DoubleOr; ++import java.util.OptionalDouble; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.purpurmc.purpur.PurpurConfig; ++import org.spongepowered.configurate.ConfigurateException; ++import org.spongepowered.configurate.ConfigurationNode; ++import org.spongepowered.configurate.NodePath; ++import org.spongepowered.configurate.transformation.ConfigurationTransformation; ++import org.spongepowered.configurate.transformation.TransformAction; ++ ++import static org.spongepowered.configurate.NodePath.path; ++ ++public class VoidDamageHeightMigration implements TransformAction { ++ ++ public static boolean HAS_BEEN_REGISTERED = false; ++ ++ public static final String ENVIRONMENT_KEY = "environment"; ++ public static final String VOID_DAMAGE_KEY = "void-damage-amount"; ++ public static final String VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY = "void-damage-min-build-height-offset"; ++ public static final double DEFAULT_VOID_DAMAGE_HEIGHT = -64.0D; ++ public static final double DEFAULT_VOID_DAMAGE = 4.0D; ++ ++ private final String worldName; ++ ++ private VoidDamageHeightMigration(String worldName) { ++ this.worldName = PaperConfigurations.WORLD_DEFAULTS.equals(worldName) ? "default" : worldName; ++ } ++ ++ @Override ++ public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { ++ String purpurVoidDamageHeightPath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-height"; ++ ConfigurationNode voidDamageMinHeightOffsetNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY); ++ if (PurpurConfig.config.contains(purpurVoidDamageHeightPath)) { ++ double purpurVoidDamageHeight = PurpurConfig.config.getDouble(purpurVoidDamageHeightPath); ++ if (purpurVoidDamageHeight != DEFAULT_VOID_DAMAGE_HEIGHT && (voidDamageMinHeightOffsetNode.empty() || voidDamageMinHeightOffsetNode.getDouble() == DEFAULT_VOID_DAMAGE_HEIGHT)) { ++ voidDamageMinHeightOffsetNode.raw(null); ++ voidDamageMinHeightOffsetNode.set(purpurVoidDamageHeight); ++ } ++ PurpurConfig.config.set(purpurVoidDamageHeightPath, null); ++ } ++ ++ String purpurVoidDamagePath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-dealt"; ++ ConfigurationNode voidDamageNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_KEY); ++ if (PurpurConfig.config.contains(purpurVoidDamagePath)) { ++ double purpurVoidDamage = PurpurConfig.config.getDouble(purpurVoidDamagePath); ++ if (purpurVoidDamage != DEFAULT_VOID_DAMAGE && (voidDamageNode.empty() || voidDamageNode.getDouble() == DEFAULT_VOID_DAMAGE)) { ++ voidDamageNode.raw(null); ++ voidDamageNode.set(new DoubleOr.Disabled(OptionalDouble.of(purpurVoidDamage))); ++ } ++ PurpurConfig.config.set(purpurVoidDamagePath, null); ++ } ++ ++ return null; ++ } ++ ++ public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap) { ++ if (PurpurConfig.version < 36) { ++ HAS_BEEN_REGISTERED = true; ++ builder.addAction(path(), new VoidDamageHeightMigration(contextMap.require(Configurations.WORLD_NAME))); ++ } ++ } ++ ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index d2a75850af9c6ad2aca66a5f994f1b587d73eac4..a056aa167887abef9e6d531a9edd2cda433567d2 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -2,7 +2,16 @@ + + + +- ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +index 75ed5050f72c001d6eab117a2c0b352a413548bd..2764d4204d51a3615f27b8bfd7b6675294465155 100644 +--- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java ++++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +@@ -46,6 +46,7 @@ public class MinecraftCommandPermissionsTest { + Set foundPerms = new HashSet<>(); + for (CommandNode child : root.getChildren()) { + final String vanillaPerm = VanillaCommandWrapper.getPermission(child); ++ if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur - Skip junit tests for purpur commands + if (!perms.contains(vanillaPerm)) { + missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); + } else { +@@ -58,6 +59,25 @@ public class MinecraftCommandPermissionsTest { + } + + private static final List TO_SKIP = List.of( ++ // Purpur start - Skip junit tests for purpur commands ++ "minecraft.command.compass", ++ "minecraft.command.credits", ++ "minecraft.command.demo", ++ "minecraft.command.ping", ++ "minecraft.command.ram", ++ "minecraft.command.rambar", ++ "minecraft.command.tpsbar", ++ "minecraft.command.uptime", ++ "minecraft.command.debug", ++ "minecraft.command.gamemode.adventure", ++ "minecraft.command.gamemode.adventure.other", ++ "minecraft.command.gamemode.creative", ++ "minecraft.command.gamemode.creative.other", ++ "minecraft.command.gamemode.spectator", ++ "minecraft.command.gamemode.spectator.other", ++ "minecraft.command.gamemode.survival", ++ "minecraft.command.gamemode.survival.other", ++ // Purpur end - Skip junit tests for purpur commands + "minecraft.command.selector" + ); + diff --git a/Leaf-Server/paper-patches/features/0005-Fix-Pufferfish-and-Purpur-patches.patch b/Leaf-Server/paper-patches/features/0005-Fix-Pufferfish-and-Purpur-patches.patch new file mode 100644 index 00000000..7f1e1134 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0005-Fix-Pufferfish-and-Purpur-patches.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Mon, 29 Apr 2024 14:18:58 -0400 +Subject: [PATCH] Fix Pufferfish and Purpur patches + + +diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +index ea2bca6e0bbc56156c2f744769abd677e6fb0c18..aa80daab4917e22bc3849eb9b0915b1c705091af 100644 +--- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java ++++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +@@ -32,6 +32,8 @@ public record ServerBuildInfoImpl( + + private static final String BRAND_PAPER_NAME = "Paper"; + private static final String BRAND_GALE_NAME = "Gale"; // Gale - branding changes ++ private static final String BRAND_PUFFERFISH_NAME = "Pufferfish"; // Leaf ++ private static final String BRAND_PURPUR_NAME = "Purpur"; // Leaf - Purpur - Rebrand + private static final String BRAND_LEAF_NAME = "Leaf"; // Leaf + + private static final String BUILD_DEV = "DEV"; +@@ -65,7 +67,9 @@ public record ServerBuildInfoImpl( + public boolean isBrandCompatible(final @NotNull Key brandId) { + return brandId.equals(this.brandId) + || brandId.equals(BRAND_PAPER_ID) +- || brandId.equals(BRAND_GALE_ID); // Gale - branding changes // Leaf ++ || brandId.equals(BRAND_GALE_ID) // Gale - branding changes // Leaf ++ || brandId.equals(BRAND_PUFFERFISH_ID) // Leaf ++ || brandId.equals(BRAND_PURPUR_ID); // Leaf + } + + @Override diff --git a/patches/server/0014-Remove-Timings.patch b/Leaf-Server/paper-patches/features/0006-Remove-Timings.patch similarity index 77% rename from patches/server/0014-Remove-Timings.patch rename to Leaf-Server/paper-patches/features/0006-Remove-Timings.patch index 4f09a40c..3c1cfdc5 100644 --- a/patches/server/0014-Remove-Timings.patch +++ b/Leaf-Server/paper-patches/features/0006-Remove-Timings.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Remove Timings Completely remove the Timings, since it wastes too much performance. Use Spark instead. diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index 7ce9ebba8ce304d1f3f21d4f15ee5f3560d7700b..a1c9726d25479b5326fe2fa2b0f5a98d6b2da4c5 100644 +index 7ce9ebba8ce304d1f3f21d4f15ee5f3560d7700b..4c003acccdd2dd17918b15316001e52e7670123e 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java @@ -1,6 +1,5 @@ @@ -29,7 +29,7 @@ index 7ce9ebba8ce304d1f3f21d4f15ee5f3560d7700b..a1c9726d25479b5326fe2fa2b0f5a98d } - EventExecutor executor = new TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); -+ EventExecutor executor = EventExecutor.create(method, eventClass); ++ EventExecutor executor = EventExecutor.create(method, eventClass); // Leaf - Remove Timings eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); } return ret; @@ -46,23 +46,11 @@ index 097500a59336db1bbfffcd1aa4cff7a8586e46ec..f06076864582ed153c6154fd7f3e9101 } @Override -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index a7a8273a21434fc04b01d06708b65e80487a95d3..2550e0e4e40eccb6e01cd0b8287358c105abaebf 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -3,7 +3,6 @@ package net.minecraft.server; - import com.google.common.base.Preconditions; - import com.google.common.base.Splitter; - import com.google.common.collect.ImmutableList; --import co.aikar.timings.Timings; - import com.destroystokyo.paper.event.server.PaperServerListPingEvent; - import com.google.common.base.Stopwatch; - import com.google.common.collect.Lists; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ef8041877456316fc32a382e28ee81161d962353..b4986e322cb05ad6f98bbf5eee2b571c67e4d9d3 100644 +index de2c10a18869d3fcfc1ae1329489155d7bdc4dd8..911bb12bcbcae2b8046d786e7b828cc1e1bcdb3a 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1047,10 +1047,8 @@ public final class CraftServer implements Server { +@@ -1048,10 +1048,8 @@ public final class CraftServer implements Server { commands.performCommand(results, commandLine, commandLine, true); } catch (CommandException ex) { this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper diff --git a/patches/server/0019-KeYi-Player-Skull-API.patch b/Leaf-Server/paper-patches/features/0007-KeYi-Player-Skull-API.patch similarity index 82% rename from patches/server/0019-KeYi-Player-Skull-API.patch rename to Leaf-Server/paper-patches/features/0007-KeYi-Player-Skull-API.patch index 34a1b158..53c9cf99 100644 --- a/patches/server/0019-KeYi-Player-Skull-API.patch +++ b/Leaf-Server/paper-patches/features/0007-KeYi-Player-Skull-API.patch @@ -7,13 +7,13 @@ Original license: MIT Original project: https://github.com/KeYiMC/KeYi diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 63065a22ff359c142bab23fccacfd5ebd86f81a5..d0bdce91b073344f218c1624cd66fdda5d3e5b0e 100644 +index 9fbbdc9664353fd2be8eae112e5cfe8880d51d08..b60982fa6e11c1f72e2c7021059ebbd04e064364 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3675,4 +3675,31 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -3640,4 +3640,31 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); } - // Purpur end + // Purpur end - Death screen API + + // Leaf start - KeYi - Player Skull API + @Override @@ -31,8 +31,8 @@ index 63065a22ff359c142bab23fccacfd5ebd86f81a5..d0bdce91b073344f218c1624cd66fdda + @Deprecated(forRemoval = true) + public CompletableFuture getSkullAsynchronously() { + org.apache.logging.log4j.LogManager.getLogger("Leaf") -+ .warn("You should not use this method: Player#getSkullAsynchronously(), cause low performance, " + -+ "and will be removed in the future."); ++ .warn("You should not use this method: Player#getSkullAsynchronously(), cause low performance, " + ++ "and will be removed in the future."); + java.util.concurrent.ExecutorService executorService = java.util.concurrent.Executors.newCachedThreadPool(); + + CompletableFuture future = (CompletableFuture) executorService.submit(this::getSkull); diff --git a/Leaf-Server/paper-patches/features/0008-Slice-Smooth-Teleports.patch b/Leaf-Server/paper-patches/features/0008-Slice-Smooth-Teleports.patch new file mode 100644 index 00000000..55089631 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0008-Slice-Smooth-Teleports.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sat, 13 Aug 2022 08:58:14 -0500 +Subject: [PATCH] Slice: Smooth Teleports + +Original license: MIT +Original project: https://github.com/Cryptite/Slice + +Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index b60982fa6e11c1f72e2c7021059ebbd04e064364..7729f49719ce1d42fcbc33452d2b4f8f027283da 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1376,6 +1376,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end - Teleportation API + } + ++ // Slice start ++ @Override ++ public void teleportWithoutRespawn(Location location) { ++ ServerPlayer serverPlayer = getHandle(); ++ serverPlayer.smoothWorldTeleport = true; ++ teleport(location); ++ serverPlayer.smoothWorldTeleport = false; ++ } ++ ++ @Override ++ public boolean teleportWithoutRespawnOptionally(Location location) { ++ ServerPlayer serverPlayer = getHandle(); ++ serverPlayer.smoothWorldTeleport = true; ++ boolean teleportResult = teleport(location); ++ serverPlayer.smoothWorldTeleport = false; ++ return teleportResult; ++ } ++ // Slice end ++ + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + // Paper start - Teleport API diff --git a/Leaf-Server/paper-patches/features/0009-Leaves-Protocol-Core.patch b/Leaf-Server/paper-patches/features/0009-Leaves-Protocol-Core.patch new file mode 100644 index 00000000..2292ac70 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0009-Leaves-Protocol-Core.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Tue, 26 Sep 2023 19:00:41 +0800 +Subject: [PATCH] Leaves: Protocol Core + +TODO: Check whether Leaves's Return-nether-portal-fix.patch improves performance +and change store way to sql maybe? + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +Commit: 41476d86922416c45f703df2871890831fc42bb5 + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 911bb12bcbcae2b8046d786e7b828cc1e1bcdb3a..dd054c184095644a122be0f610ade1d47d9be74a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -507,6 +507,7 @@ public final class CraftServer implements Server { + this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes + datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark ++ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol + } + + public boolean getCommandBlockOverride(String command) { +@@ -1138,6 +1139,7 @@ public final class CraftServer implements Server { + org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files + this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); + this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); ++ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // Leaves - protocol + + int pollCount = 0; + diff --git a/Leaf-Server/paper-patches/features/0010-Leaves-Replay-Mod-API.patch b/Leaf-Server/paper-patches/features/0010-Leaves-Replay-Mod-API.patch new file mode 100644 index 00000000..78d88c5b --- /dev/null +++ b/Leaf-Server/paper-patches/features/0010-Leaves-Replay-Mod-API.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 3 Aug 2023 20:36:38 +0800 +Subject: [PATCH] Leaves: Replay Mod API + +Co-authored-by: alazeprt + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +This patch is Powered by ReplayMod(https://github.com/ReplayMod) + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 4c003acccdd2dd17918b15316001e52e7670123e..780f3a48152fef6a06dc67bf7fbd1965b13bc4fa 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -40,6 +40,11 @@ class PaperEventManager { + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } ++ // Leaves start - skip photographer ++ if (event instanceof org.bukkit.event.player.PlayerEvent playerEvent && playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.Photographer) { ++ return; ++ } ++ // Leaves end - skip photographer + + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index dd054c184095644a122be0f610ade1d47d9be74a..da73276a7f7e91911919ffca8ec231f37c18ab53 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -321,6 +321,8 @@ public final class CraftServer implements Server { + private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); + private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); + ++ private final org.leavesmc.leaves.entity.CraftPhotographerManager photographerManager = new org.leavesmc.leaves.entity.CraftPhotographerManager(); // Leaves ++ + @Override + public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { + return this.regionizedScheduler; +@@ -409,7 +411,7 @@ public final class CraftServer implements Server { + public CraftServer(DedicatedServer console, PlayerList playerList) { + this.console = console; + this.playerList = (DedicatedPlayerList) playerList; +- this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function() { ++ this.playerView = Collections.unmodifiableList(Lists.transform(playerList.realPlayers, new Function() { // Leaves - replay api + @Override + public CraftPlayer apply(ServerPlayer player) { + return player.getBukkitEntity(); +@@ -3459,4 +3461,11 @@ public final class CraftServer implements Server { + return getServer().lagging; + } + // Purpur end - Lagging threshold ++ ++ // Leaves start - replay mod api ++ @Override ++ public org.leavesmc.leaves.entity.CraftPhotographerManager getPhotographerManager() { ++ return photographerManager; ++ } ++ // Leaves end - replay mod api + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index dca2761fe4765c6e95b5db0d0cb5c818eb8697b4..3235ec3415407d1f4fa420d398364fd62f4fd135 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -117,6 +117,8 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return new CraftHumanEntity(server, (net.minecraft.world.entity.player.Player) entity); + } + ++ if (entity instanceof org.leavesmc.leaves.replay.ServerPhotographer photographer) { return new org.leavesmc.leaves.entity.CraftPhotographer(server, photographer); } // Leaves - replay mod api ++ + // Special case complex part, since there is no extra entity type for them + if (entity instanceof EnderDragonPart complexPart) { + if (complexPart.parentMob instanceof EnderDragon) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 7729f49719ce1d42fcbc33452d2b4f8f027283da..9f29ca8f0c32687705906ed912721c3dbca5299c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2276,7 +2276,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean canSee(Player player) { +- return this.canSee((org.bukkit.entity.Entity) player); ++ return !(player instanceof org.leavesmc.leaves.entity.Photographer) && this.canSee((org.bukkit.entity.Entity) player); // Leaves - skip photographer + } + + @Override diff --git a/patches/server/0048-Skip-event-if-no-listeners.patch b/Leaf-Server/paper-patches/features/0011-Skip-event-if-no-listeners.patch similarity index 94% rename from patches/server/0048-Skip-event-if-no-listeners.patch rename to Leaf-Server/paper-patches/features/0011-Skip-event-if-no-listeners.patch index 7b43a3df..6998ab47 100644 --- a/patches/server/0048-Skip-event-if-no-listeners.patch +++ b/Leaf-Server/paper-patches/features/0011-Skip-event-if-no-listeners.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Skip event if no listeners diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index 50c0dd5591de5dd4b1977c557d91cc3c339069cc..e42677bb004201efe1702779a78cc8d0ca05e80f 100644 +index 780f3a48152fef6a06dc67bf7fbd1965b13bc4fa..379c2dc1853e45a96dda9b13bf28b7e08f65658a 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java @@ -35,6 +35,10 @@ class PaperEventManager { diff --git a/patches/server/0052-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch b/Leaf-Server/paper-patches/features/0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch similarity index 66% rename from patches/server/0052-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch rename to Leaf-Server/paper-patches/features/0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch index 3ef80c07..bae29166 100644 --- a/patches/server/0052-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch +++ b/Leaf-Server/paper-patches/features/0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch @@ -13,7 +13,7 @@ To avoid the hefty ArrayDeque's size() call, we check if we *really* need to exe Most entities won't have any scheduled tasks, so this is a nice performance bonus. These optimizations, however, wouldn't work in a Folia environment, but because in SparklyPaper executeTick is always executed on the main thread, it ain't an issue for us (yay). diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -index c03608fec96b51e1867f43d8f42e5aefb1520e46..15b21fa3907db1b77ed5b5d1050a37f42d27d5ab 100644 +index c03608fec96b51e1867f43d8f42e5aefb1520e46..bb56c56cdbd8a15803e85412b9c15b59a28e9e59 100644 --- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java @@ -36,6 +36,7 @@ public final class EntityScheduler { @@ -48,7 +48,7 @@ index c03608fec96b51e1867f43d8f42e5aefb1520e46..15b21fa3907db1b77ed5b5d1050a37f4 } - final Entity thisEntity = this.entity.getHandleRaw(); -+ // final Entity thisEntity = this.entity.getHandleRaw(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (moved up) ++ //final Entity thisEntity = this.entity.getHandleRaw(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (moved up) // correctly handle and order retiring while running executeTick for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { @@ -74,51 +74,11 @@ index c03608fec96b51e1867f43d8f42e5aefb1520e46..15b21fa3907db1b77ed5b5d1050a37f4 if (this.tickCount == RETIRED_TICK_COUNT) { throw new IllegalStateException("Ticking retired scheduler"); } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 87e52d6dcd855c80b3e6b4f4010b596bf8649c80..ff0e1117cbdfd40aec9d7e54492c107165614d20 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -322,6 +322,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping - - public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("Leaf Async Mob Spawn Thread"); // Pufferfish - optimize mob spawning // Leaf - Unify thread name -+ public final Set entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) - - public static S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -@@ -1799,6 +1800,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - for (final Entity entity : level.getEntities().getAll()) { - if (entity.isRemoved()) { -@@ -1810,6 +1823,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop channels = new HashSet(); @@ -41,7 +28,7 @@ index 4129d894d6604f3b2495a35ad2d026c462f68567..324e59802ff9f8757967f3383c9dfc2e private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); private int hash = 0; -@@ -2290,9 +2290,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2281,9 +2281,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public boolean canSee(org.bukkit.entity.Entity entity) { diff --git a/Leaf-Server/paper-patches/features/0014-Including-5s-in-getTPS.patch b/Leaf-Server/paper-patches/features/0014-Including-5s-in-getTPS.patch new file mode 100644 index 00000000..fda4679b --- /dev/null +++ b/Leaf-Server/paper-patches/features/0014-Including-5s-in-getTPS.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sat, 24 Feb 2024 01:16:07 -0500 +Subject: [PATCH] Including 5s in getTPS() + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index da73276a7f7e91911919ffca8ec231f37c18ab53..926d85d1584fa0e9ff16c4079f60cde98e6927f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -3182,6 +3182,8 @@ public final class CraftServer implements Server { + + @Override + public double[] getTPS() { ++ if (org.dreeam.leaf.config.modules.misc.Including5sIngetTPS.enabled) return getTPSIncluding5SecondAverage(); // Leaf - Including 5s in getTPS() ++ + return new double[] { + net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), + net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), diff --git a/patches/server/0059-Don-t-throw-exception-on-missing-ResourceKey-value.patch b/Leaf-Server/paper-patches/features/0015-Don-t-throw-exception-on-missing-ResourceKey-value.patch similarity index 100% rename from patches/server/0059-Don-t-throw-exception-on-missing-ResourceKey-value.patch rename to Leaf-Server/paper-patches/features/0015-Don-t-throw-exception-on-missing-ResourceKey-value.patch diff --git a/patches/server/0061-Virtual-Thread-for-async-scheduler.patch b/Leaf-Server/paper-patches/features/0016-Virtual-Thread-for-async-scheduler.patch similarity index 72% rename from patches/server/0061-Virtual-Thread-for-async-scheduler.patch rename to Leaf-Server/paper-patches/features/0016-Virtual-Thread-for-async-scheduler.patch index fb30f37b..4745cb3d 100644 --- a/patches/server/0061-Virtual-Thread-for-async-scheduler.patch +++ b/Leaf-Server/paper-patches/features/0016-Virtual-Thread-for-async-scheduler.patch @@ -54,30 +54,3 @@ index 0ca279fb71d39c81b1f608e0ee9ba3e498d55fa3..1aaccc688d246201db141a0d5e2c68d0 } @Override -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java b/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..df1acd2fbf3edf84005e63b88475f739dc7518f4 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class VT4BukkitScheduler extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-async-scheduler", enabled, -+ config.pickStringRegionBased( -+ "Use the new Virtual Thread introduced in JDK 21 for CraftAsyncScheduler.", -+ "是否为异步任务调度器使用虚拟线程.")); -+ } -+} diff --git a/Leaf-Server/paper-patches/features/0017-Mirai-Configurable-chat-message-signatures.patch b/Leaf-Server/paper-patches/features/0017-Mirai-Configurable-chat-message-signatures.patch new file mode 100644 index 00000000..b4ef014c --- /dev/null +++ b/Leaf-Server/paper-patches/features/0017-Mirai-Configurable-chat-message-signatures.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz <81570777+etil2jz@users.noreply.github.com> +Date: Tue, 2 Aug 2022 14:48:12 +0200 +Subject: [PATCH] Mirai: Configurable chat message signatures + +Fixed & Updated by Dreeam-qwq +Original license: GPLv3 +Original project: https://github.com/Dreeam-qwq/Mirai + +Original license: GPLv3 +Original project: https://github.com/etil2jz/Mirai + +diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +index 506c746980cfca170efd249d035a572361b667c4..5d8833dddc5145868fc2ac673b3387c83cbe6999 100644 +--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java ++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +@@ -317,7 +317,7 @@ public final class ChatProcessor { + + private void sendToServer(final ChatType.Bound chatType, final @Nullable Function msgFunction) { + final PlayerChatMessage toConsoleMessage = msgFunction == null ? ChatProcessor.this.message : ChatProcessor.this.message.withUnsignedContent(msgFunction.apply(ChatProcessor.this.server.console)); +- ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker || ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure"); // Gale - do not log Not Secure marker ++ ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker || ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure"); // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures + } + } + diff --git a/Leaf-Server/paper-patches/features/0018-Matter-Secure-Seed.patch b/Leaf-Server/paper-patches/features/0018-Matter-Secure-Seed.patch new file mode 100644 index 00000000..c955b586 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0018-Matter-Secure-Seed.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Apehum +Date: Thu, 9 Dec 2021 02:18:17 +0800 +Subject: [PATCH] Matter: Secure Seed + +TODO - Dreeam: +Able to write feature seed in existed level.dat (Done) +Update to BLAKE3 + +Original license: GPLv3 +Original project: https://github.com/plasmoapp/matter + +Co-authored-by: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index de8b9048c8395c05b8688bc9d984b8ad680f15b3..3310a780aba946d7fe9cf2d1d9487e5286177233 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -206,7 +206,12 @@ public class CraftChunk implements Chunk { + @Override + public boolean isSlimeChunk() { + // 987234911L is deterimined in EntitySlime when seeing if a slime can spawn in a chunk +- return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper ++ // Leaf start - Matter - Feature Secure Seed ++ boolean isSlimeChunk = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? worldServer.getChunk(this.getX(), this.getZ()).isSlimeChunk() ++ : WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper ++ return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || isSlimeChunk; ++ // Leaf end - Matter - Feature Secure Seed + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 926d85d1584fa0e9ff16c4079f60cde98e6927f7..c58a626e9ab72f45665cda40581e268ead322091 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1412,7 +1412,11 @@ public final class CraftServer implements Server { + registryAccess = levelDataAndDimensions.dimensions().dimensionsRegistryAccess(); + } else { + LevelSettings levelSettings; +- WorldOptions worldOptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); ++ // Leaf start - Matter - Feature Secure Seed ++ WorldOptions worldOptions = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled ++ ? new WorldOptions(creator.seed(), su.plo.matter.Globals.createRandomWorldSeed(), creator.generateStructures(), false) ++ : new WorldOptions(creator.seed(), creator.generateStructures(), false); ++ // Leaf end - Matter - Feature Secure Seed + WorldDimensions worldDimensions; + + DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); diff --git a/Leaf-Server/paper-patches/features/0019-Faster-Random-Generator.patch b/Leaf-Server/paper-patches/features/0019-Faster-Random-Generator.patch new file mode 100644 index 00000000..8f2f3a3f --- /dev/null +++ b/Leaf-Server/paper-patches/features/0019-Faster-Random-Generator.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Faster Random Generator + +This patch replaces LegacyRandomSource with FasterRandomSource by default, +which is faster in general. + +Benchmark results (10,000,000 iterations) (GraalVM 21) +SimpleRandom (Moonrise): 80ms +FasterRandomSource (Leaf) (Backed by Xoroshiro128PlusPlus): 35ms +LegacyRandomSource (Vanilla): 200ms +XoroshiroRandomSource (Vanilla): 47ms + +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +index c60c05b9e426f56ed3e812abb9aae9ef52bd20e8..268fd8e60630e835c750a8b67201cc63f0b5193d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +@@ -97,7 +97,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator { + } + + private static WorldgenRandom getSeededRandom() { +- return new WorldgenRandom(new LegacyRandomSource(0)); ++ return new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0) : new LegacyRandomSource(0)); // Leaf - Faster random generator + } + + @Override diff --git a/Leaf-Server/paper-patches/features/0020-Configurable-unknown-command-message.patch b/Leaf-Server/paper-patches/features/0020-Configurable-unknown-command-message.patch new file mode 100644 index 00000000..ca7402c7 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0020-Configurable-unknown-command-message.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 7 Aug 2024 18:54:01 +0800 +Subject: [PATCH] Configurable unknown command message + + +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index e0d4222a99f22d7130d95cf29b034a98f2f3b76e..089dd39d428bd1e3773769f2a50cc2f3bc1b4311 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -151,7 +151,6 @@ public class SpigotConfig { + } + + public static String whitelistMessage; +- public static String unknownCommandMessage; + public static String serverFullMessage; + public static String outdatedClientMessage = "Outdated client! Please use {0}"; + public static String outdatedServerMessage = "Outdated server! I'm still on {0}"; +@@ -167,7 +166,6 @@ public class SpigotConfig { + } + + SpigotConfig.whitelistMessage = SpigotConfig.transform(SpigotConfig.getString("messages.whitelist", "You are not whitelisted on this server!")); +- SpigotConfig.unknownCommandMessage = SpigotConfig.transform(SpigotConfig.getString("messages.unknown-command", "Unknown command. Type \"/help\" for help.")); + SpigotConfig.serverFullMessage = SpigotConfig.transform(SpigotConfig.getString("messages.server-full", "The server is full!")); + SpigotConfig.outdatedClientMessage = SpigotConfig.transform(SpigotConfig.getString("messages.outdated-client", SpigotConfig.outdatedClientMessage)); + SpigotConfig.outdatedServerMessage = SpigotConfig.transform(SpigotConfig.getString("messages.outdated-server", SpigotConfig.outdatedServerMessage)); diff --git a/patches/server/0089-Replace-world-map-with-optimized-collection.patch b/Leaf-Server/paper-patches/features/0021-Replace-world-map-with-optimized-collection.patch similarity index 88% rename from patches/server/0089-Replace-world-map-with-optimized-collection.patch rename to Leaf-Server/paper-patches/features/0021-Replace-world-map-with-optimized-collection.patch index da4a96e7..358124ce 100644 --- a/patches/server/0089-Replace-world-map-with-optimized-collection.patch +++ b/Leaf-Server/paper-patches/features/0021-Replace-world-map-with-optimized-collection.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Replace world map with optimized collection diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 7cf02a451ec2b82dc369333041ac7b93d35a2b44..c20ab80b92196c71664a945babaafce561f764ed 100644 +index c58a626e9ab72f45665cda40581e268ead322091..dc7ef642c8dfdb643dda3f3eed66b8371a6911fc 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -283,7 +283,7 @@ public final class CraftServer implements Server { +@@ -284,7 +284,7 @@ public final class CraftServer implements Server { private final StructureManager structureManager; protected final DedicatedServer console; protected final DedicatedPlayerList playerList; diff --git a/Leaf-Server/paper-patches/features/0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch b/Leaf-Server/paper-patches/features/0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch new file mode 100644 index 00000000..673cba45 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sun, 28 Jul 2024 17:44:10 +0800 +Subject: [PATCH] Cache CraftEntityType#minecraftToBukkit convert + +The minecraftToBukkit EntityType convert call is expensive in mob spawn. This convert call is ued for spawn event call, +and the results are always same, thus there is no need to do the convert process every time, just cache it. +Save ~0.16ms per tick, and improve 11660ms -> 60ms in around 1 hour. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java +index 47db4546242974a40f7fc1e34f237fd1f06d5f37..f73fa612a63f2a24646261988843fa8a6e7c3e04 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java +@@ -15,15 +15,25 @@ import org.bukkit.entity.EntityType; + + public class CraftEntityType { + ++ public static final java.util.Map, EntityType> MINECRAFT_TO_BUKKIT_KEY_CACHE = new java.util.concurrent.ConcurrentHashMap<>(); // Leaf - Cache CraftEntityType#minecraftToBukkit convert ++ + public static EntityType minecraftToBukkit(net.minecraft.world.entity.EntityType minecraft) { + Preconditions.checkArgument(minecraft != null); + ++ // Leaf start - Cache CraftEntityType#minecraftToBukkit convert ++ EntityType asBukkit = MINECRAFT_TO_BUKKIT_KEY_CACHE.get(minecraft); ++ ++ if (org.dreeam.leaf.config.modules.opt.EnableCachedMTBEntityTypeConvert.enabled && asBukkit != null) { ++ return asBukkit; ++ } ++ // Leaf end - Cache CraftEntityType#minecraftToBukkit convert ++ + net.minecraft.core.Registry> registry = CraftRegistry.getMinecraftRegistry(Registries.ENTITY_TYPE); + EntityType bukkit = Registry.ENTITY_TYPE.get(CraftNamespacedKey.fromMinecraft(registry.getResourceKey(minecraft).orElseThrow().location())); + + Preconditions.checkArgument(bukkit != null); + +- return bukkit; ++ return MINECRAFT_TO_BUKKIT_KEY_CACHE.put(minecraft, bukkit); // Leaf - Cache CraftEntityType#minecraftToBukkit convert + } + + private static final java.util.Map>> KEY_CACHE = java.util.Collections.synchronizedMap(new java.util.EnumMap<>(EntityType.class)); // Paper diff --git a/Leaf-Server/paper-patches/features/0023-Multithreaded-Tracker.patch b/Leaf-Server/paper-patches/features/0023-Multithreaded-Tracker.patch new file mode 100644 index 00000000..9a7f8022 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0023-Multithreaded-Tracker.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: peaches94 +Date: Sat, 2 Jul 2022 00:35:56 -0500 +Subject: [PATCH] Multithreaded Tracker + +Original license: GPL v3 +Original project: https://github.com/Bloom-host/Petal + +Original license: GPL v3 +Original project: https://github.com/TECHNOVE/Airplane-Experimental + +Co-authored-by: Paul Sauve +Co-authored-by: Kevin Raneri +Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> + +This patch refactored from original multithreaded tracker (Petal version), +and is derived from the Airplane fork by Paul Sauve, the tree is like: +Airplane -> Pufferfish? -> Petal -> Leaf + +We made much of tracking logic asynchronously, and fixed visible issue +for the case of some NPC plugins which using real entity type, e.g. Citizens. + +But it is still recommending to use those packet based, virtual entity +based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc. + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 379c2dc1853e45a96dda9b13bf28b7e08f65658a..361f4de9cdf0f7505628a2fed2a3f5366031e04b 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -42,6 +42,12 @@ class PaperEventManager { + if (event.isAsynchronous() && this.server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { ++ // Leaf start - Multithreaded tracker ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent); ++ return; ++ } ++ // Leaf end - Multithreaded tracker + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } + // Leaves start - skip photographer diff --git a/Leaf-Server/paper-patches/features/0024-Asynchronous-locator.patch b/Leaf-Server/paper-patches/features/0024-Asynchronous-locator.patch new file mode 100644 index 00000000..0da744ac --- /dev/null +++ b/Leaf-Server/paper-patches/features/0024-Asynchronous-locator.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Wed, 23 Oct 2024 23:54:00 +0800 +Subject: [PATCH] Asynchronous locator + +Original license: MIT +Original project: https://github.com/thebrightspark/AsyncLocator + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +index 157e5edb507d6d2a922833c70a1c27abc93c9c34..152fc4347000bb14b3c3fbea3a2bef75efcfc784 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +@@ -101,6 +101,12 @@ public class TickThread extends Thread { + this(null, run, name); + } + ++ // Leaf start - Async locator ++ public TickThread(final Runnable run, final String name, final int id) { ++ this(null, run, name, id); ++ } ++ // Leaf end - Async locator ++ + public TickThread(final ThreadGroup group, final Runnable run, final String name) { + this(group, run, name, ID_GENERATOR.incrementAndGet()); + } +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 361f4de9cdf0f7505628a2fed2a3f5366031e04b..befeb418c7d9021b9043b1f50b688b1870f7e591 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -48,6 +48,12 @@ class PaperEventManager { + return; + } + // Leaf end - Multithreaded tracker ++ // Leaf start - Async locator ++ if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent); ++ return; ++ } ++ // Leaf end - Async locator + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } + // Leaves start - skip photographer diff --git a/patches/server/0119-EMC-Default-don-t-use-blockstate-snapshots.patch b/Leaf-Server/paper-patches/features/0025-EMC-Default-don-t-use-blockstate-snapshots.patch similarity index 100% rename from patches/server/0119-EMC-Default-don-t-use-blockstate-snapshots.patch rename to Leaf-Server/paper-patches/features/0025-EMC-Default-don-t-use-blockstate-snapshots.patch diff --git a/Leaf-Server/paper-patches/features/0026-Faster-CraftServer-getworlds-list-creation.patch b/Leaf-Server/paper-patches/features/0026-Faster-CraftServer-getworlds-list-creation.patch new file mode 100644 index 00000000..1a600538 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0026-Faster-CraftServer-getworlds-list-creation.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Faster CraftServer#getworlds list creation + +CraftServer#getWorlds/Bukkit#getWorlds is frequently used in plugins, +replacing ArrayList with Fastutil ObjectArrayList +brings about 40% performance improvement in benchmark. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index dc7ef642c8dfdb643dda3f3eed66b8371a6911fc..de8001b527d354b16ccc6919675fd5d5038410c9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -992,7 +992,7 @@ public final class CraftServer implements Server { + + @Override + public List getWorlds() { +- return new ArrayList(this.worlds.values()); ++ return new it.unimi.dsi.fastutil.objects.ObjectArrayList(this.worlds.values()); // Leaf - Faster CraftServer#getWorlds list creation + } + + @Override diff --git a/Leaf-Server/paper-patches/features/0027-Cache-chunk-key.patch b/Leaf-Server/paper-patches/features/0027-Cache-chunk-key.patch new file mode 100644 index 00000000..1bc9e275 --- /dev/null +++ b/Leaf-Server/paper-patches/features/0027-Cache-chunk-key.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sat, 2 Nov 2024 04:15:20 -0400 +Subject: [PATCH] Cache chunk key + +Cache convert process between ChunkPos < - > chunkKey +This patch didn't cahce SectionPos or BlockPos to chunkKey, since it needs to consider the mutable blockpos siutation. + +TODO: Cache block pos and section pos, whether need? + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java +index 036c1a287db04c0191e5f84b027ea68d31447cbc..753c3e99e2f677ee1704b206a3196eb05c83f4ea 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/CoordinateUtils.java +@@ -20,15 +20,15 @@ public final class CoordinateUtils { + } + + public static long getChunkKey(final ChunkPos pos) { +- return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); ++ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); // Leaf - Cache chunk key + } + + public static long getChunkKey(final SectionPos pos) { +- return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); ++ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); // Leaf - Cache chunk key + } + + public static long getChunkKey(final int x, final int z) { +- return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); // Leaf - Cache chunk key + } + + public static int getChunkX(final long chunkKey) { diff --git a/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java new file mode 100644 index 00000000..0d1d6751 --- /dev/null +++ b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java @@ -0,0 +1,133 @@ +package gg.pufferfish.pufferfish.sentry; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import io.sentry.Breadcrumb; +import io.sentry.Sentry; +import io.sentry.SentryEvent; +import io.sentry.SentryLevel; +import io.sentry.protocol.Message; +import io.sentry.protocol.User; + +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.dreeam.leaf.config.modules.misc.SentryDSN; + +public class PufferfishSentryAppender extends AbstractAppender { + + private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class.getSimpleName()); + private static final Gson GSON = new Gson(); + private final Level logLevel; + + public PufferfishSentryAppender(Level logLevel) { + super("PufferfishSentryAdapter", new SentryFilter(), null); + this.logLevel = logLevel; + } + + @Override + public void append(LogEvent logEvent) { + if (logEvent.getLevel().isMoreSpecificThan(logLevel) && (logEvent.getThrown() != null || !SentryDSN.onlyLogThrown)) { + try { + logException(logEvent); + } catch (Exception e) { + logger.warn("Failed to log event with sentry", e); + } + } else { + try { + logBreadcrumb(logEvent); + } catch (Exception e) { + logger.warn("Failed to log event with sentry", e); + } + } + } + + private void logException(LogEvent e) { + SentryEvent event = new SentryEvent(e.getThrown()); + + Message sentryMessage = new Message(); + sentryMessage.setMessage(e.getMessage().getFormattedMessage()); + + event.setThrowable(e.getThrown()); + event.setLevel(getLevel(e.getLevel())); + event.setLogger(e.getLoggerName()); + event.setTransaction(e.getLoggerName()); + event.setExtra("thread_name", e.getThreadName()); + + boolean hasContext = e.getContextData() != null; + + if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) { + User user = new User(); + user.setId(e.getContextData().getValue("pufferfishsentry_playerid")); + user.setUsername(e.getContextData().getValue("pufferfishsentry_playername")); + event.setUser(user); + } + + if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) { + event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname")); + event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion")); + event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname")); + } + + if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) { + Map eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken>() { + }.getType()); + if (eventFields != null) { + event.setExtra("event", eventFields); + } + } + + Sentry.captureEvent(event); + } + + private void logBreadcrumb(LogEvent e) { + Breadcrumb breadcrumb = new Breadcrumb(); + + breadcrumb.setLevel(getLevel(e.getLevel())); + breadcrumb.setCategory(e.getLoggerName()); + breadcrumb.setType(e.getLoggerName()); + breadcrumb.setMessage(e.getMessage().getFormattedMessage()); + + Sentry.addBreadcrumb(breadcrumb); + } + + private SentryLevel getLevel(Level level) { + return switch (level.getStandardLevel()) { + case TRACE, DEBUG -> SentryLevel.DEBUG; + case WARN -> SentryLevel.WARNING; + case ERROR -> SentryLevel.ERROR; + case FATAL -> SentryLevel.FATAL; + default -> SentryLevel.INFO; + }; + } + + private static class SentryFilter extends AbstractFilter { + + @Override + public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg, + Object... params) { + return this.filter(logger.getName()); + } + + @Override + public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) { + return this.filter(logger.getName()); + } + + @Override + public Result filter(LogEvent event) { + return this.filter(event == null ? null : event.getLoggerName()); + } + + private Result filter(String loggerName) { + return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY + : Result.NEUTRAL; + } + } +} diff --git a/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java new file mode 100644 index 00000000..1fc08518 --- /dev/null +++ b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java @@ -0,0 +1,44 @@ +package gg.pufferfish.pufferfish.sentry; + +import io.sentry.Sentry; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SentryManager { + + private static final Logger logger = LogManager.getLogger(SentryManager.class); + + private SentryManager() { + + } + + private static boolean initialized = false; + + public static synchronized void init(Level logLevel) { + if (initialized) { + return; + } + if (logLevel == null) { + logger.error("Invalid log level, defaulting to WARN."); + logLevel = Level.WARN; + } + try { + initialized = true; + + Sentry.init(options -> { + options.setDsn(org.dreeam.leaf.config.modules.misc.SentryDSN.sentryDsn); + options.setMaxBreadcrumbs(100); + }); + + PufferfishSentryAppender appender = new PufferfishSentryAppender(logLevel); + appender.start(); + ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender); + logger.info("Sentry logging started!"); + } catch (Exception e) { + logger.warn("Failed to initialize sentry!", e); + initialized = false; + } + } + +} diff --git a/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java new file mode 100644 index 00000000..b8f01ce7 --- /dev/null +++ b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java @@ -0,0 +1,76 @@ +package gg.pufferfish.pufferfish.util; + +import com.google.common.collect.Queues; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; + +import java.util.Queue; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; + +public class AsyncExecutor implements Runnable { + + private final Logger LOGGER = LogManager.getLogger("Leaf"); + private final Queue jobs = Queues.newArrayDeque(); + private final Lock mutex = new ReentrantLock(); + private final Condition cond = mutex.newCondition(); + private final Thread thread; + private volatile boolean killswitch = false; + + public AsyncExecutor(String threadName) { + this.thread = new Thread(this, threadName); + } + + public void start() { + thread.start(); + } + + public void kill() { + killswitch = true; + cond.signalAll(); + } + + public void submit(Runnable runnable) { + mutex.lock(); + try { + jobs.offer(runnable); + cond.signalAll(); + } finally { + mutex.unlock(); + } + } + + @Override + public void run() { + while (!killswitch) { + try { + Runnable runnable = takeRunnable(); + if (runnable != null) { + runnable.run(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + LOGGER.error("Failed to execute async job for thread {}", thread.getName(), e); + } + } + } + + private Runnable takeRunnable() throws InterruptedException { + mutex.lock(); + try { + while (jobs.isEmpty() && !killswitch) { + cond.await(); + } + + if (jobs.isEmpty()) return null; // We've set killswitch + + return jobs.remove(); + } finally { + mutex.unlock(); + } + } +} diff --git a/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java new file mode 100644 index 00000000..8b4a51ae --- /dev/null +++ b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java @@ -0,0 +1,20 @@ +package gg.pufferfish.pufferfish.util; + +import java.util.Iterator; + +import org.jetbrains.annotations.NotNull; + +public class IterableWrapper implements Iterable { + + private final Iterator iterator; + + public IterableWrapper(Iterator iterator) { + this.iterator = iterator; + } + + @NotNull + @Override + public Iterator iterator() { + return iterator; + } +} diff --git a/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java new file mode 100644 index 00000000..5578acce --- /dev/null +++ b/Leaf-Server/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java @@ -0,0 +1,42 @@ +package gg.pufferfish.pufferfish.util; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +public class Long2ObjectOpenHashMapWrapper extends Long2ObjectOpenHashMap { + + private final Map backingMap; + + public Long2ObjectOpenHashMapWrapper(Map map) { + backingMap = map; + } + + @Override + public V put(Long key, V value) { + return backingMap.put(key, value); + } + + @Override + public V get(Object key) { + return backingMap.get(key); + } + + @Override + public V remove(Object key) { + return backingMap.remove(key); + } + + @Nullable + @Override + public V putIfAbsent(Long key, V value) { + return backingMap.putIfAbsent(key, value); + } + + @Override + public int size() { + return backingMap.size(); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/LeafBootstrap.java b/Leaf-Server/src/main/java/org/dreeam/leaf/LeafBootstrap.java new file mode 100644 index 00000000..0ffa8fb1 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/LeafBootstrap.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf; + +import io.papermc.paper.PaperBootstrap; +import joptsimple.OptionSet; + +public class LeafBootstrap { + public static final boolean enableFMA = Boolean.parseBoolean(System.getProperty("Leaf.enableFMA", "false")); // Leaf - FMA feature + + public static void boot(final OptionSet options) { + runPreBootTasks(); + + PaperBootstrap.boot(options); + } + + private static void runPreBootTasks() { + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java new file mode 100644 index 00000000..b5e661cd --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java @@ -0,0 +1,23 @@ +package org.dreeam.leaf.async; + +import net.minecraft.Util; +import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +public class AsyncPlayerDataSaving { + + private AsyncPlayerDataSaving() { + } + + public static void saveAsync(Runnable runnable) { + if (!AsyncPlayerDataSave.enabled) { + runnable.run(); + return; + } + + ExecutorService ioExecutor = Util.backgroundExecutor().service(); + CompletableFuture.runAsync(runnable, ioExecutor); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java new file mode 100644 index 00000000..fcede5af --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java @@ -0,0 +1,164 @@ +package org.dreeam.leaf.async.locate; + +import ca.spottedleaf.moonrise.common.util.TickThread; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.structure.Structure; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +// Original project: https://github.com/thebrightspark/AsyncLocator +public class AsyncLocator { + private static final ExecutorService LOCATING_EXECUTOR_SERVICE; + + private AsyncLocator() {} + + public static class AsyncLocatorThread extends TickThread { + private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0); + public AsyncLocatorThread(Runnable run, String name) { + super(run, name, THREAD_COUNTER.incrementAndGet()); + } + + @Override + public void run() { + super.run(); + } + } + + static { + int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads; + LOCATING_EXECUTOR_SERVICE = new ThreadPoolExecutor( + 1, + threads, + org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setThreadFactory( + r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") { + @Override + public void run() { + r.run(); + } + } + ) + .setNameFormat("Leaf Async Locator Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build() + ); + } + + public static void shutdownExecutorService() { + if (LOCATING_EXECUTOR_SERVICE != null) { + LOCATING_EXECUTOR_SERVICE.shutdown(); + } + } + + /** + * Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} + * and returns a {@link LocateTask} with the futures for it. + */ + public static LocateTask locate( + ServerLevel level, + TagKey structureTag, + BlockPos pos, + int searchRadius, + boolean skipKnownStructures + ) { + CompletableFuture completableFuture = new CompletableFuture<>(); + Future future = LOCATING_EXECUTOR_SERVICE.submit( + () -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures) + ); + return new LocateTask<>(level.getServer(), completableFuture, future); + } + + /** + * Queues a task to locate a feature using + * {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a + * {@link LocateTask} with the futures for it. + */ + public static LocateTask>> locate( + ServerLevel level, + HolderSet structureSet, + BlockPos pos, + int searchRadius, + boolean skipKnownStructures + ) { + CompletableFuture>> completableFuture = new CompletableFuture<>(); + Future future = LOCATING_EXECUTOR_SERVICE.submit( + () -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures) + ); + return new LocateTask<>(level.getServer(), completableFuture, future); + } + + private static void doLocateLevel( + CompletableFuture completableFuture, + ServerLevel level, + TagKey structureTag, + BlockPos pos, + int searchRadius, + boolean skipExistingChunks + ) { + BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks); + completableFuture.complete(foundPos); + } + + private static void doLocateChunkGenerator( + CompletableFuture>> completableFuture, + ServerLevel level, + HolderSet structureSet, + BlockPos pos, + int searchRadius, + boolean skipExistingChunks + ) { + Pair> foundPair = level.getChunkSource().getGenerator() + .findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks); + completableFuture.complete(foundPair); + } + + /** + * Holder of the futures for an async locate task as well as providing some helper functions. + * The completableFuture will be completed once the call to + * {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the + * result of it. + * The taskFuture is the future for the {@link Runnable} itself in the executor service. + */ + public record LocateTask(MinecraftServer server, CompletableFuture completableFuture, Future taskFuture) { + /** + * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action. + * Bear in mind that the action will be executed from the task's thread. If you intend to change any game data, + * it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed + * on the main server thread instead. + */ + public LocateTask then(Consumer action) { + completableFuture.thenAccept(action); + return this; + } + + /** + * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server + * thread. + */ + public LocateTask thenOnServerThread(Consumer action) { + completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos))); + return this; + } + + /** + * Helper function that cancels both completableFuture and taskFuture. + */ + public void cancel() { + taskFuture.cancel(true); + completableFuture.cancel(false); + } + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java new file mode 100644 index 00000000..d9279566 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java @@ -0,0 +1,288 @@ +package org.dreeam.leaf.async.path; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.pathfinder.Node; +import net.minecraft.world.level.pathfinder.Path; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +/** + * i'll be using this to represent a path that not be processed yet! + */ +public class AsyncPath extends Path { + + /** + * marks whether this async path has been processed + */ + private volatile PathProcessState processState = PathProcessState.WAITING; + + /** + * runnables waiting for this to be processed + */ + private final List postProcessing = new ArrayList<>(0); + + /** + * a list of positions that this path could path towards + */ + private final Set positions; + + /** + * the supplier of the real processed path + */ + private final Supplier pathSupplier; + + /* + * Processed values + */ + + /** + * this is a reference to the nodes list in the parent `Path` object + */ + private final List nodes; + /** + * the block we're trying to path to + *

+ * while processing, we have no idea where this is so consumers of `Path` should check that the path is processed before checking the target block + */ + private @Nullable BlockPos target; + /** + * how far we are to the target + *

+ * while processing, the target could be anywhere but theoretically we're always "close" to a theoretical target so default is 0 + */ + private float distToTarget = 0; + /** + * whether we can reach the target + *

+ * while processing, we can always theoretically reach the target so default is true + */ + private boolean canReach = true; + + public AsyncPath(@NotNull List emptyNodeList, @NotNull Set positions, @NotNull Supplier pathSupplier) { + //noinspection ConstantConditions + super(emptyNodeList, null, false); + + this.nodes = emptyNodeList; + this.positions = positions; + this.pathSupplier = pathSupplier; + + AsyncPathProcessor.queue(this); + } + + @Override + public boolean isProcessed() { + return this.processState == PathProcessState.COMPLETED; + } + + /** + * returns the future representing the processing state of this path + */ + public synchronized void postProcessing(@NotNull Runnable runnable) { + if (isProcessed()) { + runnable.run(); + } else { + this.postProcessing.add(runnable); + } + } + + /** + * an easy way to check if this processing path is the same as an attempted new path + * + * @param positions - the positions to compare against + * @return true if we are processing the same positions + */ + public boolean hasSameProcessingPositions(final Set positions) { + if (this.positions.size() != positions.size()) { + return false; + } + + return this.positions.containsAll(positions); + } + + /** + * starts processing this path + */ + public synchronized void process() { + if (this.processState == PathProcessState.COMPLETED || + this.processState == PathProcessState.PROCESSING) { + return; + } + + processState = PathProcessState.PROCESSING; + + final Path bestPath = this.pathSupplier.get(); + + this.nodes.addAll(bestPath.nodes); // we mutate this list to reuse the logic in Path + this.target = bestPath.getTarget(); + this.distToTarget = bestPath.getDistToTarget(); + this.canReach = bestPath.canReach(); + + processState = PathProcessState.COMPLETED; + + for (Runnable runnable : this.postProcessing) { + runnable.run(); + } // Run tasks after processing + } + + /** + * if this path is accessed while it hasn't processed, just process it in-place + */ + private void checkProcessed() { + if (this.processState == PathProcessState.WAITING || + this.processState == PathProcessState.PROCESSING) { // Block if we are on processing + this.process(); + } + } + + /* + * overrides we need for final fields that we cannot modify after processing + */ + + @Override + public @NotNull BlockPos getTarget() { + this.checkProcessed(); + + return this.target; + } + + @Override + public float getDistToTarget() { + this.checkProcessed(); + + return this.distToTarget; + } + + @Override + public boolean canReach() { + this.checkProcessed(); + + return this.canReach; + } + + /* + * overrides to ensure we're processed first + */ + + @Override + public boolean isDone() { + return this.processState == PathProcessState.COMPLETED && super.isDone(); + } + + @Override + public void advance() { + this.checkProcessed(); + + super.advance(); + } + + @Override + public boolean notStarted() { + this.checkProcessed(); + + return super.notStarted(); + } + + @Nullable + @Override + public Node getEndNode() { + this.checkProcessed(); + + return super.getEndNode(); + } + + @Override + public Node getNode(int index) { + this.checkProcessed(); + + return super.getNode(index); + } + + @Override + public void truncateNodes(int length) { + this.checkProcessed(); + + super.truncateNodes(length); + } + + @Override + public void replaceNode(int index, Node node) { + this.checkProcessed(); + + super.replaceNode(index, node); + } + + @Override + public int getNodeCount() { + this.checkProcessed(); + + return super.getNodeCount(); + } + + @Override + public int getNextNodeIndex() { + this.checkProcessed(); + + return super.getNextNodeIndex(); + } + + @Override + public void setNextNodeIndex(int nodeIndex) { + this.checkProcessed(); + + super.setNextNodeIndex(nodeIndex); + } + + @Override + public Vec3 getEntityPosAtNode(Entity entity, int index) { + this.checkProcessed(); + + return super.getEntityPosAtNode(entity, index); + } + + @Override + public BlockPos getNodePos(int index) { + this.checkProcessed(); + + return super.getNodePos(index); + } + + @Override + public Vec3 getNextEntityPos(Entity entity) { + this.checkProcessed(); + + return super.getNextEntityPos(entity); + } + + @Override + public BlockPos getNextNodePos() { + this.checkProcessed(); + + return super.getNextNodePos(); + } + + @Override + public Node getNextNode() { + this.checkProcessed(); + + return super.getNextNode(); + } + + @Nullable + @Override + public Node getPreviousNode() { + this.checkProcessed(); + + return super.getPreviousNode(); + } + + public PathProcessState getProcessState() { + return processState; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java new file mode 100644 index 00000000..192edd0f --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -0,0 +1,51 @@ +package org.dreeam.leaf.async.path; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.pathfinder.Path; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.*; +import java.util.function.Consumer; + +/** + * used to handle the scheduling of async path processing + */ +public class AsyncPathProcessor { + + private static final Executor pathProcessingExecutor = new ThreadPoolExecutor( + 1, + org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads, + org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setNameFormat("Leaf Async Pathfinding Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build() + ); + + protected static CompletableFuture queue(@NotNull AsyncPath path) { + return CompletableFuture.runAsync(path::process, pathProcessingExecutor); + } + + /** + * takes a possibly unprocessed path, and waits until it is completed + * the consumer will be immediately invoked if the path is already processed + * the consumer will always be called on the main thread + * + * @param path a path to wait on + * @param afterProcessing a consumer to be called + */ + public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) { + if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) { + asyncPath.postProcessing(() -> + MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path)) + ); + } else { + afterProcessing.accept(path); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java new file mode 100644 index 00000000..b147a967 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java @@ -0,0 +1,45 @@ +package org.dreeam.leaf.async.path; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; +import net.minecraft.world.level.pathfinder.NodeEvaluator; + +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; + +public class NodeEvaluatorCache { + private static final Map> threadLocalNodeEvaluators = new ConcurrentHashMap<>(); + private static final Map nodeEvaluatorToGenerator = new ConcurrentHashMap<>(); + + private static @NotNull Queue getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) { + return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, key -> new MultiThreadedQueue<>()); + } + + public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) { + final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator); + NodeEvaluator nodeEvaluator = getQueueForFeatures(nodeEvaluatorFeatures).poll(); + + if (nodeEvaluator == null) { + nodeEvaluator = generator.generate(nodeEvaluatorFeatures); + } + + nodeEvaluatorToGenerator.put(nodeEvaluator, generator); + + return nodeEvaluator; + } + + public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { + final NodeEvaluatorGenerator generator = nodeEvaluatorToGenerator.remove(nodeEvaluator); + Validate.notNull(generator, "NodeEvaluator already returned"); + + final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator); + getQueueForFeatures(nodeEvaluatorFeatures).offer(nodeEvaluator); + } + + public static void removeNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { + nodeEvaluatorToGenerator.remove(nodeEvaluator); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java new file mode 100644 index 00000000..2c4876bc --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java @@ -0,0 +1,23 @@ +package org.dreeam.leaf.async.path; + +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import net.minecraft.world.level.pathfinder.SwimNodeEvaluator; + +public record NodeEvaluatorFeatures( + NodeEvaluatorType type, + boolean canPassDoors, + boolean canFloat, + boolean canWalkOverFences, + boolean canOpenDoors, + boolean allowBreaching +) { + public static NodeEvaluatorFeatures fromNodeEvaluator(NodeEvaluator nodeEvaluator) { + NodeEvaluatorType type = NodeEvaluatorType.fromNodeEvaluator(nodeEvaluator); + boolean canPassDoors = nodeEvaluator.canPassDoors(); + boolean canFloat = nodeEvaluator.canFloat(); + boolean canWalkOverFences = nodeEvaluator.canWalkOverFences(); + boolean canOpenDoors = nodeEvaluator.canOpenDoors(); + boolean allowBreaching = nodeEvaluator instanceof SwimNodeEvaluator swimNodeEvaluator && swimNodeEvaluator.allowBreaching; + return new NodeEvaluatorFeatures(type, canPassDoors, canFloat, canWalkOverFences, canOpenDoors, allowBreaching); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java new file mode 100644 index 00000000..062ddc24 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.async.path; + +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import org.jetbrains.annotations.NotNull; + +public interface NodeEvaluatorGenerator { + + @NotNull + NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures); + +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java new file mode 100644 index 00000000..c0527323 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf.async.path; + +import net.minecraft.world.level.pathfinder.*; + +public enum NodeEvaluatorType { + WALK, + SWIM, + AMPHIBIOUS, + FLY; + + public static NodeEvaluatorType fromNodeEvaluator(NodeEvaluator nodeEvaluator) { + if (nodeEvaluator instanceof SwimNodeEvaluator) return SWIM; + if (nodeEvaluator instanceof FlyNodeEvaluator) return FLY; + if (nodeEvaluator instanceof AmphibiousNodeEvaluator) return AMPHIBIOUS; + return WALK; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java new file mode 100644 index 00000000..73f30b73 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java @@ -0,0 +1,7 @@ +package org.dreeam.leaf.async.path; + +public enum PathProcessState { + WAITING, + PROCESSING, + COMPLETED, +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/Leaf-Server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java new file mode 100644 index 00000000..1e7377c4 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java @@ -0,0 +1,140 @@ +package org.dreeam.leaf.async.tracker; + +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup; +import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; +import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class MultithreadedTracker { + + private static final Logger LOGGER = LogManager.getLogger("MultithreadedTracker"); + public static class MultithreadedTrackerThread extends Thread { + @Override + public void run() { + super.run(); + } + } + private static final Executor trackerExecutor = new ThreadPoolExecutor( + 1, + org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads, + org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setThreadFactory( + r -> new MultithreadedTrackerThread() { + @Override + public void run() { + r.run(); + } + } + ) + .setNameFormat("Leaf Async Tracker Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build()); + + private MultithreadedTracker() { + } + + public static Executor getTrackerExecutor() { + return trackerExecutor; + } + + public static void tick(ChunkSystemServerLevel level) { + try { + if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { + tickAsync(level); + } else { + tickAsyncWithCompatMode(level); + } + } catch (Exception e) { + LOGGER.error("Error occurred while executing async task.", e); + } + } + + private static void tickAsync(ChunkSystemServerLevel level) { + final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); + final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); + + final ReferenceList trackerEntities = entityLookup.trackerEntities; + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + + // Move tracking to off-main + trackerExecutor.execute(() -> { + for (final Entity entity : trackerEntitiesRaw) { + if (entity == null) continue; + + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + + if (tracker == null) continue; + + ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + tracker.serverEntity.sendChanges(); + } + }); + } + + private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) { + final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); + final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); + + final ReferenceList trackerEntities = entityLookup.trackerEntities; + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length]; + int index = 0; + + for (final Entity entity : trackerEntitiesRaw) { + if (entity == null) continue; + + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + + if (tracker == null) continue; + + ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array + } + + // batch submit tasks + trackerExecutor.execute(() -> { + for (final Runnable sendChanges : sendChangesTasks) { + if (sendChanges == null) continue; + + sendChanges.run(); + } + }); + } + + // Original ChunkMap#newTrackerTick of Paper + // Just for diff usage for future update + private static void tickOriginal(ServerLevel level) { + final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup(); + + final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + for (int i = 0, len = trackerEntities.size(); i < len; ++i) { + final Entity entity = trackerEntitiesRaw[i]; + final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + if (tracker == null) { + continue; + } + ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers); + if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers() + || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { + tracker.serverEntity.sendChanges(); + } + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/ConfigModules.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/ConfigModules.java new file mode 100644 index 00000000..475da124 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/ConfigModules.java @@ -0,0 +1,57 @@ +package org.dreeam.leaf.config; + +import org.dreeam.leaf.config.annotations.Experimental; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class ConfigModules extends LeafConfig { + + private static final Set modules = new HashSet<>(); + public LeafGlobalConfig config; + + public ConfigModules() { + this.config = LeafConfig.config(); + } + + public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + List> enabledExperimentalModules = new ArrayList<>(); + for (Class clazz : LeafConfig.getClasses(LeafConfig.I_CONFIG_PKG)) { + ConfigModules module = (ConfigModules) clazz.getConstructor().newInstance(); + module.onLoaded(); + + modules.add(module); + for (Field field : getAnnotatedStaticFields(clazz, Experimental.class)) { + Object obj = field.get(null); + if (!(obj instanceof Boolean)) continue; + boolean enabled = (Boolean) obj; + if (enabled) { + enabledExperimentalModules.add(clazz); + break; + } + } + } + if (!enabledExperimentalModules.isEmpty()) { + LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please report any bugs you found!", enabledExperimentalModules.stream().map(Class::getSimpleName).toList()); + } + } + + private static List getAnnotatedStaticFields(Class clazz, Class annotation) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(annotation) && Modifier.isStatic(field.getModifiers())) { + field.setAccessible(true); + fields.add(field); + } + } + return fields; + } + + public abstract void onLoaded(); +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java new file mode 100644 index 00000000..7d99f071 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java @@ -0,0 +1,26 @@ +package org.dreeam.leaf.config; + +public enum EnumConfigCategory { + + ASYNC("async"), + PERF("performance"), + FIXES("fixes"), + GAMEPLAY("gameplay-mechanisms"), + NETWORK("network"), + MISC("misc"); + + private final String baseKeyName; + private static final EnumConfigCategory[] VALUES = EnumConfigCategory.values(); + + EnumConfigCategory(String baseKeyName) { + this.baseKeyName = baseKeyName; + } + + public String getBaseKeyName() { + return this.baseKeyName; + } + + public static EnumConfigCategory[] getCategoryValues() { + return VALUES; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafConfig.java new file mode 100644 index 00000000..41b24849 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafConfig.java @@ -0,0 +1,271 @@ +package org.dreeam.leaf.config; + +import io.papermc.paper.configuration.GlobalConfiguration; +import org.dreeam.leaf.config.modules.misc.SentryDSN; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.World; + +/* + * Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol + * @author: @xGinko & @MrHua269 + */ +public class LeafConfig { + + public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName()); + protected static final File I_CONFIG_FOLDER = new File("config"); + protected static final String I_CONFIG_PKG = "org.dreeam.leaf.config.modules"; + protected static final String I_GLOBAL_CONFIG_FILE = "leaf-global.yml"; + protected static final String I_LEVEL_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per level config + + private static LeafGlobalConfig leafGlobalConfig; + + /* Load & Reload */ + + public static void reload() { + try { + long begin = System.nanoTime(); + LOGGER.info("Reloading config..."); + + loadConfig(false); + + LOGGER.info("Successfully reloaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); + } catch (Exception e) { + LOGGER.error("Failed to reload config.", e); + } + } + + @Contract(" -> new") + public static @NotNull CompletableFuture reloadAsync() { + return new CompletableFuture<>(); + } + + public static void loadConfig() { + try { + long begin = System.nanoTime(); + LOGGER.info("Loading config..."); + + purgeOutdated(); + loadConfig(true); + + LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); + } catch (Exception e) { + LeafConfig.LOGGER.error("Failed to load config modules!", e); + } + } + + /* Load Global Config */ + + private static void loadConfig(boolean init) throws Exception { + // Create config folder + createDirectory(LeafConfig.I_CONFIG_FOLDER); + + leafGlobalConfig = new LeafGlobalConfig(init); + + // Load config modules + ConfigModules.initModules(); + + // Save config to disk + leafGlobalConfig.saveConfig(); + } + + public static LeafGlobalConfig config() { + return leafGlobalConfig; + } + + /* Create config folder */ + + protected static void createDirectory(File dir) throws IOException { + try { + Files.createDirectories(dir.toPath()); + } catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory + if (dir.delete()) createDirectory(dir); + } + } + + /* Scan classes under package */ + + public static @NotNull Set> getClasses(String pack) { + Set> classes = new LinkedHashSet<>(); + String packageDirName = pack.replace('.', '/'); + Enumeration dirs; + + try { + dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); + findClassesInPackageByFile(pack, filePath, classes); + } else if ("jar".equals(protocol)) { + JarFile jar; + try { + jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + findClassesInPackageByJar(pack, entries, packageDirName, classes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return classes; + } + + private static void findClassesInPackageByFile(String packageName, String packagePath, Set> classes) { + File dir = new File(packagePath); + + if (!dir.exists() || !dir.isDirectory()) { + return; + } + + File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class")); + if (dirfiles != null) { + for (File file : dirfiles) { + if (file.isDirectory()) { + findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); + } else { + String className = file.getName().substring(0, file.getName().length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + } + } + + private static void findClassesInPackageByJar(String packageName, Enumeration entries, String packageDirName, Set> classes) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (name.charAt(0) == '/') { + name = name.substring(1); + } + + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + + if (idx != -1) { + packageName = name.substring(0, idx).replace('/', '.'); + } + + if (name.endsWith(".class") && !entry.isDirectory()) { + String className = name.substring(packageName.length() + 1, name.length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + } + } + + /* Register Spark profiler extra server configurations */ + + private static List buildSparkExtraConfigs() { + List extraConfigs = new ArrayList<>(Arrays.asList( + "config/leaf-global.yml", + "config/gale-global.yml", + "config/gale-world-defaults.yml" + )); + + for (World world : Bukkit.getWorlds()) { + extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config + } + + return extraConfigs; + } + + private static String[] buildSparkHiddenPaths() { + return new String[]{ + SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key + }; + } + + public static void regSparkExtraConfig() { + if (GlobalConfiguration.get().spark.enabled || Bukkit.getServer().getPluginManager().getPlugin("spark") != null) { + String extraConfigs = String.join(",", buildSparkExtraConfigs()); + String hiddenPaths = String.join(",", buildSparkHiddenPaths()); + + System.setProperty("spark.serverconfigs.extra", extraConfigs); + System.setProperty("spark.serverconfigs.hiddenpaths", hiddenPaths); + } + } + + /* Purge and backup old Leaf config & Pufferfish config */ + + private static void purgeOutdated() { + boolean foundLegacy = false; + String pufferfishConfig = "pufferfish.yml"; + String leafConfigV1 = "leaf.yml"; + String leafConfigV2 = "leaf_config"; + + Date date = new Date(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss"); + String backupDir = "config/backup" + dateFormat.format(date) + "/"; + + File pufferfishConfigFile = new File(pufferfishConfig); + File leafConfigV1File = new File(leafConfigV1); + File leafConfigV2File = new File(leafConfigV2); + File backupDirFile = new File(backupDir); + + try { + if (pufferfishConfigFile.exists() && pufferfishConfigFile.isFile()) { + createDirectory(backupDirFile); + Files.move(pufferfishConfigFile.toPath(), Path.of(backupDir + pufferfishConfig), StandardCopyOption.REPLACE_EXISTING); + foundLegacy = true; + } + if (leafConfigV1File.exists() && leafConfigV1File.isFile()) { + createDirectory(backupDirFile); + Files.move(leafConfigV1File.toPath(), Path.of(backupDir + leafConfigV1), StandardCopyOption.REPLACE_EXISTING); + foundLegacy = true; + } + if (leafConfigV2File.exists() && leafConfigV2File.isDirectory()) { + createDirectory(backupDirFile); + Files.move(leafConfigV2File.toPath(), Path.of(backupDir + leafConfigV2), StandardCopyOption.REPLACE_EXISTING); + foundLegacy = true; + } + + if (foundLegacy) { + LOGGER.warn("Found legacy Leaf config files, move to backup directory: {}", backupDir); + LOGGER.warn("New Leaf config located at config/ folder, You need to transfer config to the new one manually and restart the server!"); + } + } catch (IOException e) { + LOGGER.error("Failed to purge old configs.", e); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java new file mode 100644 index 00000000..794bc822 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java @@ -0,0 +1,137 @@ +package org.dreeam.leaf.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import io.github.thatsmusic99.configurationmaster.api.ConfigSection; + +import java.io.File; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class LeafGlobalConfig { + + protected static ConfigFile configFile; + private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT); // It will be in uppercase by default, just make sure + protected static final boolean isCN = CURRENT_REGION.equals("CN"); + + public LeafGlobalConfig(boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE)); + configFile.set("config-version", 3.0); + configFile.addComments("config-version", pickStringRegionBased(""" + Leaf Config + GitHub Repo: https://github.com/Winds-Studio/Leaf + Discord: https://discord.com/invite/gfgAwdSEuM""", + """ + Leaf Config + GitHub Repo: https://github.com/Winds-Studio/Leaf + QQ Group: 619278377""")); + + // Pre-structure to force order + structureConfig(); + } + + protected void structureConfig() { + for (EnumConfigCategory configCate : EnumConfigCategory.getCategoryValues()) { + createTitledSection(configCate.name(), configCate.getBaseKeyName()); + } + } + + public void saveConfig() throws Exception { + configFile.save(); + } + + // Config Utilities + + public void createTitledSection(String title, String path) { + configFile.addSection(title); + configFile.addDefault(path, null); + } + + public boolean getBoolean(String path, boolean def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getBoolean(path, def); + } + + public boolean getBoolean(String path, boolean def) { + configFile.addDefault(path, def); + return configFile.getBoolean(path, def); + } + + public String getString(String path, String def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getString(path, def); + } + + public String getString(String path, String def) { + configFile.addDefault(path, def); + return configFile.getString(path, def); + } + + public double getDouble(String path, double def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getDouble(path, def); + } + + public double getDouble(String path, double def) { + configFile.addDefault(path, def); + return configFile.getDouble(path, def); + } + + public int getInt(String path, int def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getInteger(path, def); + } + + public int getInt(String path, int def) { + configFile.addDefault(path, def); + return configFile.getInteger(path, def); + } + + public List getList(String path, List def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getStringList(path); + } + + public List getList(String path, List def) { + configFile.addDefault(path, def); + return configFile.getStringList(path); + } + + public ConfigSection getConfigSection(String path, Map defaultKeyValue) { + configFile.addDefault(path, null); + configFile.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) { + configFile.addDefault(path, null, comment); + configFile.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + public void addComment(String path, String comment) { + configFile.addComment(path, comment); + } + + public void addCommentIfCN(String path, String comment) { + if (isCN) { + configFile.addComment(path, comment); + } + } + + public void addCommentIfNonCN(String path, String comment) { + if (!isCN) { + configFile.addComment(path, comment); + } + } + + public void addCommentRegionBased(String path, String en, String cn) { + configFile.addComment(path, isCN ? cn : en); + } + + public String pickStringRegionBased(String en, String cn) { + return isCN ? cn : en; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java new file mode 100644 index 00000000..b6687584 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java @@ -0,0 +1,8 @@ +package org.dreeam.leaf.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface DoNotLoad { +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java new file mode 100644 index 00000000..26a6967c --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java @@ -0,0 +1,12 @@ +package org.dreeam.leaf.config.annotations; + +import java.lang.annotation.*; + +/** + * Indicates that a feature is experimental and may be removed or changed in the future. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.FIELD}) +public @interface Experimental { +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java new file mode 100644 index 00000000..c89bf6a7 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java @@ -0,0 +1,8 @@ +package org.dreeam.leaf.config.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HotReloadUnsupported { +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java new file mode 100644 index 00000000..0d741e4d --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java @@ -0,0 +1,37 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +public class AsyncLocator extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator"; + } + + public static boolean enabled = false; + public static int asyncLocatorThreads = 0; + public static int asyncLocatorKeepalive = 60; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Whether or not asynchronous locator should be enabled. + This offloads structure locating to other threads. + Only for locate command, dolphin treasure finding and eye of ender currently.""", + """ + 是否启用异步结构搜索. + 目前可用于 /locate 指令, 海豚寻宝和末影之眼."""); + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads); + asyncLocatorKeepalive = config.getInt(getBasePath() + ".keepalive", asyncLocatorKeepalive); + + if (asyncLocatorThreads <= 0) + asyncLocatorThreads = 1; + if (!enabled) + asyncLocatorThreads = 0; + else + LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java new file mode 100644 index 00000000..9a9cbb8a --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java @@ -0,0 +1,34 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class AsyncMobSpawning extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-mob-spawning"; + } + + public static boolean enabled = true; + public static boolean asyncMobSpawningInitialized; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Whether or not asynchronous mob spawning should be enabled. + On servers with many entities, this can improve performance by up to 15%. You must have + paper's per-player-mob-spawns setting set to true for this to work. + One quick note - this does not actually spawn mobs async (that would be very unsafe). + This just offloads some expensive calculations that are required for mob spawning.""", + """ + 是否异步化生物生成. + 在实体较多的服务器上, 异步生成可最高带来15%的性能提升. + 须在Paper配置文件中打开 per-player-mob-spawns 才能生效."""); + + // This prevents us from changing the value during a reload. + if (!asyncMobSpawningInitialized) { + asyncMobSpawningInitialized = true; + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java new file mode 100644 index 00000000..b45d738f --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java @@ -0,0 +1,32 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +public class AsyncPathfinding extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-pathfinding"; + } + + public static boolean enabled = false; + public static int asyncPathfindingMaxThreads = 0; + public static int asyncPathfindingKeepalive = 60; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + asyncPathfindingMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncPathfindingMaxThreads); + asyncPathfindingKeepalive = config.getInt(getBasePath() + ".keepalive", asyncPathfindingKeepalive); + + if (asyncPathfindingMaxThreads < 0) + asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathfindingMaxThreads, 1); + else if (asyncPathfindingMaxThreads == 0) + asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); + if (!enabled) + asyncPathfindingMaxThreads = 0; + else + LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", asyncPathfindingMaxThreads); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java new file mode 100644 index 00000000..148a75e8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java @@ -0,0 +1,28 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; + +public class AsyncPlayerDataSave extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save"; + } + + @Experimental + public static boolean enabled = false; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), + """ + **Experimental feature, may have data lost in some circumstances!** + Make PlayerData saving asynchronously.""", + """ + **实验性功能, 在部分场景下可能丢失玩家数据!** + 异步保存玩家数据."""); + + enabled = config().getBoolean(getBasePath() + ".enabled", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java new file mode 100644 index 00000000..bf253dbd --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java @@ -0,0 +1,48 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +public class MultithreadedTracker extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker"; + } + + public static boolean enabled = false; + public static boolean compatModeEnabled = false; + public static int asyncEntityTrackerMaxThreads = 0; + public static int asyncEntityTrackerKeepalive = 60; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Make entity tracking saving asynchronously, can improve performance significantly, + especially in some massive entities in small area situations.""", + """ + 异步实体跟踪, + 在实体数量多且密集的情况下效果明显."""); + + enabled = config().getBoolean(getBasePath() + ".enabled", enabled); + compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased(""" + Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed, + Compat mode fixed visible issue with player type NPCs of Citizens, + But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""", + """ + 是否启用兼容模式, + 如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项.""")); + asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads); + asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive); + + if (asyncEntityTrackerMaxThreads < 0) + asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1); + else if (asyncEntityTrackerMaxThreads == 0) + asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); + + if (!enabled) + asyncEntityTrackerMaxThreads = 0; + else + LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java new file mode 100644 index 00000000..ae21bcac --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java @@ -0,0 +1,25 @@ +package org.dreeam.leaf.config.modules.fixes; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class DontPlacePlayerIfFull extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.FIXES.getBaseKeyName(); + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".dont-place-player-if-server-full", enabled, config.pickStringRegionBased(""" + Don't let player join server if the server is full. + If enable this, you should use 'purpur.joinfullserver' permission instead of + PlayerLoginEvent#allow to let player join full server.""", + """ + 服务器已满时禁止玩家加入. + 开启后需使用权限 'purpur.joinfullserver' 而不是 + PlayerLoginEvent#allow 让玩家进入已满的服务器.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java new file mode 100644 index 00000000..b1ae10ce --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java @@ -0,0 +1,28 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ConfigurableMaxUseItemDistance extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".player"; + } + + public static double maxUseItemDistance = 1.0000001D; + + @Override + public void onLoaded() { + maxUseItemDistance = config.getDouble(getBasePath() + ".max-use-item-distance", maxUseItemDistance, config.pickStringRegionBased(""" + The max distance of UseItem for players. + Set to -1 to disable max-distance-check. + NOTE: if set to -1 to disable the check, + players are able to use some packet modules of hack clients, + and NoCom Exploit!!""", + """ + 玩家 UseItem 的最大距离. + 设置为 -1 来禁用最大距离检测. + 注意: 禁用此项后, + 玩家可以使用作弊客户端的部分发包模块和 NoCom 漏洞!!""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java new file mode 100644 index 00000000..b259b702 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ConfigurableTripWireDupe extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName(); + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".allow-tripwire-dupe", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java new file mode 100644 index 00000000..14ed289c --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java @@ -0,0 +1,22 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class DisableMovedWronglyThreshold extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".player"; + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".disable-moved-wrongly-threshold", enabled, + config.pickStringRegionBased( + "Disable moved quickly/wrongly checks.", + "关闭 moved wrongly/too quickly! 警告." + )); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java new file mode 100644 index 00000000..3499b1e6 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java @@ -0,0 +1,34 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class Knockback extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".knockback"; + } + + public static boolean snowballCanKnockback = false; + public static boolean eggCanKnockback = false; + public static boolean canPlayerKnockbackZombie = true; + + @Override + public void onLoaded() { + snowballCanKnockback = config.getBoolean(getBasePath() + ".snowball-knockback-players", snowballCanKnockback, + config.pickStringRegionBased( + "Make snowball can knockback players.", + "使雪球可以击退玩家." + )); + eggCanKnockback = config.getBoolean(getBasePath() + ".egg-knockback-players", eggCanKnockback, + config.pickStringRegionBased( + "Make egg can knockback players.", + "使鸡蛋可以击退玩家." + )); + canPlayerKnockbackZombie = config.getBoolean(getBasePath() + ".can-player-knockback-zombie", canPlayerKnockbackZombie, + config.pickStringRegionBased( + "Make players can knockback zombie.", + "使玩家可以击退僵尸." + )); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/MaxItemsStackCount.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/MaxItemsStackCount.java new file mode 100644 index 00000000..434a938c --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/MaxItemsStackCount.java @@ -0,0 +1,27 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class MaxItemsStackCount extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".max-item-stack-count"; + } + + public static int maxItemStackCount = 0; + public static int maxContainerDestroyCount = 0; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), + "Don't touch this unless you know what you are doing!", + "不要动该项, 除非你知道自己在做什么!"); + + maxItemStackCount = config.getInt(getBasePath() + ".max-dropped-items-stack-count", maxItemStackCount); + maxContainerDestroyCount = config.getInt(getBasePath() + ".max-container-destroy-count", maxContainerDestroyCount); + + if (maxItemStackCount < 0) maxItemStackCount = 0; + if (maxContainerDestroyCount < 0) maxContainerDestroyCount = 0; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java new file mode 100644 index 00000000..afed2f92 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java @@ -0,0 +1,29 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; + +public class SmoothTeleport extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".smooth-teleport"; + } + + @Experimental + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased( + """ + **Experimental feature, report any bugs you encounter!** + Whether to make a "smooth teleport" when players changing dimension. + This requires original world and target world have same logical height to work.""", + """ + **实验性功能, 请及时反馈你遇到的问题!** + 是否在玩家切换世界时尝试使用 "平滑传送". + 此项要求源世界和目标世界逻辑高度相同才会生效.""" + )); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java new file mode 100644 index 00000000..25373d9c --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class UseSpigotItemMergingMech extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".use-spigot-item-merging-mechanism"; + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java new file mode 100644 index 00000000..ca342af5 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java @@ -0,0 +1,29 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class Cache extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".cache"; + } + + public static boolean cachePlayerProfileResult = true; + public static int cachePlayerProfileResultTimeout = 1440; + + @Override + public void onLoaded() { + cachePlayerProfileResult = config.getBoolean(getBasePath() + ".cache-player-profile-result", cachePlayerProfileResult, config.pickStringRegionBased(""" + Cache the player profile result on they first join. + It's useful if Mojang's verification server is down.""", + """ + 玩家首次加入时缓存 PlayerProfile. + 正版验证服务器宕机时非常有用.""")); + cachePlayerProfileResultTimeout = config.getInt(getBasePath() + ".cache-player-profile-result-timeout", cachePlayerProfileResultTimeout, + config.pickStringRegionBased( + "The timeout of the cache. Unit: Minutes.", + "缓存过期时间. 单位: 分钟." + )); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java new file mode 100644 index 00000000..72782ef6 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java @@ -0,0 +1,41 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ConnectionMessage extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".connection-message"; + } + + public static boolean joinEnabled = true; + public static String joinMessage = "default"; + public static boolean quitEnabled = true; + public static String quitMessage = "default"; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Connection message, using MiniMessage format, set to "default" to use vanilla join message. + available placeholders: + %player_name% - player name + %player_displayname% - player display name""", + """ + 自定义加入 & 退出消息 (MiniMessage 格式), 设置为 'default' 将使用原版消息. + 可用的内置变量: + %player_name% - 玩家名称 + %player_displayname% - 玩家显示名称"""); + + joinEnabled = config.getBoolean(getBasePath() + ".join.enabled", joinEnabled); + joinMessage = config.getString(getBasePath() + ".join.message", joinMessage, config.pickStringRegionBased( + "Join message of player", + "玩家加入服务器时的消息" + )); + + quitEnabled = config.getBoolean(getBasePath() + ".quit.enabled", quitEnabled); + quitMessage = config.getString(getBasePath() + ".quit.message", quitMessage, config.pickStringRegionBased( + "Quit message of player", + "玩家退出服务器时的消息")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/HiddenItemComponents.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/HiddenItemComponents.java new file mode 100644 index 00000000..81f28d84 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/HiddenItemComponents.java @@ -0,0 +1,60 @@ +package org.dreeam.leaf.config.modules.misc; + +import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class HiddenItemComponents extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName(); + } + + public static List> hiddenItemComponentTypes = List.of(); + + @Override + public void onLoaded() { + List list = config.getList(getBasePath() + ".hidden-item-components", new ArrayList<>(), config.pickStringRegionBased(""" + Controls whether specified component information is sent to clients. + This may break resource packs and mods that rely on this information. + It needs a component type list, incorrect things will not work. + You can fill it with ["custom_data"] to hide components of CUSTOM_DATA. + Also, it can avoid some frequent client animations. + NOTICE: You must know what you're filling in and how it works! It will handle all itemStacks!""", + """ + 控制哪些物品组件信息会被发送至客户端. + 可能会导致依赖物品组件的资源包/模组无法正常工作. + 该配置项接受一个物品组件列表, 格式不正确将不会启用. + 可以填入 ["custom_data"] 来隐藏自定义数据物品组件 CUSTOM_DATA. + 也可以避免一些客户端动画效果. + 注意: 你必须知道你填进去的是什么, 有什么用, 该项配置会处理所有的ItemStack!""")); + + List> types = new ArrayList<>(list.size()); + + for (String id : list) { + // Find and check + Optional>> optional = BuiltInRegistries.DATA_COMPONENT_TYPE.get(ResourceLocation.parse(id)); + + if (optional.isEmpty()) continue; + + DataComponentType type = optional.get().value(); + + if (type != null) { + types.add(type); + } else { + LeafConfig.LOGGER.warn("Unknown component type: {}", id); + } + } + + hiddenItemComponentTypes = types; + } + +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java new file mode 100644 index 00000000..8a7eb25b --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class Including5sIngetTPS extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".including-5s-in-get-tps", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java new file mode 100644 index 00000000..73b9199b --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java @@ -0,0 +1,29 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class LagCompensation extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".lag-compensation"; + } + + public static boolean enabled = false; + public static boolean enableForWater = false; + public static boolean enableForLava = false; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + This section contains lag compensation features, + which could ensure basic playing experience during a lag.""", + """ + 这部分包含滞后补偿功能, + 可以在卡顿情况下保障基本游戏体验."""); + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + enableForWater = config.getBoolean(getBasePath() + ".enable-for-water", enableForWater); + enableForLava = config.getBoolean(getBasePath() + ".enable-for-lava", enableForLava); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java new file mode 100644 index 00000000..d94f8dd4 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java @@ -0,0 +1,62 @@ +package org.dreeam.leaf.config.modules.misc; + +import com.mojang.logging.LogUtils; +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.DoNotLoad; +import org.slf4j.Logger; +import org.stupidcraft.linearpaper.region.EnumRegionFileExtension; + +public class RegionFormatConfig extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".region-format-settings"; + } + + @DoNotLoad + private static final Logger logger = LogUtils.getLogger(); + @DoNotLoad + public static EnumRegionFileExtension regionFormatType; + + public static String regionFormatTypeName = "MCA"; + public static int linearCompressionLevel = 1; + public static boolean throwOnUnknownExtension = false; + public static int linearFlushFrequency = 5; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Linear is a region file format that uses ZSTD compression instead of + ZLIB. + This format saves about 50% of disk space. + Read Documentation before using: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools + Disclaimer: This is an experimental feature, there is potential risk to lose chunk data. + So backup your server before switching to Linear.""", + """ + Linear 是一种使用 ZSTD 压缩而非 ZLIB 的区域文件格式. + 该格式可节省约 50% 的磁盘空间. + 使用前请阅读文档: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools + 免责声明: 实验性功能,有可能导致区块数据丢失. + 切换到Linear前请备份服务器."""); + + regionFormatTypeName = config.getString(getBasePath() + ".region-format", regionFormatTypeName, + config.pickStringRegionBased( + "Available region formats: MCA, LINEAR", + "可用格式: MCA, LINEAR")); + linearCompressionLevel = config.getInt(getBasePath() + ".linear-compress-level", linearCompressionLevel); + throwOnUnknownExtension = config().getBoolean(getBasePath() + ".throw-on-unknown-extension-detected", throwOnUnknownExtension); + linearFlushFrequency = config.getInt(getBasePath() + ".flush-interval-seconds", linearFlushFrequency); + + regionFormatType = EnumRegionFileExtension.fromName(regionFormatTypeName); + if (regionFormatType == EnumRegionFileExtension.UNKNOWN) { + logger.error("Unknown region file type {} ! Falling back to MCA format.", regionFormatTypeName); + regionFormatType = EnumRegionFileExtension.MCA; + } + + if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { + logger.error("Linear region compression level should be between 1 and 22 in config: {}", linearCompressionLevel); + logger.error("Falling back to compression level 1."); + linearCompressionLevel = 1; + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java new file mode 100644 index 00000000..2f24d3e1 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java @@ -0,0 +1,22 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class RemoveChangeNonEditableSignWarning extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName(); + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".remove-change-non-editable-sign-warning", enabled, + config.pickStringRegionBased( + "Enable to prevent console spam.", + "移除修改无法编辑的告示牌时输出的警告." + )); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java new file mode 100644 index 00000000..f7dad48d --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java @@ -0,0 +1,22 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class RemoveSpigotCheckBungee extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-spigot-check-bungee-config"; + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(""" + Enable player enter backend server through proxy + without backend server enabling its bungee mode.""", + """ + 使服务器无需打开 bungee 模式即可让玩家加入后端服务器.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java new file mode 100644 index 00000000..f094806f --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java @@ -0,0 +1,23 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class RemoveVanillaUsernameCheck extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-vanilla-username-check"; + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(""" + Remove Vanilla username check, + allowing all characters as username.""", + """ + 移除原版的用户名验证, + 让所有字符均可作为玩家名.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java new file mode 100644 index 00000000..eb509447 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java @@ -0,0 +1,24 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class SecureSeed extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".secure-seed"; + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Once you enable secure seed, all ores and structures are generated with 1024-bit seed + instead of using 64-bit seed in vanilla, made seed cracker become impossible.""", + """ + 安全种子开启后, 所有矿物与结构都将使用1024位的种子进行生成, 无法被破解."""); + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java new file mode 100644 index 00000000..0234fc0d --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java @@ -0,0 +1,43 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.apache.logging.log4j.Level; +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class SentryDSN extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".sentry"; + } + + public static String sentryDsnConfigPath; + public static String sentryDsn = ""; + public static String logLevel = "WARN"; + public static boolean onlyLogThrown = true; + + @Override + public void onLoaded() { + String sentryEnvironment = System.getenv("SENTRY_DSN"); + String sentryConfig = config.getString(sentryDsnConfigPath = getBasePath() + ".dsn", sentryDsn, config.pickStringRegionBased(""" + Sentry DSN for improved error logging, leave blank to disable, + Obtain from https://sentry.io/""", + """ + Sentry DSN (出现严重错误时将发送至配置的Sentry DSN地址) (留空关闭)""")); + + sentryDsn = sentryEnvironment == null + ? sentryConfig + : sentryEnvironment; + logLevel = config.getString(getBasePath() + ".log-level", logLevel, config.pickStringRegionBased(""" + Logs with a level higher than or equal to this level will be recorded.""", + """ + 大于等于该等级的日志会被记录.""")); + onlyLogThrown = config.getBoolean(getBasePath() + ".only-log-thrown", onlyLogThrown, config.pickStringRegionBased(""" + Only log with a Throwable will be recorded after enabling this.""", + """ + 是否仅记录带有 Throwable 的日志.""")); + + if (sentryDsn != null && !sentryDsn.isBlank()) { + gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel)); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java new file mode 100644 index 00000000..6e20b63f --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java @@ -0,0 +1,20 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ServerBrand extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".rebrand"; + } + + public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); + public static String serverGUIName = "Leaf Console"; + + @Override + public void onLoaded() { + serverModName = config.getString(getBasePath() + ".server-mod-name", serverModName); + serverGUIName = config.getString(getBasePath() + ".server-gui-name", serverGUIName); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java new file mode 100644 index 00000000..66fbab02 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java @@ -0,0 +1,23 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class UnknownCommandMessage extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.MISC.getBaseKeyName() + ".message"; + } + + public static String unknownCommandMessage = ""; + + @Override + public void onLoaded() { + unknownCommandMessage = config.getString(getBasePath() + ".unknown-command", unknownCommandMessage, config.pickStringRegionBased(""" + Unknown command message, using MiniMessage format, set to "default" to use vanilla message, + placeholder: , shows detail of the unknown command information.""", + """ + 发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息. + 变量: , 显示未知命令详细信息.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java new file mode 100644 index 00000000..096969e2 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java @@ -0,0 +1,25 @@ +package org.dreeam.leaf.config.modules.network; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ChatMessageSignature extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.NETWORK.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".chat-message-signature", enabled, config.pickStringRegionBased(""" + Whether or not enable chat message signature, + disable will prevent players to report chat messages. + And also disables the popup when joining a server without + 'secure chat', such as offline-mode servers. + """, + """ + 是否启用聊天签名, 禁用后玩家无法进行聊天举报.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java new file mode 100644 index 00000000..c17e3484 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java @@ -0,0 +1,42 @@ +package org.dreeam.leaf.config.modules.network; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +import java.util.concurrent.ThreadLocalRandom; + +public class ProtocolSupport extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.NETWORK.getBaseKeyName() + ".protocol-support"; + } + + public static boolean jadeProtocol = false; + public static boolean appleskinProtocol = false; + public static int appleskinSyncTickInterval = 20; + public static boolean asteorBarProtocol = false; + public static boolean chatImageProtocol = false; + public static boolean xaeroMapProtocol = false; + public static int xaeroMapServerID = ThreadLocalRandom.current().nextInt(); // Leaf - Faster Random + public static boolean syncmaticaProtocol = false; + public static boolean syncmaticaQuota = false; + public static int syncmaticaQuotaLimit = 40000000; + + @Override + public void onLoaded() { + jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); + appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol); + appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); + asteorBarProtocol = config.getBoolean(getBasePath() + ".asteorbar-protocol", asteorBarProtocol); + chatImageProtocol = config.getBoolean(getBasePath() + ".chatimage-protocol", chatImageProtocol); + xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol); + xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID); + syncmaticaProtocol = config.getBoolean(getBasePath() + ".syncmatica-protocol", syncmaticaProtocol); + syncmaticaQuota = config.getBoolean(getBasePath() + ".syncmatica-quota", syncmaticaQuota); + syncmaticaQuotaLimit = config.getInt(getBasePath() + ".syncmatica-quota-limit", syncmaticaQuotaLimit); + + if (syncmaticaProtocol) { + org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init(); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java new file mode 100644 index 00000000..28dc5e19 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java @@ -0,0 +1,28 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class DontSaveEntity extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".dont-save-entity"; + } + + public static boolean dontSavePrimedTNT = false; + public static boolean dontSaveFallingBlock = false; + + @Override + public void onLoaded() { + dontSavePrimedTNT = config.getBoolean(getBasePath() + ".dont-save-primed-tnt", dontSavePrimedTNT, + config.pickStringRegionBased( + """ + Disable save primed tnt on chunk unloads. + Useful for redstone/technical servers, can prevent machines from being exploded by TNT, + when player disconnected caused by Internet issue.""", + """ + 区块卸载时不保存掉落的方块和激活的 TNT, + 可以避免在玩家掉线时机器被炸毁.""")); + dontSaveFallingBlock = config.getBoolean(getBasePath() + ".dont-save-falling-block", dontSaveFallingBlock); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java new file mode 100644 index 00000000..7e1e06ff --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java @@ -0,0 +1,81 @@ +package org.dreeam.leaf.config.modules.opt; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +import java.util.ArrayList; +import java.util.List; + +public class DynamicActivationofBrain extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".dab"; + } + + public static boolean enabled = true; + public static int startDistance = 12; + public static int startDistanceSquared; + public static int maximumActivationPrio = 20; + public static int activationDistanceMod = 8; + public static boolean dontEnableIfInWater = false; + public static List blackedEntities = new ArrayList<>(); + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Optimizes entity brains when + they're far away from the player""", + """ + 根据距离动态优化生物 AI"""); + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + dontEnableIfInWater = config.getBoolean(getBasePath() + ".dont-enable-if-in-water", dontEnableIfInWater, config.pickStringRegionBased(""" + After enabling this, non-aquatic entities in the water will not be affected by DAB. + This could fix entities suffocate in the water.""", + """ + 启用此项后, 在水中的非水生生物将不会被 DAB 影响. + 可以避免距离玩家较远的生物在水里淹死.""")); + startDistance = config.getInt(getBasePath() + ".start-distance", startDistance, config.pickStringRegionBased(""" + This value determines how far away an entity has to be + from the player to start being effected by DEAR.""", + """ + 生物距离玩家多少格 DAB 开始生效""")); + maximumActivationPrio = config.getInt(getBasePath() + ".max-tick-freq", maximumActivationPrio, config.pickStringRegionBased(""" + This value defines how often in ticks, the furthest entity + will get their pathfinders and behaviors ticked. 20 = 1s""", + """ + 最远处的实体每隔多少刻tick一次""")); + activationDistanceMod = config.getInt(getBasePath() + ".activation-dist-mod", activationDistanceMod, """ + This value defines how much distance modifies an entity's + tick frequency. freq = (distanceToPlayer^2) / (2^value)", + If you want further away entities to tick less often, use 7. + If you want further away entities to tick more often, try 9."""); + blackedEntities = config.getList(getBasePath() + ".blacklisted-entities", blackedEntities, + config.pickStringRegionBased("A list of entities to ignore for activation", + "不会被 DAB 影响的实体列表")); + + startDistanceSquared = startDistance * startDistance; + + for (EntityType entityType : BuiltInRegistries.ENTITY_TYPE) { + entityType.dabEnabled = true; // reset all, before setting the ones to true + } + + final String DEFAULT_PREFIX = ResourceLocation.DEFAULT_NAMESPACE + ResourceLocation.NAMESPACE_SEPARATOR; + + for (String name : blackedEntities) { + // Be compatible with both `minecraft:example` and `example` syntax + // If unknown, show user config value in the logger instead of parsed result + String typeId = name.toLowerCase().startsWith(DEFAULT_PREFIX) ? name : DEFAULT_PREFIX + name; + + EntityType.byString(typeId).ifPresentOrElse(entityType -> + entityType.dabEnabled = false, + () -> LeafConfig.LOGGER.warn("Skip unknown entity {}, in {}", name, getBasePath() + ".blacklisted-entities") + + ); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java new file mode 100644 index 00000000..68ec3573 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class EnableCachedMTBEntityTypeConvert extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".enable-cached-minecraft-to-bukkit-entitytype-convert", enabled, config.pickStringRegionBased(""" + Whether to cache expensive CraftEntityType#minecraftToBukkit call.""", + """ + 是否缓存Minecraft到Bukkit的实体类型转换.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java new file mode 100644 index 00000000..c0fe3e9d --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java @@ -0,0 +1,80 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; + +import java.util.random.RandomGeneratorFactory; + +public class FastRNG extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".faster-random-generator"; + } + + public static boolean enabled = false; + public static boolean enableForWorldgen = false; + public static String randomGenerator = "Xoroshiro128PlusPlus"; + public static boolean warnForSlimeChunk = true; + public static boolean useLegacyForSlimeChunk = false; + + public static boolean worldgenEnabled() { return enabled && enableForWorldgen; } // Helper function + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + Use faster random generator? + Requires a JVM that supports RandomGenerator. + Some JREs don't support this.""", + """ + 是否使用更快的随机生成器? + 需要支持 RandomGenerator 的 JVM. + 一些 JRE 不支持此功能."""); + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + randomGenerator = config.getString(getBasePath() + ".random-generator", randomGenerator, + config.pickStringRegionBased( + """ + Which random generator will be used? + See https://openjdk.org/jeps/356""", + """ + 使用什么种类的随机生成器. + 请参阅 https://openjdk.org/jeps/356""")); + enableForWorldgen = config.getBoolean(getBasePath() + ".enable-for-worldgen", enableForWorldgen, + config.pickStringRegionBased( + """ + Enable faster random generator for world generation. + WARNING: This will affect world generation!!!""", + """ + 是否为世界生成启用更快的随机生成器. + 警告: 此项会影响世界生成!!!""")); + warnForSlimeChunk = config.getBoolean(getBasePath() + ".warn-for-slime-chunk", warnForSlimeChunk, + config.pickStringRegionBased( + "Warn if you are not using legacy random source for slime chunk generation.", + "是否在没有为史莱姆区块使用原版随机生成器的情况下进行警告.")); + useLegacyForSlimeChunk = config.getBoolean(getBasePath() + ".use-legacy-random-for-slime-chunk", useLegacyForSlimeChunk, config.pickStringRegionBased( + """ + Use legacy random source for slime chunk generation, + to follow vanilla behavior.""", + """ + 是否使用原版随机生成器来生成史莱姆区块.""")); + + if (enabled) { + try { + RandomGeneratorFactory.of(randomGenerator); + } catch (Exception e) { + LeafConfig.LOGGER.error("Faster random generator is enabled but {} is not supported by your JVM, " + + "falling back to legacy random source.", randomGenerator); + enabled = false; + } + } + + if (enabled && warnForSlimeChunk) { + LeafConfig.LOGGER.warn("You enabled faster random generator, it will offset location of slime chunk"); + LeafConfig.LOGGER.warn("If your server has slime farms or facilities need vanilla slime chunk,"); + LeafConfig.LOGGER.warn("set performance.faster-random-generator.use-legacy-random-for-slime-chunk " + + "to true to use LegacyRandomSource for slime chunk generation."); + LeafConfig.LOGGER.warn("Set performance.faster-random-generator.warn-for-slime-chunk to false to " + + "disable this warning."); + } + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java new file mode 100644 index 00000000..97a73004 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class FasterStructureGenFutureSequencing extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".faster-structure-gen-future-sequencing", enabled, + config.pickStringRegionBased( + "May cause the inconsistent order of future compose tasks.", + "更快的结构生成任务分段.")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java new file mode 100644 index 00000000..15982984 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ReduceUselessPackets extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".reduce-packets"; + } + + public static boolean reduceUselessEntityMovePackets = false; + + @Override + public void onLoaded() { + reduceUselessEntityMovePackets = config.getBoolean(getBasePath() + ".reduce-entity-move-packets", reduceUselessEntityMovePackets); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java new file mode 100644 index 00000000..f97ca316 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class SkipAIForNonAwareMob extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config().getBoolean(getBasePath() + ".skip-ai-for-non-aware-mob", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java new file mode 100644 index 00000000..61687c05 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class SkipMapItemDataUpdates extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".skip-map-item-data-updates-if-map-does-not-have-craftmaprenderer", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java new file mode 100644 index 00000000..3facc08e --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java @@ -0,0 +1,26 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ThrottleHopperWhenFull extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".throttle-hopper-when-full"; + } + + public static boolean enabled = false; + public static int skipTicks = 0; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased(""" + Throttles the hopper if target container is full.""", + """ + 是否在目标容器已满时阻塞漏斗.""")); + skipTicks = config.getInt(getBasePath() + ".skip-ticks", skipTicks, config.pickStringRegionBased(""" + How many ticks to throttle when the Hopper is throttled.""", + """ + 每次阻塞多少 tick.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java new file mode 100644 index 00000000..785215c0 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java @@ -0,0 +1,23 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ThrottleInactiveGoalSelectorTick extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".inactive-goal-selector-throttle", enabled, config.pickStringRegionBased(""" + Throttles the AI goal selector in entity inactive ticks. + This can improve performance by a few percent, but has minor gameplay implications.""", + """ + 是否在实体不活跃 tick 时阻塞 AI 目标选择器. + 有助于提升性能, 但对游戏有轻微影响.""")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java new file mode 100644 index 00000000..1c0d34c1 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class TileEntitySnapshotCreation extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".create-snapshot-on-retrieving-blockstate", enabled); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java new file mode 100644 index 00000000..df1acd2f --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4BukkitScheduler.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class VT4BukkitScheduler extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-async-scheduler", enabled, + config.pickStringRegionBased( + "Use the new Virtual Thread introduced in JDK 21 for CraftAsyncScheduler.", + "是否为异步任务调度器使用虚拟线程.")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ChatExecutor.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ChatExecutor.java new file mode 100644 index 00000000..964c2033 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ChatExecutor.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class VT4ChatExecutor extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-async-chat-executor", enabled, + config.pickStringRegionBased( + "Use the new Virtual Thread introduced in JDK 21 for Async Chat Executor.", + "是否为异步聊天线程使用虚拟线程.")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java new file mode 100644 index 00000000..751549c6 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class VT4UserAuthenticator extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-user-authenticator", enabled, + config.pickStringRegionBased( + "Use the new Virtual Thread introduced in JDK 21 for User Authenticator.", + "是否为用户验证器使用虚拟线程.")); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/misc/LagCompensation.java b/Leaf-Server/src/main/java/org/dreeam/leaf/misc/LagCompensation.java new file mode 100644 index 00000000..6ec73caf --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/misc/LagCompensation.java @@ -0,0 +1,114 @@ +package org.dreeam.leaf.misc; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; + +import java.util.Collections; +import java.util.List; + +public class LagCompensation { + + public static float tt20(float ticks, boolean limitZero) { + float newTicks = (float) rawTT20(ticks); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static int tt20(int ticks, boolean limitZero) { + int newTicks = (int) Math.ceil(rawTT20(ticks)); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double tt20(double ticks, boolean limitZero) { + double newTicks = rawTT20(ticks); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double rawTT20(double ticks) { + return ticks == 0 ? 0 : ticks * TPSCalculator.getMostAccurateTPS() / TPSCalculator.MAX_TPS; + } + + public static class TPSCalculator { + public static Long lastTick; + public static Long currentTick; + private static double allMissedTicks = 0; + private static final List tpsHistory = Collections.synchronizedList(new DoubleArrayList()); + private static final int historyLimit = 40; + + public static final int MAX_TPS = 20; + public static final int FULL_TICK = 50; + + private TPSCalculator() {} + + public static void onTick() { + if (currentTick != null) { + lastTick = currentTick; + } + + currentTick = System.currentTimeMillis(); + addToHistory(getTPS()); + clearMissedTicks(); + missedTick(); + } + + private static void addToHistory(double tps) { + if (tpsHistory.size() >= historyLimit) { + tpsHistory.removeFirst(); + } + + tpsHistory.add(tps); + } + + public static long getMSPT() { + return currentTick - lastTick; + } + + public static double getAverageTPS() { + double sum = 0.0; + for (double value : tpsHistory) { + sum += value; + } + return tpsHistory.isEmpty() ? 0.1 : sum / tpsHistory.size(); + } + + public static double getTPS() { + if (lastTick == null) return -1; + if (getMSPT() <= 0) return 0.1; + + double tps = 1000 / (double) getMSPT(); + return tps > MAX_TPS ? MAX_TPS : tps; + } + + public static void missedTick() { + if (lastTick == null) return; + + long mspt = getMSPT() <= 0 ? 50 : getMSPT(); + double missedTicks = (mspt / (double) FULL_TICK) - 1; + allMissedTicks += missedTicks <= 0 ? 0 : missedTicks; + } + + public static double getMostAccurateTPS() { + return Math.min(getTPS(), getAverageTPS()); + } + + public double getAllMissedTicks() { + return allMissedTicks; + } + + public static int applicableMissedTicks() { + return (int) Math.floor(allMissedTicks); + } + + public static void clearMissedTicks() { + allMissedTicks -= applicableMissedTicks(); + } + + public void resetMissedTicks() { + allMissedTicks = 0; + } + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java new file mode 100644 index 00000000..8c082074 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java @@ -0,0 +1,282 @@ +package org.dreeam.leaf.util; + +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type + * contained by this list must use reference-equality semantics. + */ +@SuppressWarnings("SuspiciousMethodCalls") +public class HashedReferenceList implements List { + private final ReferenceArrayList list; + private final Reference2IntOpenHashMap counter; + + public HashedReferenceList(List list) { + this.list = new ReferenceArrayList<>(); + this.list.addAll(list); + + this.counter = new Reference2IntOpenHashMap<>(); + this.counter.defaultReturnValue(0); + + for (T obj : this.list) { + this.counter.addTo(obj, 1); + } + } + + @Override + public int size() { + return this.list.size(); + } + + @Override + public boolean isEmpty() { + return this.list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.counter.containsKey(o); + } + + @Override + public Iterator iterator() { + return this.listIterator(); + } + + @Override + public Object[] toArray() { + return this.list.toArray(); + } + + @SuppressWarnings("SuspiciousToArrayCall") + @Override + public T1[] toArray(T1 @NotNull [] a) { + return this.list.toArray(a); + } + + @Override + public boolean add(T t) { + this.trackReferenceAdded(t); + + return this.list.add(t); + } + + @Override + public boolean remove(Object o) { + this.trackReferenceRemoved(o); + + return this.list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + for (Object obj : c) { + if (!this.counter.containsKey(obj)) { + return false; + } + } + + return true; + } + + @Override + public boolean addAll(Collection c) { + for (T obj : c) { + this.trackReferenceAdded(obj); + } + + return this.list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + for (T obj : c) { + this.trackReferenceAdded(obj); + } + + return this.list.addAll(index, c); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + if (this.size() >= 2 && c.size() > 4 && c instanceof List) { + //HashReferenceList uses reference equality, so using ReferenceOpenHashSet is fine + c = new ReferenceOpenHashSet<>(c); + } + this.counter.keySet().removeAll(c); + return this.list.removeAll(c); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + this.counter.keySet().retainAll(c); + return this.list.retainAll(c); + } + + @Override + public void clear() { + this.counter.clear(); + this.list.clear(); + } + + @Override + public T get(int index) { + return this.list.get(index); + } + + @Override + public T set(int index, T element) { + T prev = this.list.set(index, element); + + if (prev != element) { + if (prev != null) { + this.trackReferenceRemoved(prev); + } + + this.trackReferenceAdded(element); + } + + return prev; + } + + @Override + public void add(int index, T element) { + this.trackReferenceAdded(element); + + this.list.add(index, element); + } + + @Override + public T remove(int index) { + T prev = this.list.remove(index); + + if (prev != null) { + this.trackReferenceRemoved(prev); + } + + return prev; + } + + @Override + public int indexOf(Object o) { + return this.list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.listIterator(0); + } + + @Override + public ListIterator listIterator(int index) { + return new ListIterator<>() { + private final ListIterator inner = HashedReferenceList.this.list.listIterator(index); + + @Override + public boolean hasNext() { + return this.inner.hasNext(); + } + + @Override + public T next() { + return this.inner.next(); + } + + @Override + public boolean hasPrevious() { + return this.inner.hasPrevious(); + } + + @Override + public T previous() { + return this.inner.previous(); + } + + @Override + public int nextIndex() { + return this.inner.nextIndex(); + } + + @Override + public int previousIndex() { + return this.inner.previousIndex(); + } + + @Override + public void remove() { + int last = this.previousIndex(); + + if (last == -1) { + throw new NoSuchElementException(); + } + + T prev = HashedReferenceList.this.get(last); + + if (prev != null) { + HashedReferenceList.this.trackReferenceRemoved(prev); + } + + this.inner.remove(); + } + + @Override + public void set(T t) { + int last = this.previousIndex(); + + if (last == -1) { + throw new NoSuchElementException(); + } + + T prev = HashedReferenceList.this.get(last); + + if (prev != t) { + if (prev != null) { + HashedReferenceList.this.trackReferenceRemoved(prev); + } + + HashedReferenceList.this.trackReferenceAdded(t); + } + + this.inner.remove(); + } + + @Override + public void add(T t) { + HashedReferenceList.this.trackReferenceAdded(t); + + this.inner.add(t); + } + }; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.list.subList(fromIndex, toIndex); + } + + private void trackReferenceAdded(T t) { + this.counter.addTo(t, 1); + } + + @SuppressWarnings("unchecked") + private void trackReferenceRemoved(Object o) { + if (this.counter.addTo((T) o, -1) <= 1) { + this.counter.removeInt(o); + } + } + +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java new file mode 100644 index 00000000..3683c61e --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java @@ -0,0 +1,36 @@ +package org.dreeam.leaf.util.biome; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; + +import java.util.function.Function; +import java.util.function.Supplier; + +public class PositionalBiomeGetter implements Supplier> { + private final Function> biomeGetter; + private final BlockPos.MutableBlockPos pos; + private int nextX, nextY, nextZ; + private volatile Holder curBiome; + + public PositionalBiomeGetter(Function> biomeGetter, BlockPos.MutableBlockPos pos) { + this.biomeGetter = biomeGetter; + this.pos = pos; + } + + public void update(int nextX, int nextY, int nextZ) { + this.nextX = nextX; + this.nextY = nextY; + this.nextZ = nextZ; + this.curBiome = null; + } + + @Override + public Holder get() { + Holder biome = curBiome; + if (biome == null) { + curBiome = biome = biomeGetter.apply(pos.set(nextX, nextY, nextZ)); + } + return biome; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java new file mode 100644 index 00000000..5a8abdff --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.util.cache; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import java.util.BitSet; +import java.util.function.IntFunction; + +public class CachedOrNewBitsGetter { + private static final IntFunction bitSetConstructor = BitSet::new; + + public static ThreadLocal> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new); + + private CachedOrNewBitsGetter() { + } + + public static BitSet getCachedOrNewBitSet(int bits) { + final BitSet bitSet = BITSETS.get().computeIfAbsent(bits, bitSetConstructor); + bitSet.clear(); + return bitSet; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java new file mode 100644 index 00000000..9ae6b0a5 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java @@ -0,0 +1,71 @@ +package org.dreeam.leaf.util.cache; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import net.minecraft.core.BlockPos; + +/** + * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 + */ +public class IterateOutwardsCache { + //POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache + public static final BlockPos POS_ZERO = new BlockPos(0,0,0); + + + private final ConcurrentHashMap table; + private final int capacity; + private final Random random; + + public IterateOutwardsCache(int capacity) { + this.capacity = capacity; + this.table = new ConcurrentHashMap<>(31); + this.random = new Random(); + } + + private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { + // Add all positions to the cached list + for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) { + entry.add(pos.asLong()); + } + } + + public LongList getOrCompute(int xRange, int yRange, int zRange) { + long key = BlockPos.asLong(xRange, yRange, zRange); + + LongArrayList entry = this.table.get(key); + if (entry != null) { + return entry; + } + + // Cache miss: compute and store + entry = new LongArrayList(128); + + this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange); + + //decrease the array size, as of now it won't be modified anymore anyways + entry.trim(); + + //this might overwrite an entry as the same entry could have been computed and added during this thread's computation + //we do not use computeIfAbsent, as it can delay other threads for too long + Object previousEntry = this.table.put(key, entry); + + + if (previousEntry == null && this.table.size() > this.capacity) { + //prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded + final Iterator iterator = this.table.keySet().iterator(); + //prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting + for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) { + Long key2 = iterator.next(); + //random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers + if (this.random.nextInt(8) == 0 && key2 != key) { + iterator.remove(); + } + } + } + + return entry; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java new file mode 100644 index 00000000..bdc1e6a9 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java @@ -0,0 +1,46 @@ +package org.dreeam.leaf.util.cache; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Iterator; +import net.minecraft.core.BlockPos; + +/** + * @author 2No2Name + */ +public class LongList2BlockPosMutableIterable implements Iterable { + + private final LongList positions; + private final int xOffset, yOffset, zOffset; + + public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) { + this.xOffset = offset.getX(); + this.yOffset = offset.getY(); + this.zOffset = offset.getZ(); + this.positions = posList; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator(); + private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public net.minecraft.core.BlockPos next() { + long nextPos = this.it.nextLong(); + return this.pos.set( + LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos), + LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos), + LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos)); + } + }; + } + +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/item/ItemStackObfuscator.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/item/ItemStackObfuscator.java new file mode 100644 index 00000000..55cc2528 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/item/ItemStackObfuscator.java @@ -0,0 +1,29 @@ +package org.dreeam.leaf.util.item; + +import net.minecraft.core.component.DataComponentType; +import net.minecraft.world.item.ItemStack; +import org.dreeam.leaf.config.modules.misc.HiddenItemComponents; + +import java.util.List; + +public class ItemStackObfuscator { + + public static ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) { + if (itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty()) return itemStack; + + final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; + + // Get the types which need to hide + List> hiddenTypes = HiddenItemComponents.hiddenItemComponentTypes; + + if (hiddenTypes.isEmpty()) return copy; + + // Remove specified types + for (DataComponentType type : hiddenTypes) { + // Only remove, no others + copy.remove(type); + } + + return copy; + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java new file mode 100644 index 00000000..72538e77 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java @@ -0,0 +1,58 @@ +package org.dreeam.leaf.util.map; + +import com.github.benmanes.caffeine.cache.Interner; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; + +import java.util.Map; +import java.util.function.Function; + +/** + * Backed by an {@link Object2ObjectOpenHashMap}, with string keys interned to save memory. + */ +public class StringCanonizingOpenHashMap extends Object2ObjectOpenHashMap { + private static final Interner KEY_INTERNER = Interner.newWeakInterner(); + + private static String intern(String key) { + return key != null ? KEY_INTERNER.intern(key) : null; + } + + public StringCanonizingOpenHashMap() { + super(); + } + + public StringCanonizingOpenHashMap(int expectedSize) { + super(expectedSize); + } + + public StringCanonizingOpenHashMap(int expectedSize, float loadFactor) { + super(expectedSize, loadFactor); + } + + @Override + public T put(String key, T value) { + return super.put(intern(key), value); + } + + @Override + public void putAll(Map m) { + if (m.isEmpty()) return; + Map tmp = new Object2ObjectOpenHashMap<>(m.size()); + m.forEach((k, v) -> tmp.put(intern(k), v)); + super.putAll(tmp); + } + + private void putWithoutInterning(String key, T value) { + super.put(key, value); + } + + public static StringCanonizingOpenHashMap deepCopy(StringCanonizingOpenHashMap incomingMap, Function deepCopier) { + StringCanonizingOpenHashMap newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f); + ObjectIterator> iterator = incomingMap.object2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + newMap.putWithoutInterning(entry.getKey(), deepCopier.apply(entry.getValue())); + } + return newMap; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java new file mode 100644 index 00000000..ef19bc09 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java @@ -0,0 +1,90 @@ +package org.dreeam.leaf.util.math; + +import net.minecraft.util.Mth; + +/** + * A replacement for the sine angle lookup table used in {@link Mth}, both reducing the size of LUT and improving + * the access patterns for common paired sin/cos operations. + *

+ * sin(-x) = -sin(x) + * ... to eliminate negative angles from the LUT. + *

+ * sin(x) = sin(pi/2 - x) + * ... to eliminate supplementary angles from the LUT. + *

+ * Using these identities allows us to reduce the LUT from 64K entries (256 KB) to just 16K entries (64 KB), enabling + * it to better fit into the CPU's caches at the expense of some cycles on the fast path. The implementation has been + * tightly optimized to avoid branching where possible and to use very quick integer operations. + *

+ * Generally speaking, reducing the size of a lookup table is always a good optimization, but since we need to spend + * extra CPU cycles trying to maintain parity with vanilla, there is the potential risk that this implementation ends + * up being slower than vanilla when the lookup table is able to be kept in cache memory. + *

+ * Unlike other "fast math" implementations, the values returned by this class are *bit-for-bit identical* with those + * from {@link Mth}. Validation is performed during runtime to ensure that the table is correct. + * + * @author coderbot16 Author of the original (and very clever) implementation in Rust: + * https://gitlab.com/coderbot16/i73/-/tree/master/i73-trig/src + * @author jellysquid3 Additional optimizations, port to Java + */ +public class CompactSineLUT { + private static final int[] SINE_TABLE_INT = new int[16384 + 1]; + private static final float SINE_TABLE_MIDPOINT; + + static { + final float[] SINE_TABLE = Mth.SIN; + // Copy the sine table, covering to raw int bits + for (int i = 0; i < SINE_TABLE_INT.length; i++) { + SINE_TABLE_INT[i] = Float.floatToRawIntBits(SINE_TABLE[i]); + } + + SINE_TABLE_MIDPOINT = SINE_TABLE[SINE_TABLE.length / 2]; + + // Test that the lookup table is correct during runtime + for (int i = 0; i < SINE_TABLE.length; i++) { + float expected = SINE_TABLE[i]; + float value = lookup(i); + + if (expected != value) { + throw new IllegalArgumentException(String.format("LUT error at index %d (expected: %s, found: %s)", i, expected, value)); + } + } + } + + // [VanillaCopy] MathHelper#sin(float) + public static float sin(float f) { + return lookup((int) (f * 10430.378f) & 0xFFFF); + } + + // [VanillaCopy] MathHelper#cos(float) + public static float cos(float f) { + return lookup((int) (f * 10430.378f + 16384.0f) & 0xFFFF); + } + + private static float lookup(int index) { + // A special case... Is there some way to eliminate this? + if (index == 32768) { + return SINE_TABLE_MIDPOINT; + } + + // Trigonometric identity: sin(-x) = -sin(x) + // Given a domain of 0 <= x <= 2*pi, just negate the value if x > pi. + // This allows the sin table size to be halved. + int neg = (index & 0x8000) << 16; + + // All bits set if (pi/2 <= x), none set otherwise + // Extracts the 15th bit from 'half' + int mask = (index << 17) >> 31; + + // Trigonometric identity: sin(x) = sin(pi/2 - x) + int pos = (0x8001 & mask) + (index ^ mask); + + // Wrap the position in the table. Moving this down to immediately before the array access + // seems to help the Hotspot compiler optimize the bit math better. + pos &= 0x7fff; + + // Fetch the corresponding value from the LUT and invert the sign bit as needed + // This directly manipulate the sign bit on the float bits to simplify logic + return Float.intBitsToFloat(SINE_TABLE_INT[pos] ^ neg); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java b/Leaf-Server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java new file mode 100644 index 00000000..77fe5ab8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java @@ -0,0 +1,127 @@ +package org.dreeam.leaf.util.math.random; + +import com.google.common.annotations.VisibleForTesting; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.BitRandomSource; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; +import org.dreeam.leaf.config.modules.opt.FastRNG; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.random.RandomGenerator; +import java.util.random.RandomGeneratorFactory; + + +public class FasterRandomSource implements BitRandomSource { + private static final int INT_BITS = 48; + private static final long SEED_MASK = 0xFFFFFFFFFFFFL; + private static final long MULTIPLIER = 25214903917L; + private static final long INCREMENT = 11L; + private static final RandomGeneratorFactory RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of(FastRNG.randomGenerator); + private static final boolean isSplittableGenerator = RANDOM_GENERATOR_FACTORY.isSplittable(); + private long seed; + private RandomGenerator randomGenerator; + public static final FasterRandomSource SHARED_INSTANCE = new FasterRandomSource(ThreadLocalRandom.current().nextLong()); + + public FasterRandomSource(long seed) { + this.seed = seed; + this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed); + } + + private FasterRandomSource(long seed, RandomGenerator.SplittableGenerator randomGenerator) { + this.seed = seed; + this.randomGenerator = randomGenerator; + } + + @Override + public final RandomSource fork() { + if (isSplittableGenerator) { + return new FasterRandomSource(seed, ((RandomGenerator.SplittableGenerator) this.randomGenerator).split()); + } + return new FasterRandomSource(this.nextLong()); + } + + @Override + public final PositionalRandomFactory forkPositional() { + return new FasterRandomSourcePositionalRandomFactory(this.seed); + } + + @Override + public final void setSeed(long seed) { + this.seed = seed; + this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed); + } + + @Override + public final int next(int bits) { + // >>> instead of Mojang's >> fixes MC-239059 + return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> INT_BITS - bits); + } + + public static class FasterRandomSourcePositionalRandomFactory implements PositionalRandomFactory { + private final long seed; + + public FasterRandomSourcePositionalRandomFactory(long seed) { + this.seed = seed; + } + + @Override + public RandomSource at(int x, int y, int z) { + long l = Mth.getSeed(x, y, z); + long m = l ^ this.seed; + return new FasterRandomSource(m); + } + + @Override + public RandomSource fromHashOf(String seed) { + int i = seed.hashCode(); + return new FasterRandomSource((long)i ^ this.seed); + } + + @Override + public RandomSource fromSeed(long seed) { + return new FasterRandomSource(seed); + } + + @VisibleForTesting + @Override + public void parityConfigString(StringBuilder info) { + info.append("FasterRandomSourcePositionalRandomFactory{").append(this.seed).append("}"); + } + } + + @Override + public final int nextInt() { + return randomGenerator.nextInt(); + } + + @Override + public final int nextInt(int bound) { + return randomGenerator.nextInt(bound); + } + + @Override + public final long nextLong() { + return randomGenerator.nextLong(); + } + + @Override + public final boolean nextBoolean() { + return randomGenerator.nextBoolean(); + } + + @Override + public final float nextFloat() { + return randomGenerator.nextFloat(); + } + + @Override + public final double nextDouble() { + return randomGenerator.nextDouble(); + } + + @Override + public final double nextGaussian() { + return randomGenerator.nextGaussian(); + } +} diff --git a/Leaf-Server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java b/Leaf-Server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java new file mode 100644 index 00000000..a02577f0 --- /dev/null +++ b/Leaf-Server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf.version; + +import org.galemc.gale.version.AbstractPaperVersionFetcher; + +public class LeafVersionFetcher extends AbstractPaperVersionFetcher { + + public LeafVersionFetcher() { + super( + "ver/1.21.3", + "https://github.com/Winds-Studio/Leaf", + "Winds-Studio", + "Leaf", + "Winds-Studio", + "Leaf" + ); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/LeavesLogger.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/LeavesLogger.java new file mode 100644 index 00000000..bc45935c --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/LeavesLogger.java @@ -0,0 +1,24 @@ +package org.leavesmc.leaves; + +import org.bukkit.Bukkit; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class LeavesLogger extends Logger { + public static final LeavesLogger LOGGER = new LeavesLogger(); + + private LeavesLogger() { + super("Leaves", null); + setParent(Bukkit.getLogger()); + setLevel(Level.ALL); + } + + public void severe(String msg, Exception exception) { + this.log(Level.SEVERE, msg, exception); + } + + public void warning(String msg, Exception exception) { + this.log(Level.WARNING, msg, exception); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java new file mode 100644 index 00000000..5bd34353 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java @@ -0,0 +1,38 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.datafixers.DataFixer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stat; +import net.minecraft.world.entity.player.Player; + +import java.io.File; + +public class BotStatsCounter extends ServerStatsCounter { + + private static final File UNKOWN_FILE = new File("BOT_STATS_REMOVE_THIS"); + + public BotStatsCounter(MinecraftServer server) { + super(server, UNKOWN_FILE); + } + + @Override + public void save() { + + } + + @Override + public void setValue(Player player, Stat stat, int value) { + + } + + @Override + public void parseLocal(DataFixer dataFixer, String json) { + + } + + @Override + public int getValue(Stat stat) { + return 0; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java new file mode 100644 index 00000000..fed2005c --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java @@ -0,0 +1,73 @@ +package org.leavesmc.leaves.entity; + +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.replay.ServerPhotographer; + +import java.io.File; + +public class CraftPhotographer extends CraftPlayer implements Photographer { + + public CraftPhotographer(CraftServer server, ServerPhotographer entity) { + super(server, entity); + } + + @Override + public void stopRecording() { + this.stopRecording(true); + } + + @Override + public void stopRecording(boolean async) { + this.stopRecording(async, true); + } + + @Override + public void stopRecording(boolean async, boolean save) { + this.getHandle().remove(async, save); + } + + @Override + public void pauseRecording() { + this.getHandle().pauseRecording(); + } + + @Override + public void resumeRecording() { + this.getHandle().resumeRecording(); + } + + @Override + public void setRecordFile(@NotNull File file) { + this.getHandle().setSaveFile(file); + } + + @Override + public void setFollowPlayer(@Nullable Player player) { + ServerPlayer serverPlayer = player != null ? ((CraftPlayer) player).getHandle() : null; + this.getHandle().setFollowPlayer(serverPlayer); + } + + @Override + public @NotNull String getId() { + return this.getHandle().createState.id; + } + + @Override + public ServerPhotographer getHandle() { + return (ServerPhotographer) entity; + } + + public void setHandle(final ServerPhotographer entity) { + super.setHandle(entity); + } + + @Override + public String toString() { + return "CraftPhotographer{" + "name=" + getName() + '}'; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java new file mode 100644 index 00000000..e87d1e72 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java @@ -0,0 +1,82 @@ +package org.leavesmc.leaves.entity; + +import com.google.common.collect.Lists; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.replay.BukkitRecorderOption; +import org.leavesmc.leaves.replay.RecorderOption; +import org.leavesmc.leaves.replay.ServerPhotographer; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +public class CraftPhotographerManager implements PhotographerManager { + + private final Collection photographerViews = Collections.unmodifiableList(Lists.transform(ServerPhotographer.getPhotographers(), ServerPhotographer::getBukkitPlayer)); + + @Override + public @Nullable Photographer getPhotographer(@NotNull UUID uuid) { + ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid); + if (photographer != null) { + return photographer.getBukkitPlayer(); + } + return null; + } + + @Override + public @Nullable Photographer getPhotographer(@NotNull String id) { + ServerPhotographer photographer = ServerPhotographer.getPhotographer(id); + if (photographer != null) { + return photographer.getBukkitPlayer(); + } + return null; + } + + @Override + public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location) { + ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createDefaultOption()).createSync(); + if (photographer != null) { + return photographer.getBukkitPlayer(); + } + return null; + } + + @Override + public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location, @NotNull BukkitRecorderOption recorderOption) { + ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createFromBukkit(recorderOption)).createSync(); + if (photographer != null) { + return photographer.getBukkitPlayer(); + } + return null; + } + + @Override + public void removePhotographer(@NotNull String id) { + ServerPhotographer photographer = ServerPhotographer.getPhotographer(id); + if (photographer != null) { + photographer.remove(true); + } + } + + @Override + public void removePhotographer(@NotNull UUID uuid) { + ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid); + if (photographer != null) { + photographer.remove(true); + } + } + + @Override + public void removeAllPhotographers() { + for (ServerPhotographer photographer : ServerPhotographer.getPhotographers()) { + photographer.remove(true); + } + } + + @Override + public Collection getPhotographers() { + return photographerViews; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java new file mode 100644 index 00000000..1e5d1469 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java @@ -0,0 +1,124 @@ +package org.leavesmc.leaves.protocol; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.food.FoodData; +import net.minecraft.world.level.GameRules; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@LeavesProtocol(namespace = "appleskin") +public class AppleSkinProtocol { + + public static final String PROTOCOL_ID = "appleskin"; + + private static final ResourceLocation SATURATION_KEY = id("saturation"); + private static final ResourceLocation EXHAUSTION_KEY = id("exhaustion"); + private static final ResourceLocation NATURAL_REGENERATION_KEY = id("natural_regeneration"); + + private static final float MINIMUM_EXHAUSTION_CHANGE_THRESHOLD = 0.01F; + + private static final Map previousSaturationLevels = new HashMap<>(); + private static final Map previousExhaustionLevels = new HashMap<>(); + private static final Map previousNaturalRegeneration = new HashMap<>(); + + private static final Map> subscribedChannels = new HashMap<>(); + + public static boolean shouldEnable() { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol; + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation id(String path) { + return new ResourceLocation(PROTOCOL_ID, path); + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { + resetPlayerData(player); + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { + subscribedChannels.remove(player); + resetPlayerData(player); + } + + @ProtocolHandler.MinecraftRegister(ignoreId = true) + public static void onPlayerSubscribed(@NotNull ServerPlayer player, String channel) { + if (org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { + subscribedChannels.computeIfAbsent(player, k -> new HashSet<>()).add(channel); + } + } + + @ProtocolHandler.Ticker + public static void tick() { + if (MinecraftServer.getServer().getTickCount() % org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinSyncTickInterval != 0) { + return; + } + + for (Map.Entry> entry : subscribedChannels.entrySet()) { + ServerPlayer player = entry.getKey(); + FoodData data = player.getFoodData(); + + for (String channel : entry.getValue()) { + switch (channel) { + case "saturation" -> { + float saturation = data.getSaturationLevel(); + Float previousSaturation = previousSaturationLevels.get(player); + if (previousSaturation == null || saturation != previousSaturation) { + ProtocolUtils.sendPayloadPacket(player, SATURATION_KEY, buf -> buf.writeFloat(saturation)); + previousSaturationLevels.put(player, saturation); + } + } + + case "exhaustion" -> { + float exhaustion = data.exhaustionLevel; + Float previousExhaustion = previousExhaustionLevels.get(player); + if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= MINIMUM_EXHAUSTION_CHANGE_THRESHOLD) { + ProtocolUtils.sendPayloadPacket(player, EXHAUSTION_KEY, buf -> buf.writeFloat(exhaustion)); + previousExhaustionLevels.put(player, exhaustion); + } + } + + case "natural_regeneration" -> { + boolean regeneration = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION); + Boolean previousRegeneration = previousNaturalRegeneration.get(player); + if (previousRegeneration == null || regeneration != previousRegeneration) { + ProtocolUtils.sendPayloadPacket(player, NATURAL_REGENERATION_KEY, buf -> buf.writeBoolean(regeneration)); + previousNaturalRegeneration.put(player, regeneration); + } + } + } + } + } + } + + @ProtocolHandler.ReloadServer + public static void onServerReload() { + if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { + disableAllPlayer(); + } + } + + public static void disableAllPlayer() { + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + onPlayerLoggedOut(player); + } + } + + private static void resetPlayerData(@NotNull ServerPlayer player) { + previousExhaustionLevels.remove(player); + previousSaturationLevels.remove(player); + previousNaturalRegeneration.remove(player); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java new file mode 100644 index 00000000..1f22ebe7 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java @@ -0,0 +1,102 @@ +package org.leavesmc.leaves.protocol; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.food.FoodData; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@LeavesProtocol(namespace = "asteorbar") +public class AsteorBarProtocol { + + public static final String PROTOCOL_ID = "asteorbar"; + + private static final ResourceLocation NETWORK_KEY = id("network"); + + private static final Map previousSaturationLevels = new HashMap<>(); + private static final Map previousExhaustionLevels = new HashMap<>(); + + private static final float THRESHOLD = 0.01F; + + private static final Set players = new HashSet<>(); + + public static boolean shouldEnable() { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol; + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(PROTOCOL_ID, path); + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { + resetPlayerData(player); + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { + players.remove(player); + resetPlayerData(player); + } + + @ProtocolHandler.MinecraftRegister(ignoreId = true) + public static void onPlayerSubscribed(@NotNull ServerPlayer player) { + players.add(player); + } + + @ProtocolHandler.Ticker + public static void tick() { + for (ServerPlayer player : players) { + FoodData data = player.getFoodData(); + + float saturation = data.getSaturationLevel(); + Float previousSaturation = previousSaturationLevels.get(player.getUUID()); + if (previousSaturation == null || saturation != previousSaturation) { + ProtocolUtils.sendPayloadPacket(player, NETWORK_KEY, buf -> { + buf.writeByte(1); + buf.writeFloat(saturation); + }); + previousSaturationLevels.put(player.getUUID(), saturation); + } + + float exhaustion = data.exhaustionLevel; + Float previousExhaustion = previousExhaustionLevels.get(player.getUUID()); + if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= THRESHOLD) { + ProtocolUtils.sendPayloadPacket(player, NETWORK_KEY, buf -> { + buf.writeByte(0); + buf.writeFloat(exhaustion); + }); + previousExhaustionLevels.put(player.getUUID(), exhaustion); + } + } + } + + @ProtocolHandler.ReloadServer + public static void onServerReload() { + if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol) { + disableAllPlayer(); + } + } + + public static void disableAllPlayer() { + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + onPlayerLoggedOut(player); + } + } + + private static void resetPlayerData(@NotNull ServerPlayer player) { + previousExhaustionLevels.remove(player.getUUID()); + previousSaturationLevels.remove(player.getUUID()); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java new file mode 100644 index 00000000..364922e9 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java @@ -0,0 +1,147 @@ +package org.leavesmc.leaves.protocol; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.dreeam.leaf.config.modules.network.ProtocolSupport; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.chatimage.ChatImageIndex; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.LeavesProtocolManager; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@LeavesProtocol(namespace = "chatimage") +public class ChatImageProtocol { + + public static final String PROTOCOL_ID = "chatimage"; + private static final Map> SERVER_BLOCK_CACHE = new HashMap<>(); + private static final Map FILE_COUNT_MAP = new HashMap<>(); + private static final Map> USER_CACHE_MAP = new HashMap<>(); + public static int MAX_STRING = 532767; + private static final Gson gson = new Gson(); + + public static boolean shouldEnable() { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.chatImageProtocol; + } + + public record FileInfoChannelPacket( + String message) implements LeavesCustomPayload { + private static final ResourceLocation FILE_INFO = ChatImageProtocol.id("file_info"); + + @New + public FileInfoChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { + this(buffer.readUtf(MAX_STRING)); + } + + @Override + public void write(final FriendlyByteBuf buffer) { + buffer.writeUtf(message(), MAX_STRING); + } + + @Override + public @NotNull ResourceLocation id() { + return FILE_INFO; + } + } + + public record DownloadFileChannelPacket( + String message) implements LeavesCustomPayload { + private static final ResourceLocation DOWNLOAD_FILE_CHANNEL = ChatImageProtocol.id("download_file_channel"); + + @New + public DownloadFileChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { + this(buffer.readUtf(MAX_STRING)); + } + + @Override + public void write(final FriendlyByteBuf buffer) { + buffer.writeUtf(message(), MAX_STRING); + } + + @Override + public @NotNull ResourceLocation id() { + return DOWNLOAD_FILE_CHANNEL; + } + + } + + public record FileChannelPacket( + String message) implements LeavesCustomPayload { + private static final ResourceLocation FILE_CHANNEL = ChatImageProtocol.id("file_channel"); + + @New + public FileChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { + this(buffer.readUtf(MAX_STRING)); + } + + @Override + public void write(final FriendlyByteBuf buffer) { + buffer.writeUtf(message(), MAX_STRING); + } + + @Override + public @NotNull ResourceLocation id() { + return FILE_CHANNEL; + } + + } + + @ProtocolHandler.PayloadReceiver(payload = FileChannelPacket.class, payloadId = "file_channel") + public static void serverFileChannelReceived(ServerPlayer player, String res) { + ChatImageIndex title = gson.fromJson(res, ChatImageIndex.class); + HashMap blocks = SERVER_BLOCK_CACHE.containsKey(title.url) ? SERVER_BLOCK_CACHE.get(title.url) : new HashMap<>(); + blocks.put(title.index, res); + SERVER_BLOCK_CACHE.put(title.url, blocks); + FILE_COUNT_MAP.put(title.url, title.total); + if (title.total == blocks.size()) { + if (USER_CACHE_MAP.containsKey(title.url)) { + List names = USER_CACHE_MAP.get(title.url); + for (String uuid : names) { + ServerPlayer serverPlayer = player.server.getPlayerList().getPlayer(UUID.fromString(uuid)); + if (serverPlayer != null) { + sendToPlayer(new FileInfoChannelPacket("true->" + title.url), serverPlayer); + } + } + USER_CACHE_MAP.put(title.url, Lists.newArrayList()); + } + } + } + + @ProtocolHandler.PayloadReceiver(payload = FileInfoChannelPacket.class, payloadId = "file_info") + public static void serverFileInfoChannelReceived(ServerPlayer player, String url) { + if (SERVER_BLOCK_CACHE.containsKey(url) && FILE_COUNT_MAP.containsKey(url)) { + HashMap list = SERVER_BLOCK_CACHE.get(url); + Integer total = FILE_COUNT_MAP.get(url); + if (total == list.size()) { + for (Map.Entry entry : list.entrySet()) { + sendToPlayer(new DownloadFileChannelPacket(entry.getValue()), player); + } + return; + } + } + sendToPlayer(new FileInfoChannelPacket("null->" + url), player); + List names = USER_CACHE_MAP.containsKey(url) ? USER_CACHE_MAP.get(url) : Lists.newArrayList(); + names.add(player.getStringUUID()); + USER_CACHE_MAP.put(url, names); + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(PROTOCOL_ID, path); + } + + public static void sendToPlayer(CustomPacketPayload payload, ServerPlayer player) { + player.connection.send((Packet) payload); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java new file mode 100644 index 00000000..5ef19098 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java @@ -0,0 +1,45 @@ +package org.leavesmc.leaves.protocol; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; + +@LeavesProtocol(namespace = {"xaerominimap", "xaeroworldmap"}) +public class XaeroMapProtocol { + + public static final String PROTOCOL_ID_MINI = "xaerominimap"; + public static final String PROTOCOL_ID_WORLD = "xaeroworldmap"; + + private static final ResourceLocation MINIMAP_KEY = idMini("main"); + private static final ResourceLocation WORLDMAP_KEY = idWorld("main"); + + public static boolean shouldEnable() { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.xaeroMapProtocol; + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation idMini(String path) { + return new ResourceLocation(PROTOCOL_ID_MINI, path); + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation idWorld(String path) { + return new ResourceLocation(PROTOCOL_ID_WORLD, path); + } + + public static void onSendWorldInfo(@NotNull ServerPlayer player) { + if (shouldEnable()) { + ProtocolUtils.sendPayloadPacket(player, MINIMAP_KEY, buf -> { + buf.writeByte(0); + buf.writeInt(org.dreeam.leaf.config.modules.network.ProtocolSupport.xaeroMapServerID); + }); + ProtocolUtils.sendPayloadPacket(player, WORLDMAP_KEY, buf -> { + buf.writeByte(0); + buf.writeInt(org.dreeam.leaf.config.modules.network.ProtocolSupport.xaeroMapServerID); + }); + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java new file mode 100644 index 00000000..e4b55c5b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java @@ -0,0 +1,16 @@ +package org.leavesmc.leaves.protocol.chatimage; + +public class ChatImageIndex { + + public int index; + public int total; + public String url; + public String bytes; + + public ChatImageIndex(int index, int total, String url, String bytes) { + this.index = index; + this.total = total; + this.url = url; + this.bytes = bytes; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java new file mode 100644 index 00000000..b09a7bfe --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.protocol.core; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public interface LeavesCustomPayload> extends CustomPacketPayload { + + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @Retention(RetentionPolicy.RUNTIME) + @interface New { + } + + void write(FriendlyByteBuf buf); + + ResourceLocation id(); + + @Override + @NotNull + default Type type() { + return new CustomPacketPayload.Type<>(id()); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java new file mode 100644 index 00000000..986d2a66 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.protocol.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface LeavesProtocol { + String[] namespace(); +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java new file mode 100644 index 00000000..89cc2bc4 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java @@ -0,0 +1,379 @@ +package org.leavesmc.leaves.protocol.core; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang.ArrayUtils; +import org.bukkit.event.player.PlayerKickEvent; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesLogger; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class LeavesProtocolManager { + + private static final Class[] PAYLOAD_PARAMETER_TYPES = {ResourceLocation.class, FriendlyByteBuf.class}; + + private static final LeavesLogger LOGGER = LeavesLogger.LOGGER; + + private static final Map> KNOWN_TYPES = new HashMap<>(); + private static final Map> KNOW_RECEIVERS = new HashMap<>(); + private static Set ALL_KNOWN_ID = new HashSet<>(); + + private static final List TICKERS = new ArrayList<>(); + private static final List PLAYER_JOIN = new ArrayList<>(); + private static final List PLAYER_LEAVE = new ArrayList<>(); + private static final List RELOAD_SERVER = new ArrayList<>(); + private static final Map> MINECRAFT_REGISTER = new HashMap<>(); + + public static void reload() { + handleServerReload(); + cleanProtocols(); // Do cleanup + init(); + } + + public static void init() { + boolean shouldEnable; + + for (Class clazz : org.dreeam.leaf.config.LeafConfig.getClasses("org.leavesmc.leaves.protocol")) { + final LeavesProtocol protocol = clazz.getAnnotation(LeavesProtocol.class); + if (protocol != null) { + Set methods; + try { + Method[] publicMethods = clazz.getMethods(); + Method[] privateMethods = clazz.getDeclaredMethods(); + methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0f); + Collections.addAll(methods, publicMethods); + Collections.addAll(methods, privateMethods); + + Object instance = clazz.getConstructor().newInstance(); + Method method = clazz.getMethod("shouldEnable"); + shouldEnable = (boolean) method.invoke(instance); + } catch (NoClassDefFoundError | InvocationTargetException | InstantiationException | + IllegalAccessException | NoSuchMethodException error) { + LOGGER.severe("Failed to load class " + clazz.getName() + " due to missing dependencies, " + error.getCause() + ": " + error.getMessage()); + return; + } + + Map map = KNOWN_TYPES.getOrDefault(protocol, new HashMap<>()); + for (final Method method : methods) { + if (method.isBridge() || method.isSynthetic() || !Modifier.isStatic(method.getModifiers())) { + continue; + } + + method.setAccessible(true); + + final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class); + if (reloadServer != null) { + RELOAD_SERVER.add(method); + continue; + } + + if (!shouldEnable) { + continue; + } + + final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class); + if (init != null) { + try { + method.invoke(null); + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.severe("Failed to invoke init method " + method.getName() + " in " + clazz.getName() + ", " + exception.getCause() + ": " + exception.getMessage()); + } + continue; + } + + final ProtocolHandler.PayloadReceiver receiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class); + if (receiver != null) { + try { + boolean found = false; + for (Method payloadMethod : receiver.payload().getDeclaredMethods()) { + if (payloadMethod.isAnnotationPresent(LeavesCustomPayload.New.class)) { + if (Arrays.equals(payloadMethod.getParameterTypes(), PAYLOAD_PARAMETER_TYPES) && payloadMethod.getReturnType() == receiver.payload() && Modifier.isStatic(payloadMethod.getModifiers())) { + payloadMethod.setAccessible(true); + map.put(receiver, payloadMethod); + found = true; + break; + } + } + } + + if (!found) { + Constructor> constructor = receiver.payload().getConstructor(PAYLOAD_PARAMETER_TYPES); + if (constructor.isAnnotationPresent(LeavesCustomPayload.New.class)) { + constructor.setAccessible(true); + map.put(receiver, constructor); + } else { + throw new NoSuchMethodException(); + } + } + } catch (NoSuchMethodException exception) { + LOGGER.severe("Failed to find constructor for " + receiver.payload().getName() + ", " + exception.getCause() + ": " + exception.getMessage()); + continue; + } + + if (!KNOW_RECEIVERS.containsKey(protocol)) { + KNOW_RECEIVERS.put(protocol, new HashMap<>()); + } + + KNOW_RECEIVERS.get(protocol).put(receiver, method); + continue; + } + + final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class); + if (ticker != null) { + TICKERS.add(method); + continue; + } + + final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class); + if (playerJoin != null) { + PLAYER_JOIN.add(method); + continue; + } + + final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class); + if (playerLeave != null) { + PLAYER_LEAVE.add(method); + continue; + } + + final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class); + if (minecraftRegister != null) { + if (!MINECRAFT_REGISTER.containsKey(protocol)) { + MINECRAFT_REGISTER.put(protocol, new HashMap<>()); + } + + MINECRAFT_REGISTER.get(protocol).put(minecraftRegister, method); + } + } + KNOWN_TYPES.put(protocol, map); + } + } + + for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) { + Map map = KNOWN_TYPES.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (receiver.sendFabricRegister() && !receiver.ignoreId()) { + for (String payloadId : receiver.payloadId()) { + for (String namespace : protocol.namespace()) { + ALL_KNOWN_ID.add(new ResourceLocation(namespace, payloadId)); + } + } + } + } + } + ALL_KNOWN_ID = ImmutableSet.copyOf(ALL_KNOWN_ID); + } + + private static void cleanProtocols() { + KNOWN_TYPES.clear(); + KNOW_RECEIVERS.clear(); + //ALL_KNOWN_ID.clear(); // No need + TICKERS.clear(); + PLAYER_JOIN.clear(); + PLAYER_LEAVE.clear(); + //RELOAD_SERVER.clear(); // No need + MINECRAFT_REGISTER.clear(); + } + + public static LeavesCustomPayload decode(ResourceLocation id, FriendlyByteBuf buf) { + for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) { + if (!ArrayUtils.contains(protocol.namespace(), id.getNamespace())) { + continue; + } + + Map map = KNOWN_TYPES.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), id.getPath())) { + try { + if (map.get(receiver) instanceof Constructor constructor) { + return (LeavesCustomPayload) constructor.newInstance(id, buf); + } else if (map.get(receiver) instanceof Method method) { + return (LeavesCustomPayload) method.invoke(null, id, buf); + } + } catch (InvocationTargetException | InstantiationException | IllegalAccessException exception) { + LOGGER.warning("Failed to create payload for " + id + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage()); + buf.readBytes(buf.readableBytes()); + return new ErrorPayload(id, protocol.namespace(), receiver.payloadId()); + } + } + } + } + return null; + } + + public static void handlePayload(ServerPlayer player, LeavesCustomPayload payload) { + if (payload instanceof ErrorPayload errorPayload) { + player.connection.disconnect(Component.literal("Payload " + Arrays.toString(errorPayload.packetID) + " from " + Arrays.toString(errorPayload.protocolID) + " error"), PlayerKickEvent.Cause.INVALID_PAYLOAD); + return; + } + + for (LeavesProtocol protocol : KNOW_RECEIVERS.keySet()) { + if (!ArrayUtils.contains(protocol.namespace(), payload.type().id().getNamespace())) { + continue; + } + + Map map = KNOW_RECEIVERS.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (payload.getClass() == receiver.payload()) { + if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), payload.type().id().getPath())) { + try { + map.get(receiver).invoke(null, player, payload); + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle payload " + payload.type().id() + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } + } + } + + public static void handleTick() { + if (!TICKERS.isEmpty()) { + try { + for (Method method : TICKERS) { + method.invoke(null); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to tick, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handlePlayerJoin(ServerPlayer player) { + if (!PLAYER_JOIN.isEmpty()) { + try { + for (Method method : PLAYER_JOIN) { + method.invoke(null, player); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle player join, " + exception.getCause() + ": " + exception.getMessage()); + } + } + + ProtocolUtils.sendPayloadPacket(player, new FabricRegisterPayload(ALL_KNOWN_ID)); + } + + public static void handlePlayerLeave(ServerPlayer player) { + if (!PLAYER_LEAVE.isEmpty()) { + try { + for (Method method : PLAYER_LEAVE) { + method.invoke(null, player); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle player leave, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handleServerReload() { + if (!RELOAD_SERVER.isEmpty()) { + try { + for (Method method : RELOAD_SERVER) { + method.invoke(null); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle server reload, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handleMinecraftRegister(String channelId, ServerPlayer player) { + for (LeavesProtocol protocol : MINECRAFT_REGISTER.keySet()) { + String[] channel = channelId.split(":"); + if (!ArrayUtils.contains(protocol.namespace(), channel[0])) { + continue; + } + + Map map = MINECRAFT_REGISTER.get(protocol); + for (ProtocolHandler.MinecraftRegister register : map.keySet()) { + if (register.ignoreId() || ArrayUtils.contains(register.channelId(), channel[1])) { + try { + map.get(register).invoke(null, player, channel[1]); + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle minecraft register, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } + } + + public record ErrorPayload(ResourceLocation id, String[] protocolID, String[] packetID) implements LeavesCustomPayload { + @Override + public void write(@NotNull FriendlyByteBuf buf) { + } + } + + public record EmptyPayload(ResourceLocation id) implements LeavesCustomPayload { + + @New + public EmptyPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(location); + } + + @Override + public void write(@NotNull FriendlyByteBuf buf) { + } + } + + public record LeavesPayload(FriendlyByteBuf data, ResourceLocation id) implements LeavesCustomPayload { + + @New + public LeavesPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(new FriendlyByteBuf(buf.readBytes(buf.readableBytes())), location); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeBytes(data); + } + } + + public record FabricRegisterPayload( + Set channels) implements LeavesCustomPayload { + + public static final ResourceLocation CHANNEL = ResourceLocation.withDefaultNamespace("register"); + + @New + public FabricRegisterPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(buf.readCollection(HashSet::new, FriendlyByteBuf::readResourceLocation)); + } + + @Override + public void write(FriendlyByteBuf buf) { + boolean first = true; + + ResourceLocation channel; + for (Iterator var3 = this.channels.iterator(); var3.hasNext(); buf.writeBytes(channel.toString().getBytes(StandardCharsets.US_ASCII))) { + channel = var3.next(); + if (first) { + first = false; + } else { + buf.writeByte(0); + } + } + } + + @Override + public ResourceLocation id() { + return CHANNEL; + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java new file mode 100644 index 00000000..22f47824 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java @@ -0,0 +1,56 @@ +package org.leavesmc.leaves.protocol.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class ProtocolHandler { + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Init { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PayloadReceiver { + Class> payload(); + + String[] payloadId() default ""; + + boolean ignoreId() default false; + + boolean sendFabricRegister() default true; + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Ticker { + int delay() default 0; + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PlayerJoin { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PlayerLeave { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface ReloadServer { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface MinecraftRegister { + + String[] channelId() default ""; + + boolean ignoreId() default false; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java new file mode 100644 index 00000000..72fb1e65 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java @@ -0,0 +1,52 @@ +package org.leavesmc.leaves.protocol.core; + +import io.netty.buffer.ByteBuf; +import io.papermc.paper.ServerBuildInfo; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class ProtocolUtils { + + private static final Function bufDecorator = RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess()); + + public static String buildProtocolVersion(String protocol) { + return protocol + "-leaves-" + ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE); + } + + public static void sendEmptyPayloadPacket(ServerPlayer player, ResourceLocation id) { + player.connection.send(new ClientboundCustomPayloadPacket(new LeavesProtocolManager.EmptyPayload(id))); + } + + @SuppressWarnings("all") + public static void sendPayloadPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer consumer) { + player.connection.send(new ClientboundCustomPayloadPacket(new LeavesCustomPayload() { + @Override + public void write(@NotNull FriendlyByteBuf buf) { + consumer.accept(buf); + } + + @Override + @NotNull + public ResourceLocation id() { + return id; + } + })); + } + + public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) { + player.connection.send(new ClientboundCustomPayloadPacket(payload)); + } + + public static RegistryFriendlyByteBuf decorate(ByteBuf buf) { + return bufDecorator.apply(buf); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java new file mode 100644 index 00000000..a7d9bd32 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java @@ -0,0 +1,271 @@ +package org.leavesmc.leaves.protocol.jade; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.AgeableMob; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.Chicken; +import net.minecraft.world.entity.animal.allay.Allay; +import net.minecraft.world.entity.animal.armadillo.Armadillo; +import net.minecraft.world.entity.animal.frog.Tadpole; +import net.minecraft.world.entity.monster.ZombieVillager; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.CampfireBlock; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; +import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; +import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; +import net.minecraft.world.level.block.entity.CommandBlockEntity; +import net.minecraft.world.level.block.entity.ComparatorBlockEntity; +import net.minecraft.world.level.block.entity.HopperBlockEntity; +import net.minecraft.world.level.block.entity.JukeboxBlockEntity; +import net.minecraft.world.level.block.entity.LecternBlockEntity; +import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.payload.ReceiveDataPayload; +import org.leavesmc.leaves.protocol.jade.payload.RequestBlockPayload; +import org.leavesmc.leaves.protocol.jade.payload.RequestEntityPayload; +import org.leavesmc.leaves.protocol.jade.payload.ServerPingPayload; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; +import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; +import org.leavesmc.leaves.protocol.jade.provider.ItemStorageExtensionProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.BeehiveProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.BrewingStandProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.CampfireProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.ChiseledBookshelfProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.CommandBlockProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.FurnaceProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.HopperLockProvider; +import org.leavesmc.leaves.protocol.jade.provider.ItemStorageProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.JukeboxProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.LecternProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.MobSpawnerCooldownProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.ObjectNameProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.RedstoneProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.AnimalOwnerProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.MobBreedingProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.MobGrowthProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.NextEntityDropProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.StatusEffectsProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.ZombieVillagerProvider; +import org.leavesmc.leaves.protocol.jade.util.HierarchyLookup; +import org.leavesmc.leaves.protocol.jade.util.LootTableMineableCollector; +import org.leavesmc.leaves.protocol.jade.util.PairHierarchyLookup; +import org.leavesmc.leaves.protocol.jade.util.PriorityStore; +import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup; + +import java.util.Collections; +import java.util.List; + +@LeavesProtocol(namespace = "jade") +public class JadeProtocol { + + public static PriorityStore priorities; + private static List shearableBlocks = null; + + public static final String PROTOCOL_ID = "jade"; + + public static final HierarchyLookup> entityDataProviders = new HierarchyLookup<>(Entity.class); + public static final PairHierarchyLookup> blockDataProviders = new PairHierarchyLookup<>(new HierarchyLookup<>(Block.class), new HierarchyLookup<>(BlockEntity.class)); + public static final WrappedHierarchyLookup> itemStorageProviders = WrappedHierarchyLookup.forAccessor(); + + public static boolean shouldEnable() { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol; + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation id(String path) { + return new ResourceLocation(PROTOCOL_ID, path); + } + + @Contract("_ -> new") + public static @NotNull ResourceLocation mc_id(String path) { + return ResourceLocation.withDefaultNamespace(path); + } + + @ProtocolHandler.Init + public static void init() { + priorities = new PriorityStore<>(IJadeProvider::getDefaultPriority, IJadeProvider::getUid); + + // core plugin + blockDataProviders.register(BlockEntity.class, ObjectNameProvider.ForBlock.INSTANCE); + + // universal plugin + entityDataProviders.register(Entity.class, ItemStorageProvider.getEntity()); + blockDataProviders.register(Block.class, ItemStorageProvider.getBlock()); + + itemStorageProviders.register(Object.class, ItemStorageExtensionProvider.INSTANCE); + itemStorageProviders.register(Block.class, ItemStorageExtensionProvider.INSTANCE); + + // vanilla plugin + entityDataProviders.register(Entity.class, AnimalOwnerProvider.INSTANCE); + entityDataProviders.register(LivingEntity.class, StatusEffectsProvider.INSTANCE); + entityDataProviders.register(AgeableMob.class, MobGrowthProvider.INSTANCE); + entityDataProviders.register(Tadpole.class, MobGrowthProvider.INSTANCE); + entityDataProviders.register(Animal.class, MobBreedingProvider.INSTANCE); + entityDataProviders.register(Allay.class, MobBreedingProvider.INSTANCE); + + entityDataProviders.register(Chicken.class, NextEntityDropProvider.INSTANCE); + entityDataProviders.register(Armadillo.class, NextEntityDropProvider.INSTANCE); + + entityDataProviders.register(ZombieVillager.class, ZombieVillagerProvider.INSTANCE); + + blockDataProviders.register(BrewingStandBlockEntity.class, BrewingStandProvider.INSTANCE); + blockDataProviders.register(BeehiveBlockEntity.class, BeehiveProvider.INSTANCE); + blockDataProviders.register(CommandBlockEntity.class, CommandBlockProvider.INSTANCE); + blockDataProviders.register(JukeboxBlockEntity.class, JukeboxProvider.INSTANCE); + blockDataProviders.register(LecternBlockEntity.class, LecternProvider.INSTANCE); + + blockDataProviders.register(ComparatorBlockEntity.class, RedstoneProvider.INSTANCE); + blockDataProviders.register(HopperBlockEntity.class, HopperLockProvider.INSTANCE); + blockDataProviders.register(CalibratedSculkSensorBlockEntity.class, RedstoneProvider.INSTANCE); + + blockDataProviders.register(AbstractFurnaceBlockEntity.class, FurnaceProvider.INSTANCE); + blockDataProviders.register(ChiseledBookShelfBlockEntity.class, ChiseledBookshelfProvider.INSTANCE); + blockDataProviders.register(TrialSpawnerBlockEntity.class, MobSpawnerCooldownProvider.INSTANCE); + + blockDataProviders.idMapped(); + entityDataProviders.idMapped(); + itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE); + + blockDataProviders.loadComplete(priorities); + entityDataProviders.loadComplete(priorities); + itemStorageProviders.loadComplete(priorities); + + try { + shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute( + MinecraftServer.getServer().reloadableRegistries().lookup().lookupOrThrow(Registries.LOOT_TABLE), + Items.SHEARS.getDefaultInstance() + )); + } catch (Throwable ignore) { + shearableBlocks = List.of(); + LeavesLogger.LOGGER.severe("Failed to collect shearable blocks"); + } + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerJoin(ServerPlayer player) { + sendPingPacket(player); + } + + @ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class, payloadId = "request_entity") + public static void requestEntityData(ServerPlayer player, RequestEntityPayload payload) { + MinecraftServer.getServer().execute(() -> { + EntityAccessor accessor = payload.data().unpack(player); + if (accessor == null) { + return; + } + + Entity entity = accessor.getEntity(); + double maxDistance = Mth.square(player.entityInteractionRange() + 21); + if (entity == null || player.distanceToSqr(entity) > maxDistance) { + return; + } + + List> providers = entityDataProviders.get(entity); + if (providers.isEmpty()) { + return; + } + + CompoundTag tag = new CompoundTag(); + for (IServerDataProvider provider : providers) { + if (!payload.dataProviders().contains(provider)) { + continue; + } + try { + provider.appendServerData(tag, accessor); + } catch (Exception e) { + LeavesLogger.LOGGER.warning("Error while saving data for entity " + entity); + } + } + tag.putInt("EntityId", entity.getId()); + + ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag)); + }); + } + + @ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class, payloadId = "request_block") + public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) { + MinecraftServer server = MinecraftServer.getServer(); + server.execute(() -> { + BlockAccessor accessor = payload.data().unpack(player); + if (accessor == null) { + return; + } + + BlockPos pos = accessor.getPosition(); + Block block = accessor.getBlock(); + BlockEntity blockEntity = accessor.getBlockEntity(); + double maxDistance = Mth.square(player.blockInteractionRange() + 21); + if (pos.distSqr(player.blockPosition()) > maxDistance || !accessor.getLevel().isLoaded(pos)) { + return; + } + + List> providers; + if (blockEntity != null) { + providers = blockDataProviders.getMerged(block, blockEntity); + } else { + providers = blockDataProviders.first.get(block); + } + + if (providers.isEmpty()) { + return; + } + + CompoundTag tag = new CompoundTag(); + for (IServerDataProvider provider : providers) { + if (!payload.dataProviders().contains(provider)) { + continue; + } + try { + provider.appendServerData(tag, accessor); + } catch (Exception e) { + LeavesLogger.LOGGER.warning("Error while saving data for block " + accessor.getBlockState()); + } + } + tag.putInt("x", pos.getX()); + tag.putInt("y", pos.getY()); + tag.putInt("z", pos.getZ()); + tag.putString("BlockId", BuiltInRegistries.BLOCK.getKey(block).toString()); + + ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag)); + }); + } + + @ProtocolHandler.ReloadServer + public static void onServerReload() { + if (org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { + enableAllPlayer(); + } + } + + public static void enableAllPlayer() { + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().players) { + sendPingPacket(player); + } + } + + public static void sendPingPacket(ServerPlayer player) { + ProtocolUtils.sendPayloadPacket(player, new ServerPingPayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds())); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java new file mode 100644 index 00000000..1a637045 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamEncoder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.HitResult; +import org.jetbrains.annotations.Nullable; + +public interface Accessor { + + Level getLevel(); + + Player getPlayer(); + + Tag encodeAsNbt(StreamEncoder codec, D value); + + T getHitResult(); + + /** + * @return {@code true} if the dedicated server has Jade installed. + */ + boolean isServerConnected(); + + boolean showDetails(); + + @Nullable + Object getTarget(); + + float tickRate(); +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java new file mode 100644 index 00000000..a04fbb45 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java @@ -0,0 +1,83 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import java.util.function.Supplier; + +import org.apache.commons.lang3.ArrayUtils; + +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamEncoder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.HitResult; + +public abstract class AccessorImpl implements Accessor { + + private final Level level; + private final Player player; + private final Supplier hit; + private final boolean serverConnected; + private final boolean showDetails; + protected boolean verify; + private RegistryFriendlyByteBuf buffer; + + public AccessorImpl(Level level, Player player, Supplier hit, boolean serverConnected, boolean showDetails) { + this.level = level; + this.player = player; + this.hit = hit; + this.serverConnected = serverConnected; + this.showDetails = showDetails; + } + + @Override + public Level getLevel() { + return level; + } + + @Override + public Player getPlayer() { + return player; + } + + private RegistryFriendlyByteBuf buffer() { + if (buffer == null) { + buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), level.registryAccess()); + } + buffer.clear(); + return buffer; + } + + @Override + public Tag encodeAsNbt(StreamEncoder streamCodec, D value) { + RegistryFriendlyByteBuf buffer = buffer(); + streamCodec.encode(buffer, value); + ByteArrayTag tag = new ByteArrayTag(ArrayUtils.subarray(buffer.array(), 0, buffer.readableBytes())); + buffer.clear(); + return tag; + } + + @Override + public T getHitResult() { + return hit.get(); + } + + /** + * Returns true if dedicated server has Jade installed. + */ + @Override + public boolean isServerConnected() { + return serverConnected; + } + + @Override + public boolean showDetails() { + return showDetails; + } + + @Override + public float tickRate() { + return getLevel().tickRateManager().tickrate(); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java new file mode 100644 index 00000000..12d689ca --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java @@ -0,0 +1,50 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Supplier; + +public interface BlockAccessor extends Accessor { + + Block getBlock(); + + BlockState getBlockState(); + + BlockEntity getBlockEntity(); + + BlockPos getPosition(); + + Direction getSide(); + + @ApiStatus.NonExtendable + interface Builder { + Builder level(Level level); + + Builder player(Player player); + + Builder showDetails(boolean showDetails); + + Builder hit(BlockHitResult hit); + + Builder blockState(BlockState state); + + default Builder blockEntity(BlockEntity blockEntity) { + return blockEntity(() -> blockEntity); + } + + Builder blockEntity(Supplier blockEntity); + + Builder from(BlockAccessor accessor); + + BlockAccessor build(); + } + +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java new file mode 100644 index 00000000..9e4a321b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java @@ -0,0 +1,163 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.base.Suppliers; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +/** + * Class to get information of block target and context. + */ +public class BlockAccessorImpl extends AccessorImpl implements BlockAccessor { + + private final BlockState blockState; + @Nullable + private final Supplier blockEntity; + + private BlockAccessorImpl(Builder builder) { + super(builder.level, builder.player, Suppliers.ofInstance(builder.hit), builder.connected, builder.showDetails); + blockState = builder.blockState; + blockEntity = builder.blockEntity; + } + + @Override + public Block getBlock() { + return getBlockState().getBlock(); + } + + @Override + public BlockState getBlockState() { + return blockState; + } + + @Override + public BlockEntity getBlockEntity() { + return blockEntity == null ? null : blockEntity.get(); + } + + @Override + public BlockPos getPosition() { + return getHitResult().getBlockPos(); + } + + @Override + public Direction getSide() { + return getHitResult().getDirection(); + } + + @Nullable + @Override + public Object getTarget() { + return getBlockEntity(); + } + + public static class Builder implements BlockAccessor.Builder { + + private Level level; + private Player player; + private boolean connected; + private boolean showDetails; + private BlockHitResult hit; + private BlockState blockState = Blocks.AIR.defaultBlockState(); + private Supplier blockEntity; + + @Override + public Builder level(Level level) { + this.level = level; + return this; + } + + @Override + public Builder player(Player player) { + this.player = player; + return this; + } + + @Override + public Builder showDetails(boolean showDetails) { + this.showDetails = showDetails; + return this; + } + + @Override + public Builder hit(BlockHitResult hit) { + this.hit = hit; + return this; + } + + @Override + public Builder blockState(BlockState blockState) { + this.blockState = blockState; + return this; + } + + @Override + public Builder blockEntity(Supplier blockEntity) { + this.blockEntity = blockEntity; + return this; + } + + @Override + public Builder from(BlockAccessor accessor) { + level = accessor.getLevel(); + player = accessor.getPlayer(); + connected = accessor.isServerConnected(); + showDetails = accessor.showDetails(); + hit = accessor.getHitResult(); + blockEntity = accessor::getBlockEntity; + blockState = accessor.getBlockState(); + return this; + } + + @Override + public BlockAccessor build() { + return new BlockAccessorImpl(this); + } + } + + public record SyncData(boolean showDetails, BlockHitResult hit, BlockState blockState, ItemStack fakeBlock) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, + SyncData::showDetails, + StreamCodec.of(FriendlyByteBuf::writeBlockHitResult, FriendlyByteBuf::readBlockHitResult), + SyncData::hit, + ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), + SyncData::blockState, + ItemStack.OPTIONAL_STREAM_CODEC, + SyncData::fakeBlock, + SyncData::new + ); + + public BlockAccessor unpack(ServerPlayer player) { + Supplier blockEntity = null; + if (blockState.hasBlockEntity()) { + blockEntity = Suppliers.memoize(() -> player.level().getBlockEntity(hit.getBlockPos())); + } + return new Builder() + .level(player.level()) + .player(player) + .showDetails(showDetails) + .hit(hit) + .blockState(blockState) + .blockEntity(blockEntity) + .build(); + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java new file mode 100644 index 00000000..454360d5 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java @@ -0,0 +1,44 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Supplier; + +public interface EntityAccessor extends Accessor { + + Entity getEntity(); + + /** + * For part entity like ender dragon's, getEntity() will return the parent entity. + */ + Entity getRawEntity(); + + @ApiStatus.NonExtendable + interface Builder { + Builder level(Level level); + + Builder player(Player player); + + Builder showDetails(boolean showDetails); + + default Builder hit(EntityHitResult hit) { + return hit(() -> hit); + } + + Builder hit(Supplier hit); + + default Builder entity(Entity entity) { + return entity(() -> entity); + } + + Builder entity(Supplier entity); + + Builder from(EntityAccessor accessor); + + EntityAccessor build(); + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java new file mode 100644 index 00000000..65d16c00 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java @@ -0,0 +1,123 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import com.google.common.base.Suppliers; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.util.CommonUtil; + +import java.util.function.Supplier; + +public class EntityAccessorImpl extends AccessorImpl implements EntityAccessor { + + private final Supplier entity; + + public EntityAccessorImpl(Builder builder) { + super(builder.level, builder.player, builder.hit, builder.connected, builder.showDetails); + entity = builder.entity; + } + + @Override + public Entity getEntity() { + return CommonUtil.wrapPartEntityParent(getRawEntity()); + } + + @Override + public Entity getRawEntity() { + return entity.get(); + } + + @NotNull + @Override + public Object getTarget() { + return getEntity(); + } + + public static class Builder implements EntityAccessor.Builder { + + public boolean showDetails; + private Level level; + private Player player; + private boolean connected; + private Supplier hit; + private Supplier entity; + + @Override + public Builder level(Level level) { + this.level = level; + return this; + } + + @Override + public Builder player(Player player) { + this.player = player; + return this; + } + + @Override + public Builder showDetails(boolean showDetails) { + this.showDetails = showDetails; + return this; + } + + @Override + public Builder hit(Supplier hit) { + this.hit = hit; + return this; + } + + @Override + public Builder entity(Supplier entity) { + this.entity = entity; + return this; + } + + @Override + public Builder from(EntityAccessor accessor) { + level = accessor.getLevel(); + player = accessor.getPlayer(); + connected = accessor.isServerConnected(); + showDetails = accessor.showDetails(); + hit = accessor::getHitResult; + entity = accessor::getEntity; + return this; + } + + @Override + public EntityAccessor build() { + return new EntityAccessorImpl(this); + } + } + + public record SyncData(boolean showDetails, int id, int partIndex, Vec3 hitVec) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, + SyncData::showDetails, + ByteBufCodecs.VAR_INT, + SyncData::id, + ByteBufCodecs.VAR_INT, + SyncData::partIndex, + ByteBufCodecs.VECTOR3F.map(Vec3::new, Vec3::toVector3f), + SyncData::hitVec, + SyncData::new + ); + + public EntityAccessor unpack(ServerPlayer player) { + Supplier entity = Suppliers.memoize(() -> CommonUtil.getPartEntity(player.level().getEntity(id), partIndex)); + return new EntityAccessorImpl.Builder() + .level(player.level()) + .player(player) + .showDetails(showDetails) + .entity(entity) + .hit(Suppliers.memoize(() -> new EntityHitResult(entity.get(), hitVec))) + .build(); + } + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java new file mode 100644 index 00000000..1b474ea8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java @@ -0,0 +1,28 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; + +public record ReceiveDataPayload(CompoundTag tag) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_RECEIVE_DATA = JadeProtocol.id("receive_data"); + + @New + public ReceiveDataPayload(ResourceLocation id, FriendlyByteBuf buf) { + this(buf.readNbt()); + } + + @Override + public void write(@NotNull FriendlyByteBuf buf) { + buf.writeNbt(tag); + } + + @Override + public ResourceLocation id() { + return PACKET_RECEIVE_DATA; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java new file mode 100644 index 00000000..480ec35f --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessorImpl; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +import java.util.List; +import java.util.Objects; + +import static org.leavesmc.leaves.protocol.jade.JadeProtocol.blockDataProviders; + +public record RequestBlockPayload(BlockAccessorImpl.SyncData data, List<@Nullable IServerDataProvider> dataProviders) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block"); + private static final StreamCodec CODEC = StreamCodec.composite( + BlockAccessorImpl.SyncData.STREAM_CODEC, + RequestBlockPayload::data, + ByteBufCodecs.>list() + .apply(ByteBufCodecs.idMapper( + $ -> Objects.requireNonNull(blockDataProviders.idMapper()).byId($), + $ -> Objects.requireNonNull(blockDataProviders.idMapper()).getIdOrThrow($))), + RequestBlockPayload::dataProviders, + RequestBlockPayload::new); + + @Override + public void write(FriendlyByteBuf buf) { + CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); + } + + @New + public static RequestBlockPayload create(ResourceLocation location, FriendlyByteBuf buf) { + return CODEC.decode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess())); + } + + @Override + @NotNull + public ResourceLocation id() { + return PACKET_REQUEST_BLOCK; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java new file mode 100644 index 00000000..395138d4 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java @@ -0,0 +1,53 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessorImpl; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +import java.util.List; +import java.util.Objects; + +import static org.leavesmc.leaves.protocol.jade.JadeProtocol.entityDataProviders; + +public record RequestEntityPayload(EntityAccessorImpl.SyncData data, List<@Nullable IServerDataProvider> dataProviders) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity"); + private static final StreamCodec CODEC = StreamCodec.composite( + EntityAccessorImpl.SyncData.STREAM_CODEC, + RequestEntityPayload::data, + ByteBufCodecs.>list() + .apply(ByteBufCodecs.idMapper( + $ -> Objects.requireNonNull(entityDataProviders.idMapper()).byId($), + $ -> Objects.requireNonNull(entityDataProviders.idMapper()).getIdOrThrow($) + )), + RequestEntityPayload::dataProviders, + RequestEntityPayload::new); + + + @Override + public void write(FriendlyByteBuf buf) { + CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); + } + + @New + public static RequestEntityPayload create(ResourceLocation location, FriendlyByteBuf buf) { + return CODEC.decode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess())); + } + + @Override + @NotNull + public ResourceLocation id() { + return PACKET_REQUEST_ENTITY; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java new file mode 100644 index 00000000..f4419962 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java @@ -0,0 +1,49 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import com.google.common.collect.Maps; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.block.Block; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; + +import java.util.List; +import java.util.Map; + +import static org.leavesmc.leaves.protocol.jade.util.JadeCodec.PRIMITIVE_STREAM_CODEC; + +public record ServerPingPayload( + Map serverConfig, + List shearableBlocks, + List blockProviderIds, + List entityProviderIds) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_SERVER_HANDSHAKE = JadeProtocol.id("server_ping_v1"); + private static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.map(Maps::newHashMapWithExpectedSize, ResourceLocation.STREAM_CODEC, PRIMITIVE_STREAM_CODEC), + ServerPingPayload::serverConfig, + ByteBufCodecs.registry(Registries.BLOCK).apply(ByteBufCodecs.list()), + ServerPingPayload::shearableBlocks, + ByteBufCodecs.list().apply(ResourceLocation.STREAM_CODEC), + ServerPingPayload::blockProviderIds, + ByteBufCodecs.list().apply(ResourceLocation.STREAM_CODEC), + ServerPingPayload::entityProviderIds, + ServerPingPayload::new); + + @Override + public void write(FriendlyByteBuf buf) { + CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); + } + + @Override + public ResourceLocation id() { + return PACKET_SERVER_HANDSHAKE; + } +} + diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java new file mode 100644 index 00000000..d62fc8f9 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import net.minecraft.resources.ResourceLocation; + +public interface IJadeProvider { + + ResourceLocation getUid(); + + default int getDefaultPriority() { + return 0; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java new file mode 100644 index 00000000..7d839f17 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java @@ -0,0 +1,8 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import net.minecraft.nbt.CompoundTag; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; + +public interface IServerDataProvider> extends IJadeProvider { + void appendServerData(CompoundTag data, T accessor); +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java new file mode 100644 index 00000000..6e32eed1 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java @@ -0,0 +1,10 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.util.ViewGroup; + +import java.util.List; + +public interface IServerExtensionProvider extends IJadeProvider { + List> getGroups(Accessor request); +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java new file mode 100644 index 00000000..3efa3ceb --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java @@ -0,0 +1,138 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.LockCode; +import net.minecraft.world.RandomizableContainer; +import net.minecraft.world.WorldlyContainerHolder; +import net.minecraft.world.entity.animal.horse.AbstractHorse; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.vehicle.ContainerEntity; +import net.minecraft.world.inventory.PlayerEnderChestContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.entity.EnderChestBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.util.ItemCollector; +import org.leavesmc.leaves.protocol.jade.util.ItemIterator; +import org.leavesmc.leaves.protocol.jade.util.ViewGroup; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public enum ItemStorageExtensionProvider implements IServerExtensionProvider { + INSTANCE; + + public static final Cache> targetCache = CacheBuilder.newBuilder().weakKeys().expireAfterAccess(60, TimeUnit.SECONDS).build(); + + private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default"); + + @Override + public List> getGroups(Accessor request) { + Object target = request.getTarget(); + + switch (target) { + case null -> { + return createItemCollector(request).update(request); + } + case RandomizableContainer te when te.getLootTable() != null -> { + return List.of(); + } + case ContainerEntity containerEntity when containerEntity.getContainerLootTable() != null -> { + return List.of(); + } + default -> { + } + } + + Player player = request.getPlayer(); + if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { + if (te.lockKey != LockCode.NO_LOCK) { + return List.of(); + } + } + + if (target instanceof EnderChestBlockEntity) { + PlayerEnderChestContainer inventory = player.getEnderChestInventory(); + return new ItemCollector<>(new ItemIterator.ContainerItemIterator(x -> inventory, 0)).update(request); + } + + ItemCollector itemCollector; + try { + itemCollector = targetCache.get(target, () -> createItemCollector(request)); + } catch (ExecutionException e) { + LeavesLogger.LOGGER.severe("Failed to get item collector for " + target); + return null; + } + + return itemCollector.update(request); + } + + @Override + public ResourceLocation getUid() { + return UNIVERSAL_ITEM_STORAGE; + } + + public static ItemCollector createItemCollector(Accessor request) { + if (request.getTarget() instanceof AbstractHorse) { + return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { + if (o instanceof AbstractHorse horse) { + return horse.inventory; + } + return null; + }, 2)); + } + + // TODO BlockEntity like fabric's ItemStorage + + final Container container = findContainer(request); + if (container != null) { + if (container instanceof ChestBlockEntity) { + return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { + if (o instanceof ChestBlockEntity blockEntity) { + if (blockEntity.getBlockState().getBlock() instanceof ChestBlock chestBlock) { + Container compound = null; + if (blockEntity.getLevel() != null) { + compound = ChestBlock.getContainer(chestBlock, blockEntity.getBlockState(), blockEntity.getLevel(), blockEntity.getBlockPos(), false); + } + if (compound != null) { + return compound; + } + } + return blockEntity; + } + return null; + }, 0)); + } + return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0)); + } + + return ItemCollector.EMPTY; + } + + public static @Nullable Container findContainer(@NotNull Accessor accessor) { + Object target = accessor.getTarget(); + if (target == null && accessor instanceof BlockAccessor blockAccessor && + blockAccessor.getBlock() instanceof WorldlyContainerHolder holder) { + return holder.getContainer(blockAccessor.getBlockState(), accessor.getLevel(), blockAccessor.getPosition()); + } else if (target instanceof Container container) { + return container; + } + return null; + } + + @Override + public int getDefaultPriority() { + return IServerExtensionProvider.super.getDefaultPriority() + 1000; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java new file mode 100644 index 00000000..8289b5c4 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java @@ -0,0 +1,88 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.LockCode; +import net.minecraft.world.RandomizableContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.util.CommonUtil; +import org.leavesmc.leaves.protocol.jade.util.ItemCollector; +import org.leavesmc.leaves.protocol.jade.util.ViewGroup; + +import java.util.List; +import java.util.Map; + +public abstract class ItemStorageProvider> implements IServerDataProvider { + + private static final StreamCodec>>> STREAM_CODEC = ViewGroup.listCodec( + ItemStack.OPTIONAL_STREAM_CODEC); + + private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage"); + + public static ForBlock getBlock() { + return ForBlock.INSTANCE; + } + + public static ForEntity getEntity() { + return ForEntity.INSTANCE; + } + + public static class ForBlock extends ItemStorageProvider { + private static final ForBlock INSTANCE = new ForBlock(); + } + + public static class ForEntity extends ItemStorageProvider { + private static final ForEntity INSTANCE = new ForEntity(); + } + + public static void putData(CompoundTag tag, @NotNull Accessor accessor) { + Object target = accessor.getTarget(); + Player player = accessor.getPlayer(); + Map.Entry>> entry = CommonUtil.getServerExtensionData(accessor, JadeProtocol.itemStorageProviders); + if (entry != null) { + List> groups = entry.getValue(); + for (ViewGroup group : groups) { + if (group.views.size() > ItemCollector.MAX_SIZE) { + group.views = group.views.subList(0, ItemCollector.MAX_SIZE); + } + } + tag.put(UNIVERSAL_ITEM_STORAGE.toString(), accessor.encodeAsNbt(STREAM_CODEC, entry)); + return; + } + if (target instanceof RandomizableContainer containerEntity && containerEntity.getLootTable() != null) { + tag.putBoolean("Loot", true); + } else if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { + if (te.lockKey != LockCode.NO_LOCK) { + tag.putBoolean("Locked", true); + } + } + } + + @Override + public ResourceLocation getUid() { + return UNIVERSAL_ITEM_STORAGE; + } + + @Override + public void appendServerData(CompoundTag tag, @NotNull T accessor) { + if (accessor.getTarget() instanceof AbstractFurnaceBlockEntity) { + return; + } + putData(tag, accessor); + } + + @Override + public int getDefaultPriority() { + return 9999; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java new file mode 100644 index 00000000..52887edb --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java @@ -0,0 +1,26 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; + +import java.util.Optional; + +public interface StreamServerDataProvider, D> extends IServerDataProvider { + + @Override + default void appendServerData(CompoundTag data, T accessor) { + D value = streamData(accessor); + if (value != null) { + data.put(getUid().toString(), accessor.encodeAsNbt(streamCodec(), value)); + } + } + + @Nullable + D streamData(T accessor); + + StreamCodec streamCodec(); +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java new file mode 100644 index 00000000..ee92d79b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java @@ -0,0 +1,34 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum BeehiveProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_BEEHIVE = JadeProtocol.mc_id("beehive"); + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.BYTE.cast(); + } + + @Override + public Byte streamData(@NotNull BlockAccessor accessor) { + BeehiveBlockEntity beehive = (BeehiveBlockEntity) accessor.getBlockEntity(); + int bees = beehive.getOccupantCount(); + return (byte) (beehive.isFull() ? bees : -bees); + } + + @Override + public ResourceLocation getUid() { + return MC_BEEHIVE; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java new file mode 100644 index 00000000..e6f15f87 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum BrewingStandProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_BREWING_STAND = JadeProtocol.mc_id("brewing_stand"); + + @Override + public @NotNull Data streamData(@NotNull BlockAccessor accessor) { + BrewingStandBlockEntity brewingStand = (BrewingStandBlockEntity) accessor.getBlockEntity(); + return new Data(brewingStand.fuel, brewingStand.brewTime); + } + + @Override + public @NotNull StreamCodec streamCodec() { + return Data.STREAM_CODEC.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_BREWING_STAND; + } + + public record Data(int fuel, int time) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + Data::fuel, + ByteBufCodecs.VAR_INT, + Data::time, + Data::new); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java new file mode 100644 index 00000000..2deb3777 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java @@ -0,0 +1,55 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import com.google.common.collect.Lists; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.NbtOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.block.entity.CampfireBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; +import org.leavesmc.leaves.protocol.jade.util.ViewGroup; + +import java.util.List; + +public enum CampfireProvider implements IServerExtensionProvider { + INSTANCE; + + private static final MapCodec COOKING_TIME_CODEC = Codec.INT.fieldOf("jade:cooking"); + private static final ResourceLocation MC_CAMPFIRE = JadeProtocol.mc_id("campfire"); + + @Override + public @Nullable @Unmodifiable List> getGroups(@NotNull Accessor request) { + if (request.getTarget() instanceof CampfireBlockEntity campfire) { + List list = Lists.newArrayList(); + for (int i = 0; i < campfire.cookingTime.length; i++) { + ItemStack stack = campfire.getItems().get(i); + if (stack.isEmpty()) { + continue; + } + stack = stack.copy(); + + CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) + .update(NbtOps.INSTANCE, COOKING_TIME_CODEC, campfire.cookingTime[i] - campfire.cookingProgress[i]) + .getOrThrow(); + stack.set(DataComponents.CUSTOM_DATA, customData); + + list.add(stack); + } + return List.of(new ViewGroup<>(list)); + } + return null; + } + + @Override + public ResourceLocation getUid() { + return MC_CAMPFIRE; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java new file mode 100644 index 00000000..bde872cc --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java @@ -0,0 +1,39 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.ChiseledBookShelfBlock; +import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum ChiseledBookshelfProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_CHISELED_BOOKSHELF = JadeProtocol.mc_id("chiseled_bookshelf"); + + @Override + public @Nullable ItemStack streamData(@NotNull BlockAccessor accessor) { + int slot = ((ChiseledBookShelfBlock) accessor.getBlock()).getHitSlot(accessor.getHitResult(), accessor.getBlockState()).orElse(-1); + if (slot == -1) { + return null; + } + return ((ChiseledBookShelfBlockEntity) accessor.getBlockEntity()).getItem(slot); + } + + @Override + public StreamCodec streamCodec() { + return ItemStack.OPTIONAL_STREAM_CODEC; + } + + + @Override + public ResourceLocation getUid() { + return MC_CHISELED_BOOKSHELF; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java new file mode 100644 index 00000000..5f71fada --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java @@ -0,0 +1,40 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.CommandBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum CommandBlockProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_COMMAND_BLOCK = JadeProtocol.mc_id("command_block"); + + @Nullable + public String streamData(@NotNull BlockAccessor accessor) { + if (!accessor.getPlayer().canUseGameMasterBlocks()) { + return null; + } + String command = ((CommandBlockEntity) accessor.getBlockEntity()).getCommandBlock().getCommand(); + if (command.length() > 40) { + command = command.substring(0, 37) + "..."; + } + return command; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.STRING_UTF8.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_COMMAND_BLOCK; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java new file mode 100644 index 00000000..090e6a35 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java @@ -0,0 +1,60 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +import java.util.List; + +public enum FurnaceProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_FURNACE = JadeProtocol.mc_id("furnace"); + + @Override + public @Nullable Data streamData(@NotNull BlockAccessor accessor) { + if (!(accessor.getTarget() instanceof AbstractFurnaceBlockEntity furnace)) { + return null; + } + + if (furnace.isEmpty()) { + return null; + } + + CompoundTag furnaceTag = furnace.saveWithoutMetadata(accessor.getLevel().registryAccess()); + return new Data( + furnaceTag.getInt("CookTime"), + furnaceTag.getInt("CookTimeTotal"), + List.of(furnace.getItem(0), furnace.getItem(1), furnace.getItem(2))); + } + + @Override + public StreamCodec streamCodec() { + return Data.STREAM_CODEC; + } + + @Override + public ResourceLocation getUid() { + return MC_FURNACE; + } + + public record Data(int progress, int total, List inventory) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + Data::progress, + ByteBufCodecs.VAR_INT, + Data::total, + ItemStack.OPTIONAL_LIST_STREAM_CODEC, + Data::inventory, + Data::new); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java new file mode 100644 index 00000000..a3937081 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum HopperLockProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_HOPPER_LOCK = JadeProtocol.mc_id("hopper_lock"); + + @Override + public Boolean streamData(@NotNull BlockAccessor accessor) { + return !accessor.getBlockState().getValue(BlockStateProperties.ENABLED); + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.BOOL.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_HOPPER_LOCK; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java new file mode 100644 index 00000000..0b6e224e --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.JukeboxBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum JukeboxProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_JUKEBOX = JadeProtocol.mc_id("jukebox"); + + @Override + public @NotNull ItemStack streamData(BlockAccessor accessor) { + return ((JukeboxBlockEntity) accessor.getBlockEntity()).getTheItem(); + } + + @Override + public StreamCodec streamCodec() { + return ItemStack.OPTIONAL_STREAM_CODEC; + } + + @Override + public ResourceLocation getUid() { + return MC_JUKEBOX; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java new file mode 100644 index 00000000..c363bd61 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java @@ -0,0 +1,33 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.LecternBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum LecternProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_LECTERN = JadeProtocol.mc_id("lectern"); + + @Override + public @NotNull ItemStack streamData(@NotNull BlockAccessor accessor) { + return ((LecternBlockEntity) accessor.getBlockEntity()).getBook(); + } + + @Override + public StreamCodec streamCodec() { + return ItemStack.OPTIONAL_STREAM_CODEC; + } + + + @Override + public ResourceLocation getUid() { + return MC_LECTERN; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java new file mode 100644 index 00000000..a70f4a81 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java @@ -0,0 +1,42 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; +import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum MobSpawnerCooldownProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_MOB_SPAWNER_COOLDOWN = JadeProtocol.mc_id("mob_spawner.cooldown"); + + @Override + public @Nullable Integer streamData(@NotNull BlockAccessor accessor) { + TrialSpawnerBlockEntity spawner = (TrialSpawnerBlockEntity) accessor.getBlockEntity(); + TrialSpawnerData spawnerData = spawner.getTrialSpawner().getData(); + ServerLevel level = ((ServerLevel) accessor.getLevel()); + if (spawner.getTrialSpawner().canSpawnInLevel(level) && level.getGameTime() < spawnerData.cooldownEndsAt) { + return (int) (spawnerData.cooldownEndsAt - level.getGameTime()); + } + return null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + + @Override + public ResourceLocation getUid() { + return MC_MOB_SPAWNER_COOLDOWN; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java new file mode 100644 index 00000000..6a060c8a --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java @@ -0,0 +1,63 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.Nameable; +import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.state.properties.ChestType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public abstract class ObjectNameProvider implements StreamServerDataProvider { + + private static final ResourceLocation CORE_OBJECT_NAME = JadeProtocol.id("object_name"); + + public static class ForBlock extends ObjectNameProvider implements StreamServerDataProvider { + public static final ForBlock INSTANCE = new ForBlock(); + + @Override + @Nullable + public Component streamData(@NotNull BlockAccessor accessor) { + if (!(accessor.getBlockEntity() instanceof Nameable nameable)) { + return null; + } + if (nameable instanceof ChestBlockEntity && accessor.getBlock() instanceof ChestBlock && accessor.getBlockState().getValue(ChestBlock.TYPE) != ChestType.SINGLE) { + MenuProvider menuProvider = accessor.getBlockState().getMenuProvider(accessor.getLevel(), accessor.getPosition()); + if (menuProvider != null) { + Component name = menuProvider.getDisplayName(); + if (!(name.getContents() instanceof TranslatableContents contents) || !"container.chestDouble".equals(contents.getKey())) { + return name; + } + } + } else if (nameable.hasCustomName()) { + return nameable.getDisplayName(); + } + return accessor.getBlockEntity().components().get(DataComponents.ITEM_NAME); + } + + @Override + public StreamCodec streamCodec() { + return ComponentSerialization.STREAM_CODEC; + } + } + + @Override + public ResourceLocation getUid() { + return CORE_OBJECT_NAME; + } + + @Override + public int getDefaultPriority() { + return -10100; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java new file mode 100644 index 00000000..1cdcf21e --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java @@ -0,0 +1,36 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.CalibratedSculkSensorBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; +import net.minecraft.world.level.block.entity.ComparatorBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +public enum RedstoneProvider implements IServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_REDSTONE = JadeProtocol.mc_id("redstone"); + + @Override + public void appendServerData(CompoundTag data, @NotNull BlockAccessor accessor) { + BlockEntity blockEntity = accessor.getBlockEntity(); + if (blockEntity instanceof ComparatorBlockEntity comparator) { + data.putInt("Signal", comparator.getOutputSignal()); + } else if (blockEntity instanceof CalibratedSculkSensorBlockEntity) { + Direction direction = accessor.getBlockState().getValue(CalibratedSculkSensorBlock.FACING).getOpposite(); + int signal = accessor.getLevel().getSignal(accessor.getPosition().relative(direction), direction); + data.putInt("Signal", signal); + } + } + + @Override + public ResourceLocation getUid() { + return MC_REDSTONE; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java new file mode 100644 index 00000000..ff50ef60 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.OwnableEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; +import org.leavesmc.leaves.protocol.jade.util.CommonUtil; + +import java.util.UUID; + +public enum AnimalOwnerProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_ANIMAL_OWNER = JadeProtocol.mc_id("animal_owner"); + + @Override + public String streamData(@NotNull EntityAccessor accessor) { + return CommonUtil.getLastKnownUsername(getOwnerUUID(accessor.getEntity())); + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.STRING_UTF8.cast(); + } + + public static UUID getOwnerUUID(Entity entity) { + if (entity instanceof OwnableEntity ownableEntity) { + return ownableEntity.getOwnerUUID(); + } + return null; + } + + @Override + public ResourceLocation getUid() { + return MC_ANIMAL_OWNER; + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java new file mode 100644 index 00000000..0acba2f9 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java @@ -0,0 +1,44 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.allay.Allay; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum MobBreedingProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_MOB_BREEDING = JadeProtocol.mc_id("mob_breeding"); + + @Override + public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { + int time = 0; + Entity entity = accessor.getEntity(); + if (entity instanceof Allay allay) { + if (allay.duplicationCooldown > 0 && allay.duplicationCooldown < Integer.MAX_VALUE) { + time = (int) allay.duplicationCooldown; + } + } else { + time = ((Animal) entity).getAge(); + } + return time > 0 ? time : null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_MOB_BREEDING; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java new file mode 100644 index 00000000..44f5f4b8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.AgeableMob; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.animal.frog.Tadpole; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum MobGrowthProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_MOB_GROWTH = JadeProtocol.mc_id("mob_growth"); + + @Override + public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { + int time = -1; + Entity entity = accessor.getEntity(); + if (entity instanceof AgeableMob ageable) { + time = -ageable.getAge(); + } else if (entity instanceof Tadpole tadpole) { + time = tadpole.getTicksLeftUntilAdult(); + } + return time > 0 ? time : null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + + @Override + public ResourceLocation getUid() { + return MC_MOB_GROWTH; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java new file mode 100644 index 00000000..892911a3 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java @@ -0,0 +1,35 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.animal.Chicken; +import net.minecraft.world.entity.animal.armadillo.Armadillo; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +public enum NextEntityDropProvider implements IServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_NEXT_ENTITY_DROP = JadeProtocol.mc_id("next_entity_drop"); + + @Override + public void appendServerData(CompoundTag tag, @NotNull EntityAccessor accessor) { + int max = 24000 * 2; + if (accessor.getEntity() instanceof Chicken chicken) { + if (!chicken.isBaby() && chicken.eggTime < max) { + tag.putInt("NextEggIn", chicken.eggTime); + } + } else if (accessor.getEntity() instanceof Armadillo armadillo) { + if (!armadillo.isBaby() && armadillo.scuteTime < max) { + tag.putInt("NextScuteIn", armadillo.scuteTime); + } + } + } + + @Override + public ResourceLocation getUid() { + return MC_NEXT_ENTITY_DROP; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java new file mode 100644 index 00000000..ffc57187 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java @@ -0,0 +1,45 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +import java.util.List; + +public enum StatusEffectsProvider implements StreamServerDataProvider> { + INSTANCE; + + + private static final StreamCodec> STREAM_CODEC = ByteBufCodecs.list() + .apply(MobEffectInstance.STREAM_CODEC); + private static final ResourceLocation MC_POTION_EFFECTS = JadeProtocol.mc_id("potion_effects"); + + @Override + @Nullable + public List streamData(@NotNull EntityAccessor accessor) { + List effects = ((LivingEntity) accessor.getEntity()).getActiveEffects() + .stream() + .filter(MobEffectInstance::isVisible) + .toList(); + return effects.isEmpty() ? null : effects; + } + + @Override + public StreamCodec> streamCodec() { + return STREAM_CODEC; + } + + + @Override + public ResourceLocation getUid() { + return MC_POTION_EFFECTS; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java new file mode 100644 index 00000000..b7c9afd2 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java @@ -0,0 +1,34 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.monster.ZombieVillager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum ZombieVillagerProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_ZOMBIE_VILLAGER = JadeProtocol.mc_id("zombie_villager"); + + @Override + public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { + int time = ((ZombieVillager) accessor.getEntity()).villagerConversionTime; + return time > 0 ? time : null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_ZOMBIE_VILLAGER; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java new file mode 100644 index 00000000..6a9cd9f0 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.protocol.jade.tool; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; + +import java.util.List; + +public class ShearsToolHandler extends SimpleToolHandler { + + private static final ShearsToolHandler INSTANCE = new ShearsToolHandler(); + + public static ShearsToolHandler getInstance() { + return INSTANCE; + } + + public ShearsToolHandler() { + super(JadeProtocol.id("shears"), List.of(Items.SHEARS.getDefaultInstance()), true); + } + + @Override + public ItemStack test(BlockState state, Level world, BlockPos pos) { + if (state.is(Blocks.TRIPWIRE)) { + return tools.getFirst(); + } + return super.test(state, world, pos); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java new file mode 100644 index 00000000..d45ecdb1 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java @@ -0,0 +1,71 @@ +package org.leavesmc.leaves.protocol.jade.tool; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.Tool; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SimpleToolHandler implements ToolHandler { + + protected final List tools = Lists.newArrayList(); + private final ResourceLocation uid; + private final boolean skipInstaBreakingBlock; + + protected SimpleToolHandler(ResourceLocation uid, @NotNull List tools, boolean skipInstaBreakingBlock) { + this.uid = uid; + Preconditions.checkArgument(!tools.isEmpty(), "tools cannot be empty"); + this.tools.addAll(tools); + this.skipInstaBreakingBlock = skipInstaBreakingBlock; + } + + @Contract("_, _ -> new") + public static @NotNull SimpleToolHandler create(ResourceLocation uid, List tools) { + return create(uid, tools, true); + } + + @Contract("_, _, _ -> new") + public static @NotNull SimpleToolHandler create(ResourceLocation uid, List tools, boolean skipInstaBreakingBlock) { + return new SimpleToolHandler(uid, Lists.transform(tools, Item::getDefaultInstance), skipInstaBreakingBlock); + } + + @Override + public ItemStack test(BlockState state, Level world, BlockPos pos) { + if (skipInstaBreakingBlock && !state.requiresCorrectToolForDrops() && state.getDestroySpeed(world, pos) == 0) { + return ItemStack.EMPTY; + } + return test(state); + } + + public ItemStack test(BlockState state) { + for (ItemStack toolItem : tools) { + if (toolItem.isCorrectToolForDrops(state)) { + return toolItem; + } + Tool tool = toolItem.get(DataComponents.TOOL); + if (tool != null && tool.getMiningSpeed(state) > tool.defaultMiningSpeed()) { + return toolItem; + } + } + return ItemStack.EMPTY; + } + + @Override + public List getTools() { + return tools; + } + + @Override + public ResourceLocation getUid() { + return uid; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java new file mode 100644 index 00000000..18f11e70 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java @@ -0,0 +1,17 @@ +package org.leavesmc.leaves.protocol.jade.tool; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.List; + +public interface ToolHandler extends IJadeProvider { + + ItemStack test(BlockState state, Level world, BlockPos pos); + + List getTools(); + +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java new file mode 100644 index 00000000..a0a85361 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java @@ -0,0 +1,79 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.boss.EnderDragonPart; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.SkullBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public class CommonUtil { + + public static @NotNull ResourceLocation getId(Block block) { + return BuiltInRegistries.BLOCK.getKey(block); + } + + public static Entity wrapPartEntityParent(Entity target) { + if (target instanceof EnderDragonPart part) { + return part.parentMob; + } + return target; + } + + public static Entity getPartEntity(Entity parent, int index) { + if (parent == null) { + return null; + } + if (index < 0) { + return parent; + } + if (parent instanceof EnderDragon dragon) { + EnderDragonPart[] parts = dragon.getSubEntities(); + if (index < parts.length) { + return parts[index]; + } + } + return parent; + } + + + @Nullable + public static String getLastKnownUsername(@Nullable UUID uuid) { + if (uuid == null) { + return null; + } + Optional optional = SkullBlockEntity.fetchGameProfile(String.valueOf(uuid)).getNow(Optional.empty()); + return optional.map(GameProfile::getName).orElse(null); + } + + + public static Map.Entry>> getServerExtensionData( + Accessor accessor, + WrappedHierarchyLookup> lookup) { + for (var provider : lookup.wrappedGet(accessor)) { + List> groups; + try { + groups = provider.getGroups(accessor); + } catch (Exception e) { + LeavesLogger.LOGGER.severe(e.toString()); + continue; + } + if (groups != null) { + return Map.entry(provider.getUid(), groups); + } + } + return null; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java new file mode 100644 index 00000000..0070fd22 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java @@ -0,0 +1,139 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.base.Preconditions; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import net.minecraft.core.IdMapper; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +public class HierarchyLookup implements IHierarchyLookup { + private final Class baseClass; + private final Cache, List> resultCache = CacheBuilder.newBuilder().build(); + private final boolean singleton; + protected boolean idMapped; + @Nullable + protected IdMapper idMapper; + private ListMultimap, T> objects = ArrayListMultimap.create(); + + public HierarchyLookup(Class baseClass) { + this(baseClass, false); + } + + public HierarchyLookup(Class baseClass, boolean singleton) { + this.baseClass = baseClass; + this.singleton = singleton; + } + + @Override + public void idMapped() { + this.idMapped = true; + } + + @Override + @Nullable + public IdMapper idMapper() { + return idMapper; + } + + @Override + public void register(Class clazz, T provider) { + Preconditions.checkArgument(isClassAcceptable(clazz), "Class %s is not acceptable", clazz); + Objects.requireNonNull(provider.getUid()); + JadeProtocol.priorities.put(provider); + objects.put(clazz, provider); + } + + @Override + public boolean isClassAcceptable(Class clazz) { + return baseClass.isAssignableFrom(clazz); + } + + @Override + public List get(Class clazz) { + try { + return resultCache.get(clazz, () -> { + List list = Lists.newArrayList(); + getInternal(clazz, list); + list = ImmutableList.sortedCopyOf(Comparator.comparingInt(JadeProtocol.priorities::byValue), list); + if (singleton && !list.isEmpty()) { + return ImmutableList.of(list.getFirst()); + } + return list; + }); + } catch (ExecutionException e) { + LeavesLogger.LOGGER.warning("HierarchyLookup error", e); + } + return List.of(); + } + + private void getInternal(Class clazz, List list) { + if (clazz != baseClass && clazz != Object.class) { + getInternal(clazz.getSuperclass(), list); + } + list.addAll(objects.get(clazz)); + } + + @Override + public boolean isEmpty() { + return objects.isEmpty(); + } + + @Override + public Stream, Collection>> entries() { + return objects.asMap().entrySet().stream(); + } + + @Override + public void invalidate() { + resultCache.invalidateAll(); + } + + @Override + public void loadComplete(PriorityStore priorityStore) { + objects.asMap().forEach((clazz, list) -> { + if (list.size() < 2) { + return; + } + Set set = Sets.newHashSetWithExpectedSize(list.size()); + for (T provider : list) { + if (set.contains(provider.getUid())) { + throw new IllegalStateException("Duplicate UID: %s for %s".formatted(provider.getUid(), list.stream() + .filter(p -> p.getUid().equals(provider.getUid())) + .map(p -> p.getClass().getName()) + .toList() + )); + } + set.add(provider.getUid()); + } + }); + + objects = ImmutableListMultimap., T>builder() + .orderValuesBy(Comparator.comparingInt(priorityStore::byValue)) + .putAll(objects) + .build(); + + if (idMapped) { + idMapper = createIdMapper(); + } + } + +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java new file mode 100644 index 00000000..0536309c --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java @@ -0,0 +1,66 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.collect.Streams; +import net.minecraft.core.IdMapper; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +public interface IHierarchyLookup { + default IHierarchyLookup cast() { + return this; + } + + void idMapped(); + + @Nullable + IdMapper idMapper(); + + default List mappedIds() { + return Streams.stream(Objects.requireNonNull(idMapper())) + .map(IJadeProvider::getUid) + .toList(); + } + + void register(Class clazz, T provider); + + boolean isClassAcceptable(Class clazz); + + default List get(Object obj) { + if (obj == null) { + return List.of(); + } + return get(obj.getClass()); + } + + List get(Class clazz); + + boolean isEmpty(); + + Stream, Collection>> entries(); + + void invalidate(); + + void loadComplete(PriorityStore priorityStore); + + default IdMapper createIdMapper() { + List list = entries().flatMap(entry -> entry.getValue().stream()).toList(); + IdMapper idMapper = idMapper(); + if (idMapper == null) { + idMapper = new IdMapper<>(list.size()); + } + for (T provider : list) { + if (idMapper.getId(provider) == IdMapper.DEFAULT) { + idMapper.add(provider); + } + } + return idMapper; + } +} + diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java new file mode 100644 index 00000000..408c81a0 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java @@ -0,0 +1,118 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomData; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +public class ItemCollector { + public static final int MAX_SIZE = 54; + public static final ItemCollector EMPTY = new ItemCollector<>(null); + private static final Predicate NON_EMPTY = stack -> { + if (stack.isEmpty()) { + return false; + } + CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + if (customData.contains("CustomModelData")) { + CompoundTag tag = customData.copyTag(); + for (String key : tag.getAllKeys()) { + if (key.toLowerCase(Locale.ENGLISH).endsWith("clear") && tag.getBoolean(key)) { + return false; + } + } + } + return true; + }; + private final Object2IntLinkedOpenHashMap items = new Object2IntLinkedOpenHashMap<>(); + private final ItemIterator iterator; + public long version; + public long lastTimeFinished; + public boolean lastTimeIsEmpty; + public List> mergedResult; + + public ItemCollector(ItemIterator iterator) { + this.iterator = iterator; + } + + public List> update(Accessor request) { + if (iterator == null) { + return null; + } + T container = iterator.find(request.getTarget()); + if (container == null) { + return null; + } + long currentVersion = iterator.getVersion(container); + long gameTime = request.getLevel().getGameTime(); + if (mergedResult != null && iterator.isFinished()) { + if (version == currentVersion) { + return mergedResult; // content not changed + } + if (lastTimeFinished + 5 > gameTime) { + return mergedResult; // avoid update too frequently + } + iterator.reset(); + } + AtomicInteger count = new AtomicInteger(); + iterator.populate(container).forEach(stack -> { + count.incrementAndGet(); + if (NON_EMPTY.test(stack)) { + ItemDefinition def = new ItemDefinition(stack); + items.addTo(def, stack.getCount()); + } + }); + iterator.afterPopulate(count.get()); + if (mergedResult != null && !iterator.isFinished()) { + updateCollectingProgress(mergedResult.getFirst()); + return mergedResult; + } + List partialResult = items.object2IntEntrySet().stream().limit(MAX_SIZE ).map(entry -> { + ItemDefinition def = entry.getKey(); + return def.toStack(entry.getIntValue()); + }).toList(); + List> groups = List.of(updateCollectingProgress(new ViewGroup<>(partialResult))); + if (iterator.isFinished()) { + mergedResult = groups; + lastTimeIsEmpty = mergedResult.getFirst().views.isEmpty(); + version = currentVersion; + lastTimeFinished = gameTime; + items.clear(); + } + return groups; + } + + protected ViewGroup updateCollectingProgress(ViewGroup group) { + if (lastTimeIsEmpty && group.views.isEmpty()) { + return group; + } + float progress = iterator.getCollectingProgress(); + CompoundTag data = group.getExtraData(); + if (Float.isNaN(progress) || progress >= 1) { + data.remove("Collecting"); + } else { + data.putFloat("Collecting", progress); + } + return group; + } + + public record ItemDefinition(Item item, DataComponentPatch components) { + ItemDefinition(ItemStack stack) { + this(stack.getItem(), stack.getComponentsPatch()); + } + + public ItemStack toStack(int count) { + ItemStack itemStack = new ItemStack(item, count); + itemStack.applyComponents(components); + return itemStack; + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java new file mode 100644 index 00000000..4d65e9a8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java @@ -0,0 +1,102 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public abstract class ItemIterator { + public static final AtomicLong version = new AtomicLong(); + protected final Function containerFinder; + protected final int fromIndex; + protected boolean finished; + protected int currentIndex; + + protected ItemIterator(Function containerFinder, int fromIndex) { + this.containerFinder = containerFinder; + this.currentIndex = this.fromIndex = fromIndex; + } + + public @Nullable T find(Object target) { + return containerFinder.apply(target); + } + + public final boolean isFinished() { + return finished; + } + + public long getVersion(T container) { + return version.getAndIncrement(); + } + + public abstract Stream populate(T container); + + public void reset() { + currentIndex = fromIndex; + finished = false; + } + + public void afterPopulate(int count) { + currentIndex += count; + if (count == 0 || currentIndex >= 10000) { + finished = true; + } + } + + public float getCollectingProgress() { + return Float.NaN; + } + + public static abstract class SlottedItemIterator extends ItemIterator { + protected float progress; + + public SlottedItemIterator(Function containerFinder, int fromIndex) { + super(containerFinder, fromIndex); + } + + protected abstract int getSlotCount(T container); + + protected abstract ItemStack getItemInSlot(T container, int slot); + + @Override + public Stream populate(T container) { + int slotCount = getSlotCount(container); + int toIndex = currentIndex + ItemCollector.MAX_SIZE * 2; + if (toIndex >= slotCount) { + toIndex = slotCount; + finished = true; + } + progress = (float) (currentIndex - fromIndex) / (slotCount - fromIndex); + return IntStream.range(currentIndex, toIndex).mapToObj(slot -> getItemInSlot(container, slot)); + } + + @Override + public float getCollectingProgress() { + return progress; + } + } + + public static class ContainerItemIterator extends SlottedItemIterator { + public ContainerItemIterator(int fromIndex) { + this(Container.class::cast, fromIndex); + } + + public ContainerItemIterator(Function containerFinder, int fromIndex) { + super(containerFinder, fromIndex); + } + + @Override + protected int getSlotCount(Container container) { + return container.getContainerSize(); + } + + @Override + protected ItemStack getItemInSlot(Container container, int slot) { + return container.getItem(slot); + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java new file mode 100644 index 00000000..a046ae4e --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java @@ -0,0 +1,59 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.NotNull; + +public class JadeCodec { + public static final StreamCodec PRIMITIVE_STREAM_CODEC = new StreamCodec<>() { + @Override + public @NotNull Object decode(@NotNull ByteBuf buf) { + byte b = buf.readByte(); + if (b == 0) { + return false; + } else if (b == 1) { + return true; + } else if (b == 2) { + return ByteBufCodecs.VAR_INT.decode(buf); + } else if (b == 3) { + return ByteBufCodecs.FLOAT.decode(buf); + } else if (b == 4) { + return ByteBufCodecs.STRING_UTF8.decode(buf); + } else if (b > 20) { + return b - 20; + } + throw new IllegalArgumentException("Unknown primitive type: " + b); + } + + @Override + public void encode(@NotNull ByteBuf buf, @NotNull Object o) { + switch (o) { + case Boolean b -> buf.writeByte(b ? 1 : 0); + case Number n -> { + float f = n.floatValue(); + if (f != (int) f) { + buf.writeByte(3); + ByteBufCodecs.FLOAT.encode(buf, f); + } + int i = n.intValue(); + if (i <= Byte.MAX_VALUE - 20 && i >= 0) { + buf.writeByte(i + 20); + } else { + ByteBufCodecs.VAR_INT.encode(buf, i); + } + } + case String s -> { + buf.writeByte(4); + ByteBufCodecs.STRING_UTF8.encode(buf, s); + } + case Enum anEnum -> { + buf.writeByte(4); + ByteBufCodecs.STRING_UTF8.encode(buf, anEnum.name()); + } + case null -> throw new NullPointerException(); + default -> throw new IllegalArgumentException("Unknown primitive type: %s (%s)".formatted(o, o.getClass())); + } + } + }; +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java new file mode 100644 index 00000000..81575cfa --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java @@ -0,0 +1,109 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.collect.Lists; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.AlternativesEntry; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.entries.NestedLootTable; +import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.level.storage.loot.predicates.MatchTool; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.tool.ShearsToolHandler; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +public class LootTableMineableCollector { + + private final HolderGetter lootRegistry; + private final ItemStack toolItem; + + public LootTableMineableCollector(HolderGetter lootRegistry, ItemStack toolItem) { + this.lootRegistry = lootRegistry; + this.toolItem = toolItem; + } + + public static @NotNull List execute(HolderGetter lootRegistry, ItemStack toolItem) { + LootTableMineableCollector collector = new LootTableMineableCollector(lootRegistry, toolItem); + List list = Lists.newArrayList(); + for (Block block : BuiltInRegistries.BLOCK) { + if (!ShearsToolHandler.getInstance().test(block.defaultBlockState()).isEmpty()) { + continue; + } + + if (block.getLootTable().isPresent()) { + LootTable lootTable = lootRegistry.get(block.getLootTable().get()).map(Holder::value).orElse(null); + if (collector.doLootTable(lootTable)) { + list.add(block); + } + } + } + return list; + } + + private boolean doLootTable(LootTable lootTable) { + if (lootTable == null || lootTable == LootTable.EMPTY) { + return false; + } + + for (LootPool pool : lootTable.pools) { + if (doLootPool(pool)) { + return true; + } + } + return false; + } + + private boolean doLootPool(@NotNull LootPool lootPool) { + for (LootPoolEntryContainer entry : lootPool.entries) { + if (doLootPoolEntry(entry)) { + return true; + } + } + return false; + } + + private boolean doLootPoolEntry(LootPoolEntryContainer entry) { + if (entry instanceof AlternativesEntry alternativesEntry) { + for (LootPoolEntryContainer child : alternativesEntry.children) { + if (doLootPoolEntry(child)) { + return true; + } + } + } else if (entry instanceof NestedLootTable nestedLootTable) { + LootTable lootTable = nestedLootTable.contents.map($ -> lootRegistry.get($).map(Holder::value).orElse(null), Function.identity()); + return doLootTable(lootTable); + } else { + return isCorrectConditions(entry.conditions, toolItem); + } + return false; + } + + public static boolean isCorrectConditions(@NotNull List conditions, ItemStack toolItem) { + if (conditions.size() != 1) { + return false; + } + + LootItemCondition condition = conditions.getFirst(); + if (condition instanceof MatchTool(Optional predicate)) { + ItemPredicate itemPredicate = predicate.orElse(null); + return itemPredicate != null && itemPredicate.test(toolItem); + } else if (condition instanceof AnyOfCondition anyOfCondition) { + for (LootItemCondition child : anyOfCondition.terms) { + if (isCorrectConditions(List.of(child), toolItem)) { + return true; + } + } + } + return false; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java new file mode 100644 index 00000000..cb5c8201 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java @@ -0,0 +1,120 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import net.minecraft.core.IdMapper; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +public class PairHierarchyLookup implements IHierarchyLookup { + public final IHierarchyLookup first; + public final IHierarchyLookup second; + private final Cache, Class>, List> mergedCache = CacheBuilder.newBuilder().build(); + protected boolean idMapped; + @Nullable + protected IdMapper idMapper; + + public PairHierarchyLookup(IHierarchyLookup first, IHierarchyLookup second) { + this.first = first; + this.second = second; + } + + @SuppressWarnings("unchecked") + public List getMerged(Object first, Object second) { + Objects.requireNonNull(first); + Objects.requireNonNull(second); + try { + return (List) mergedCache.get(Pair.of(first.getClass(), second.getClass()), () -> { + List firstList = this.first.get(first); + List secondList = this.second.get(second); + if (firstList.isEmpty()) { + return secondList; + } else if (secondList.isEmpty()) { + return firstList; + } + return ImmutableList.sortedCopyOf( + Comparator.comparingInt(JadeProtocol.priorities::byValue), + Iterables.concat(firstList, secondList) + ); + }); + } catch (ExecutionException e) { + LeavesLogger.LOGGER.severe(e.toString()); + } + return List.of(); + } + + @Override + public void idMapped() { + idMapped = true; + } + + @Override + public @Nullable IdMapper idMapper() { + return idMapper; + } + + @Override + public void register(Class clazz, T provider) { + if (first.isClassAcceptable(clazz)) { + first.register(clazz, provider); + } else if (second.isClassAcceptable(clazz)) { + second.register(clazz, provider); + } else { + throw new IllegalArgumentException("Class " + clazz + " is not acceptable"); + } + } + + @Override + public boolean isClassAcceptable(Class clazz) { + return first.isClassAcceptable(clazz) || second.isClassAcceptable(clazz); + } + + @Override + public List get(Class clazz) { + List result = first.get(clazz); + if (result.isEmpty()) { + result = second.get(clazz); + } + return result; + } + + @Override + public boolean isEmpty() { + return first.isEmpty() && second.isEmpty(); + } + + @Override + public Stream, Collection>> entries() { + return Stream.concat(first.entries(), second.entries()); + } + + @Override + public void invalidate() { + first.invalidate(); + second.invalidate(); + mergedCache.invalidateAll(); + } + + @Override + public void loadComplete(PriorityStore priorityStore) { + first.loadComplete(priorityStore); + second.loadComplete(priorityStore); + if (idMapped) { + idMapper = createIdMapper(); + } + } +} \ No newline at end of file diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java new file mode 100644 index 00000000..da4d5a77 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java @@ -0,0 +1,40 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.ToIntFunction; + +public class PriorityStore { + + private final Object2IntMap priorities = new Object2IntLinkedOpenHashMap<>(); + private final Function keyGetter; + private final ToIntFunction defaultPriorityGetter; + + public PriorityStore(ToIntFunction defaultPriorityGetter, Function keyGetter) { + this.defaultPriorityGetter = defaultPriorityGetter; + this.keyGetter = keyGetter; + } + + public void put(V provider) { + Objects.requireNonNull(provider); + put(provider, defaultPriorityGetter.applyAsInt(provider)); + } + + public void put(V provider, int priority) { + Objects.requireNonNull(provider); + K uid = keyGetter.apply(provider); + Objects.requireNonNull(uid); + priorities.put(uid, priority); + } + + public int byValue(V value) { + return byKey(keyGetter.apply(value)); + } + + public int byKey(K id) { + return priorities.getInt(id); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java new file mode 100644 index 00000000..520eadbf --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java @@ -0,0 +1,62 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import io.netty.buffer.ByteBuf; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ViewGroup { + public static StreamCodec> codec(StreamCodec viewCodec) { + return StreamCodec.composite( + ByteBufCodecs.list().apply(viewCodec), + $ -> $.views, + ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), + $ -> Optional.ofNullable($.id), + ByteBufCodecs.optional(ByteBufCodecs.COMPOUND_TAG), + $ -> Optional.ofNullable($.extraData), + ViewGroup::new); + } + + public static StreamCodec>>> listCodec(StreamCodec viewCodec) { + return StreamCodec.composite( + ResourceLocation.STREAM_CODEC, + Map.Entry::getKey, + ByteBufCodecs.>list().apply(codec(viewCodec)), + Map.Entry::getValue, + Map::entry); + } + + public List views; + @Nullable + public String id; + @Nullable + protected CompoundTag extraData; + + public ViewGroup(List views) { + this(views, Optional.empty(), Optional.empty()); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public ViewGroup(List views, Optional id, Optional extraData) { + this.views = views; + this.id = id.orElse(null); + this.extraData = extraData.orElse(null); + } + + public CompoundTag getExtraData() { + if (extraData == null) { + extraData = new CompoundTag(); + } + return extraData; + } + + public void setProgress(float progress) { + getExtraData().putFloat("Progress", progress); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java new file mode 100644 index 00000000..9b49efd3 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java @@ -0,0 +1,104 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.collect.Lists; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +public class WrappedHierarchyLookup extends HierarchyLookup { + public final List, Function, @Nullable Object>>> overrides = Lists.newArrayList(); + private boolean empty = true; + + public WrappedHierarchyLookup() { + super(Object.class); + } + + @NotNull + public static WrappedHierarchyLookup forAccessor() { + WrappedHierarchyLookup lookup = new WrappedHierarchyLookup<>(); + lookup.overrides.add(Pair.of( + new HierarchyLookup<>(Block.class), accessor -> { + if (accessor instanceof BlockAccessor blockAccessor) { + return blockAccessor.getBlock(); + } + return null; + })); + return lookup; + } + + public List wrappedGet(Accessor accessor) { + List list = Lists.newArrayList(); + for (var override : overrides) { + Object o = override.getRight().apply(accessor); + if (o != null) { + list.addAll(override.getLeft().get(o)); + } + } + list.addAll(get(accessor.getTarget())); + return list; + } + + @Override + public void register(Class clazz, T provider) { + for (var override : overrides) { + if (override.getLeft().isClassAcceptable(clazz)) { + override.getLeft().register(clazz, provider); + empty = false; + return; + } + } + super.register(clazz, provider); + empty = false; + } + + @Override + public boolean isClassAcceptable(Class clazz) { + for (var override : overrides) { + if (override.getLeft().isClassAcceptable(clazz)) { + return true; + } + } + return super.isClassAcceptable(clazz); + } + + @Override + public void invalidate() { + for (var override : overrides) { + override.getLeft().invalidate(); + } + super.invalidate(); + } + + @Override + public void loadComplete(PriorityStore priorityStore) { + for (var override : overrides) { + override.getLeft().loadComplete(priorityStore); + } + super.loadComplete(priorityStore); + } + + @Override + public boolean isEmpty() { + return empty; + } + + @Override + public Stream, Collection>> entries() { + Stream, Collection>> stream = super.entries(); + for (var override : overrides) { + stream = Stream.concat(stream, override.getLeft().entries()); + } + return stream; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java new file mode 100644 index 00000000..c0397652 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java @@ -0,0 +1,391 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.mojang.authlib.GameProfile; +import io.netty.buffer.Unpooled; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.LeavesProtocolManager; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; +import org.leavesmc.leaves.protocol.syncmatica.exchange.DownloadExchange; +import org.leavesmc.leaves.protocol.syncmatica.exchange.Exchange; +import org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget; +import org.leavesmc.leaves.protocol.syncmatica.exchange.ModifyExchangeServer; +import org.leavesmc.leaves.protocol.syncmatica.exchange.UploadExchange; +import org.leavesmc.leaves.protocol.syncmatica.exchange.VersionHandshakeServer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@LeavesProtocol(namespace = "syncmatica") +public class CommunicationManager { + + private static final Map> downloadingFile = new HashMap<>(); + private static final Map playerMap = new HashMap<>(); + + protected static final Collection broadcastTargets = new ArrayList<>(); + + protected static final Map downloadState = new HashMap<>(); + protected static final Map modifyState = new HashMap<>(); + + protected static final Rotation[] rotOrdinals = Rotation.values(); + protected static final Mirror[] mirOrdinals = Mirror.values(); + + public CommunicationManager() { + } + + public static boolean shouldEnable() { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaProtocol; + } + + public static GameProfile getGameProfile(final ExchangeTarget exchangeTarget) { + return playerMap.get(exchangeTarget).getGameProfile(); + } + + public void sendMessage(final @NotNull ExchangeTarget client, final MessageType type, final String identifier) { + if (client.getFeatureSet().hasFeature(Feature.MESSAGE)) { + final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer()); + newPacketBuf.writeUtf(type.toString()); + newPacketBuf.writeUtf(identifier); + client.sendPacket(PacketType.MESSAGE.identifier, newPacketBuf); + } else if (playerMap.containsKey(client)) { + final ServerPlayer player = playerMap.get(client); + player.sendSystemMessage(Component.literal("Syncmatica " + type.toString() + " " + identifier)); + } + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerJoin(ServerPlayer player) { + final ExchangeTarget newPlayer = player.connection.exchangeTarget; + final VersionHandshakeServer hi = new VersionHandshakeServer(newPlayer); + playerMap.put(newPlayer, player); + final GameProfile profile = player.getGameProfile(); + SyncmaticaProtocol.getPlayerIdentifierProvider().updateName(profile.getId(), profile.getName()); + startExchangeUnchecked(hi); + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLeave(ServerPlayer player) { + final ExchangeTarget oldPlayer = player.connection.exchangeTarget; + final Collection potentialMessageTarget = oldPlayer.getExchanges(); + if (potentialMessageTarget != null) { + for (final Exchange target : potentialMessageTarget) { + target.close(false); + handleExchange(target); + } + } + broadcastTargets.remove(oldPlayer); + playerMap.remove(oldPlayer); + } + + @ProtocolHandler.PayloadReceiver(payload = SyncmaticaPayload.class, payloadId = "main") + public static void onPacketGet(ServerPlayer player, SyncmaticaPayload payload) { + onPacket(player.connection.exchangeTarget, payload.packetType(), payload.data()); + } + + public static void onPacket(final @NotNull ExchangeTarget source, final ResourceLocation id, final FriendlyByteBuf packetBuf) { + Exchange handler = null; + final Collection potentialMessageTarget = source.getExchanges(); + if (potentialMessageTarget != null) { + for (final Exchange target : potentialMessageTarget) { + if (target.checkPacket(id, packetBuf)) { + target.handle(id, packetBuf); + handler = target; + break; + } + } + } + if (handler == null) { + handle(source, id, packetBuf); + } else if (handler.isFinished()) { + notifyClose(handler); + } + } + + protected static void handle(ExchangeTarget source, @NotNull ResourceLocation id, FriendlyByteBuf packetBuf) { + if (id.equals(PacketType.REQUEST_LITEMATIC.identifier)) { + final UUID syncmaticaId = packetBuf.readUUID(); + final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(syncmaticaId); + if (placement == null) { + return; + } + final File toUpload = SyncmaticaProtocol.getFileStorage().getLocalLitematic(placement); + final UploadExchange upload; + try { + upload = new UploadExchange(placement, toUpload, source); + } catch (final FileNotFoundException e) { + e.printStackTrace(); + return; + } + startExchange(upload); + return; + } + if (id.equals(PacketType.REGISTER_METADATA.identifier)) { + final ServerPlacement placement = receiveMetaData(packetBuf, source); + if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) { + cancelShare(source, placement); + return; + } + + final GameProfile profile = playerMap.get(source).getGameProfile(); + final PlayerIdentifier playerIdentifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(profile); + if (!placement.getOwner().equals(playerIdentifier)) { + placement.setOwner(playerIdentifier); + placement.setLastModifiedBy(playerIdentifier); + } + + if (!SyncmaticaProtocol.getFileStorage().getLocalState(placement).isLocalFileReady()) { + if (SyncmaticaProtocol.getFileStorage().getLocalState(placement) == LocalLitematicState.DOWNLOADING_LITEMATIC) { + downloadingFile.computeIfAbsent(placement.getHash(), key -> new ArrayList<>()).add(placement); + return; + } + try { + download(placement, source); + } catch (final Exception e) { + e.printStackTrace(); + } + return; + } + + addPlacement(source, placement); + return; + } + if (id.equals(PacketType.REMOVE_SYNCMATIC.identifier)) { + final UUID placementId = packetBuf.readUUID(); + final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId); + if (placement != null) { + if (!getGameProfile(source).getId().equals(placement.getOwner().uuid)) { + return; + } + + final Exchange modifier = getModifier(placement); + if (modifier != null) { + modifier.close(true); + notifyClose(modifier); + } + SyncmaticaProtocol.getSyncmaticManager().removePlacement(placement); + for (final ExchangeTarget client : broadcastTargets) { + final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer()); + newPacketBuf.writeUUID(placement.getId()); + client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, newPacketBuf); + } + } + } + if (id.equals(PacketType.MODIFY_REQUEST.identifier)) { + final UUID placementId = packetBuf.readUUID(); + final ModifyExchangeServer modifier = new ModifyExchangeServer(placementId, source); + startExchange(modifier); + } + } + + protected static void handleExchange(Exchange exchange) { + if (exchange instanceof DownloadExchange) { + final ServerPlacement p = ((DownloadExchange) exchange).getPlacement(); + + if (exchange.isSuccessful()) { + addPlacement(exchange.getPartner(), p); + if (downloadingFile.containsKey(p.getHash())) { + for (final ServerPlacement placement : downloadingFile.get(p.getHash())) { + addPlacement(exchange.getPartner(), placement); + } + } + } else { + cancelShare(exchange.getPartner(), p); + if (downloadingFile.containsKey(p.getHash())) { + for (final ServerPlacement placement : downloadingFile.get(p.getHash())) { + cancelShare(exchange.getPartner(), placement); + } + } + } + + downloadingFile.remove(p.getHash()); + return; + } + if (exchange instanceof VersionHandshakeServer && exchange.isSuccessful()) { + broadcastTargets.add(exchange.getPartner()); + } + if (exchange instanceof ModifyExchangeServer && exchange.isSuccessful()) { + final ServerPlacement placement = ((ModifyExchangeServer) exchange).getPlacement(); + for (final ExchangeTarget client : broadcastTargets) { + if (client.getFeatureSet().hasFeature(Feature.MODIFY)) { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeUUID(placement.getId()); + putPositionData(placement, buf, client); + if (client.getFeatureSet().hasFeature(Feature.CORE_EX)) { + buf.writeUUID(placement.getLastModifiedBy().uuid); + buf.writeUtf(placement.getLastModifiedBy().getName()); + } + client.sendPacket(PacketType.MODIFY.identifier, buf); + } else { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeUUID(placement.getId()); + client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, buf); + sendMetaData(placement, client); + } + } + } + } + + private static void addPlacement(final ExchangeTarget t, final @NotNull ServerPlacement placement) { + if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) { + cancelShare(t, placement); + return; + } + SyncmaticaProtocol.getSyncmaticManager().addPlacement(placement); + for (final ExchangeTarget target : broadcastTargets) { + sendMetaData(placement, target); + } + } + + private static void cancelShare(final @NotNull ExchangeTarget source, final @NotNull ServerPlacement placement) { + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(placement.getId()); + source.sendPacket(PacketType.CANCEL_SHARE.identifier, FriendlyByteBuf); + } + + public static void sendMetaData(final ServerPlacement metaData, final ExchangeTarget target) { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + putMetaData(metaData, buf, target); + target.sendPacket(PacketType.REGISTER_METADATA.identifier, buf); + } + + public static void putMetaData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { + buf.writeUUID(metaData.getId()); + + buf.writeUtf(SyncmaticaProtocol.sanitizeFileName(metaData.getName())); + buf.writeUUID(metaData.getHash()); + + if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { + buf.writeUUID(metaData.getOwner().uuid); + buf.writeUtf(metaData.getOwner().getName()); + buf.writeUUID(metaData.getLastModifiedBy().uuid); + buf.writeUtf(metaData.getLastModifiedBy().getName()); + } + + putPositionData(metaData, buf, exchangeTarget); + } + + public static void putPositionData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { + buf.writeBlockPos(metaData.getPosition()); + buf.writeUtf(metaData.getDimension()); + buf.writeInt(metaData.getRotation().ordinal()); + buf.writeInt(metaData.getMirror().ordinal()); + + if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { + if (metaData.getSubRegionData().getModificationData() == null) { + buf.writeInt(0); + return; + } + + final Collection regionData = metaData.getSubRegionData().getModificationData().values(); + buf.writeInt(regionData.size()); + + for (final SubRegionPlacementModification subPlacement : regionData) { + buf.writeUtf(subPlacement.name); + buf.writeBlockPos(subPlacement.position); + buf.writeInt(subPlacement.rotation.ordinal()); + buf.writeInt(subPlacement.mirror.ordinal()); + } + } + } + + public static ServerPlacement receiveMetaData(final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { + final UUID id = buf.readUUID(); + + final String fileName = SyncmaticaProtocol.sanitizeFileName(buf.readUtf(32767)); + final UUID hash = buf.readUUID(); + + PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER; + PlayerIdentifier lastModifiedBy = PlayerIdentifier.MISSING_PLAYER; + + if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { + final PlayerIdentifierProvider provider = SyncmaticaProtocol.getPlayerIdentifierProvider(); + owner = provider.createOrGet(buf.readUUID(), buf.readUtf(32767)); + lastModifiedBy = provider.createOrGet(buf.readUUID(), buf.readUtf(32767)); + } + + final ServerPlacement placement = new ServerPlacement(id, fileName, hash, owner); + placement.setLastModifiedBy(lastModifiedBy); + + receivePositionData(placement, buf, exchangeTarget); + + return placement; + } + + public static void receivePositionData(final @NotNull ServerPlacement placement, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { + final BlockPos pos = buf.readBlockPos(); + final String dimensionId = buf.readUtf(32767); + final Rotation rot = rotOrdinals[buf.readInt()]; + final Mirror mir = mirOrdinals[buf.readInt()]; + placement.move(dimensionId, pos, rot, mir); + + if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { + final SubRegionData subRegionData = placement.getSubRegionData(); + subRegionData.reset(); + final int limit = buf.readInt(); + for (int i = 0; i < limit; i++) { + subRegionData.modify(buf.readUtf(32767), buf.readBlockPos(), rotOrdinals[buf.readInt()], mirOrdinals[buf.readInt()]); + } + } + } + + public static void download(final ServerPlacement syncmatic, final ExchangeTarget source) throws NoSuchAlgorithmException, IOException { + if (!SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).isReadyForDownload()) { + throw new IllegalArgumentException(syncmatic.toString() + " is not ready for download local state is: " + SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).toString()); + } + final File toDownload = SyncmaticaProtocol.getFileStorage().createLocalLitematic(syncmatic); + final Exchange downloadExchange = new DownloadExchange(syncmatic, toDownload, source); + setDownloadState(syncmatic, true); + startExchange(downloadExchange); + } + + public static void setDownloadState(final @NotNull ServerPlacement syncmatic, final boolean b) { + downloadState.put(syncmatic.getHash(), b); + } + + public static boolean getDownloadState(final @NotNull ServerPlacement syncmatic) { + return downloadState.getOrDefault(syncmatic.getHash(), false); + } + + public static void setModifier(final @NotNull ServerPlacement syncmatic, final Exchange exchange) { + modifyState.put(syncmatic.getHash(), exchange); + } + + public static Exchange getModifier(final @NotNull ServerPlacement syncmatic) { + return modifyState.get(syncmatic.getHash()); + } + + public static void startExchange(final @NotNull Exchange newExchange) { + if (!broadcastTargets.contains(newExchange.getPartner())) { + throw new IllegalArgumentException(newExchange.getPartner().toString() + " is not a valid ExchangeTarget"); + } + startExchangeUnchecked(newExchange); + } + + protected static void startExchangeUnchecked(final @NotNull Exchange newExchange) { + newExchange.getPartner().getExchanges().add(newExchange); + newExchange.init(); + if (newExchange.isFinished()) { + notifyClose(newExchange); + } + } + + public static void notifyClose(final @NotNull Exchange e) { + e.getPartner().getExchanges().remove(e); + handleExchange(e); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java new file mode 100644 index 00000000..7cb3465b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java @@ -0,0 +1,23 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import org.jetbrains.annotations.Nullable; + +public enum Feature { + CORE, + FEATURE, + MODIFY, + MESSAGE, + QUOTA, + DEBUG, + CORE_EX; + + @Nullable + public static Feature fromString(final String s) { + for (final Feature f : Feature.values()) { + if (f.toString().equals(s)) { + return f; + } + } + return null; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java new file mode 100644 index 00000000..ddd0f498 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java @@ -0,0 +1,67 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class FeatureSet { + + private static final Map versionFeatures; + private final Collection features; + + @Nullable + public static FeatureSet fromVersionString(@NotNull String version) { + if (version.matches("^\\d+(\\.\\d+){2,4}$")) { + final int minSize = version.indexOf("."); + while (version.length() > minSize) { + if (versionFeatures.containsKey(version)) { + return versionFeatures.get(version); + } + final int lastDot = version.lastIndexOf("."); + version = version.substring(0, lastDot); + } + } + return null; + } + + @NotNull + public static FeatureSet fromString(final @NotNull String features) { + final FeatureSet featureSet = new FeatureSet(new ArrayList<>()); + for (final String feature : features.split("\n")) { + final Feature f = Feature.fromString(feature); + if (f != null) { + featureSet.features.add(f); + } + } + return featureSet; + } + + @Override + public String toString() { + final StringBuilder output = new StringBuilder(); + boolean b = false; + for (final Feature feature : features) { + output.append(b ? "\n" + feature.toString() : feature.toString()); + b = true; + } + return output.toString(); + } + + public FeatureSet(final Collection features) { + this.features = features; + } + + public boolean hasFeature(final Feature f) { + return features.contains(f); + } + + static { + versionFeatures = new HashMap<>(); + versionFeatures.put("0.1", new FeatureSet(Collections.singletonList(Feature.CORE))); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java new file mode 100644 index 00000000..9139394e --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java @@ -0,0 +1,80 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.UUID; + +public class FileStorage { + + private final HashMap buffer = new HashMap<>(); + + public LocalLitematicState getLocalState(final ServerPlacement placement) { + final File localFile = getSchematicPath(placement); + if (localFile.isFile()) { + if (isDownloading(placement)) { + return LocalLitematicState.DOWNLOADING_LITEMATIC; + } + if ((buffer.containsKey(placement) && buffer.get(placement) == localFile.lastModified()) || hashCompare(localFile, placement)) { + return LocalLitematicState.LOCAL_LITEMATIC_PRESENT; + } + return LocalLitematicState.LOCAL_LITEMATIC_DESYNC; + } + return LocalLitematicState.NO_LOCAL_LITEMATIC; + } + + private boolean isDownloading(final ServerPlacement placement) { + return SyncmaticaProtocol.getCommunicationManager().getDownloadState(placement); + } + + public File getLocalLitematic(final ServerPlacement placement) { + if (getLocalState(placement).isLocalFileReady()) { + return getSchematicPath(placement); + } else { + return null; + } + } + + public File createLocalLitematic(final ServerPlacement placement) { + if (getLocalState(placement).isLocalFileReady()) { + throw new IllegalArgumentException(""); + } + final File file = getSchematicPath(placement); + if (file.exists()) { + file.delete(); + } + try { + file.createNewFile(); + } catch (final IOException e) { + e.printStackTrace(); + } + return file; + } + + private boolean hashCompare(final File localFile, final ServerPlacement placement) { + UUID hash = null; + try { + hash = SyncmaticaProtocol.createChecksum(new FileInputStream(localFile)); + } catch (final Exception e) { + e.printStackTrace(); + } + + if (hash == null) { + return false; + } + if (hash.equals(placement.getHash())) { + buffer.put(placement, localFile.lastModified()); + return true; + } + return false; + } + + @Contract("_ -> new") + private @NotNull File getSchematicPath(final @NotNull ServerPlacement placement) { + return new File(SyncmaticaProtocol.getLitematicFolder(), placement.getHash().toString() + ".litematic"); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java new file mode 100644 index 00000000..299c5739 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java @@ -0,0 +1,24 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +public enum LocalLitematicState { + NO_LOCAL_LITEMATIC(true, false), + LOCAL_LITEMATIC_DESYNC(true, false), + DOWNLOADING_LITEMATIC(false, false), + LOCAL_LITEMATIC_PRESENT(false, true); + + private final boolean downloadReady; + private final boolean fileReady; + + LocalLitematicState(final boolean downloadReady, final boolean fileReady) { + this.downloadReady = downloadReady; + this.fileReady = fileReady; + } + + public boolean isReadyForDownload() { + return downloadReady; + } + + public boolean isLocalFileReady() { + return fileReady; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java new file mode 100644 index 00000000..b56ca12c --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java @@ -0,0 +1,8 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +public enum MessageType { + SUCCESS, + INFO, + WARNING, + ERROR +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java new file mode 100644 index 00000000..36c87c5c --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java @@ -0,0 +1,30 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import net.minecraft.resources.ResourceLocation; + +public enum PacketType { + REGISTER_METADATA("register_metadata"), + CANCEL_SHARE("cancel_share"), + REQUEST_LITEMATIC("request_download"), + SEND_LITEMATIC("send_litematic"), + RECEIVED_LITEMATIC("received_litematic"), + FINISHED_LITEMATIC("finished_litematic"), + CANCEL_LITEMATIC("cancel_litematic"), + REMOVE_SYNCMATIC("remove_syncmatic"), + REGISTER_VERSION("register_version"), + CONFIRM_USER("confirm_user"), + FEATURE_REQUEST("feature_request"), + FEATURE("feature"), + MODIFY("modify"), + MODIFY_REQUEST("modify_request"), + MODIFY_REQUEST_DENY("modify_request_deny"), + MODIFY_REQUEST_ACCEPT("modify_request_accept"), + MODIFY_FINISH("modify_finish"), + MESSAGE("mesage"); + + public final ResourceLocation identifier; + + PacketType(final String id) { + identifier = new ResourceLocation(SyncmaticaProtocol.PROTOCOL_ID, id); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java new file mode 100644 index 00000000..b5891b0b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java @@ -0,0 +1,37 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.UUID; + +public class PlayerIdentifier { + + public static final UUID MISSING_PLAYER_UUID = UUID.fromString("4c1b738f-56fa-4011-8273-498c972424ea"); + public static final PlayerIdentifier MISSING_PLAYER = new PlayerIdentifier(MISSING_PLAYER_UUID, "No Player"); + + public final UUID uuid; + private String bufferedPlayerName; + + PlayerIdentifier(final UUID uuid, final String bufferedPlayerName) { + this.uuid = uuid; + this.bufferedPlayerName = bufferedPlayerName; + } + + public String getName() { + return bufferedPlayerName; + } + + public void updatePlayerName(final String name) { + bufferedPlayerName = name; + } + + public JsonObject toJson() { + final JsonObject jsonObject = new JsonObject(); + + jsonObject.add("uuid", new JsonPrimitive(uuid.toString())); + jsonObject.add("name", new JsonPrimitive(bufferedPlayerName)); + + return jsonObject; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java new file mode 100644 index 00000000..df2254ed --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class PlayerIdentifierProvider { + + private final Map identifiers = new HashMap<>(); + + public PlayerIdentifierProvider() { + identifiers.put(PlayerIdentifier.MISSING_PLAYER_UUID, PlayerIdentifier.MISSING_PLAYER); + } + + public PlayerIdentifier createOrGet(final ExchangeTarget exchangeTarget) { + return createOrGet(SyncmaticaProtocol.getCommunicationManager().getGameProfile(exchangeTarget)); + } + + public PlayerIdentifier createOrGet(final @NotNull GameProfile gameProfile) { + return createOrGet(gameProfile.getId(), gameProfile.getName()); + } + + public PlayerIdentifier createOrGet(final UUID uuid, final String playerName) { + return identifiers.computeIfAbsent(uuid, id -> new PlayerIdentifier(uuid, playerName)); + } + + public void updateName(final UUID uuid, final String playerName) { + createOrGet(uuid, playerName).updatePlayerName(playerName); + } + + public PlayerIdentifier fromJson(final @NotNull JsonObject obj) { + if (!obj.has("uuid") || !obj.has("name")) { + return PlayerIdentifier.MISSING_PLAYER; + } + + final UUID jsonUUID = UUID.fromString(obj.get("uuid").getAsString()); + return identifiers.computeIfAbsent(jsonUUID, + key -> new PlayerIdentifier(jsonUUID, obj.get("name").getAsString()) + ); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java new file mode 100644 index 00000000..70759c9d --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java @@ -0,0 +1,166 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class ServerPlacement { + + private final UUID id; + + private final String fileName; + private final UUID hashValue; + + private PlayerIdentifier owner; + private PlayerIdentifier lastModifiedBy; + + private ServerPosition origin; + private Rotation rotation; + private Mirror mirror; + + private SubRegionData subRegionData = new SubRegionData(); + + public ServerPlacement(final UUID id, final String fileName, final UUID hashValue, final PlayerIdentifier owner) { + this.id = id; + this.fileName = fileName; + this.hashValue = hashValue; + this.owner = owner; + lastModifiedBy = owner; + } + + public UUID getId() { + return id; + } + + public String getName() { + return fileName; + } + + public UUID getHash() { + return hashValue; + } + + public String getDimension() { + return origin.getDimensionId(); + } + + public BlockPos getPosition() { + return origin.getBlockPosition(); + } + + public ServerPosition getOrigin() { + return origin; + } + + public Rotation getRotation() { + return rotation; + } + + public Mirror getMirror() { + return mirror; + } + + public ServerPlacement move(final String dimensionId, final BlockPos origin, final Rotation rotation, final Mirror mirror) { + move(new ServerPosition(origin, dimensionId), rotation, mirror); + return this; + } + + public ServerPlacement move(final ServerPosition origin, final Rotation rotation, final Mirror mirror) { + this.origin = origin; + this.rotation = rotation; + this.mirror = mirror; + return this; + } + + public PlayerIdentifier getOwner() { + return owner; + } + + public void setOwner(final PlayerIdentifier playerIdentifier) { + owner = playerIdentifier; + } + + public PlayerIdentifier getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(final PlayerIdentifier lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public SubRegionData getSubRegionData() { + return subRegionData; + } + + public JsonObject toJson() { + final JsonObject obj = new JsonObject(); + obj.add("id", new JsonPrimitive(id.toString())); + + obj.add("file_name", new JsonPrimitive(fileName)); + obj.add("hash", new JsonPrimitive(hashValue.toString())); + + obj.add("origin", origin.toJson()); + obj.add("rotation", new JsonPrimitive(rotation.name())); + obj.add("mirror", new JsonPrimitive(mirror.name())); + + obj.add("owner", owner.toJson()); + if (!owner.equals(lastModifiedBy)) { + obj.add("lastModifiedBy", lastModifiedBy.toJson()); + } + + if (subRegionData.isModified()) { + obj.add("subregionData", subRegionData.toJson()); + } + + return obj; + } + + @Nullable + public static ServerPlacement fromJson(final @NotNull JsonObject obj) { + if (obj.has("id") + && obj.has("file_name") + && obj.has("hash") + && obj.has("origin") + && obj.has("rotation") + && obj.has("mirror")) { + final UUID id = UUID.fromString(obj.get("id").getAsString()); + final String name = obj.get("file_name").getAsString(); + final UUID hashValue = UUID.fromString(obj.get("hash").getAsString()); + + PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER; + if (obj.has("owner")) { + owner = SyncmaticaProtocol.getPlayerIdentifierProvider().fromJson(obj.get("owner").getAsJsonObject()); + } + + final ServerPlacement newPlacement = new ServerPlacement(id, name, hashValue, owner); + final ServerPosition pos = ServerPosition.fromJson(obj.get("origin").getAsJsonObject()); + if (pos == null) { + return null; + } + newPlacement.origin = pos; + newPlacement.rotation = Rotation.valueOf(obj.get("rotation").getAsString()); + newPlacement.mirror = Mirror.valueOf(obj.get("mirror").getAsString()); + + if (obj.has("lastModifiedBy")) { + newPlacement.lastModifiedBy = SyncmaticaProtocol.getPlayerIdentifierProvider() + .fromJson(obj.get("lastModifiedBy").getAsJsonObject()); + } else { + newPlacement.lastModifiedBy = owner; + } + + if (obj.has("subregionData")) { + newPlacement.subRegionData = SubRegionData.fromJson(obj.get("subregionData")); + } + + return newPlacement; + } + + return null; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java new file mode 100644 index 00000000..9775c6c4 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.core.BlockPos; + +public class ServerPosition { + + private final BlockPos position; + private final String dimensionId; + + public ServerPosition(final BlockPos pos, final String dim) { + position = pos; + dimensionId = dim; + } + + public BlockPos getBlockPosition() { + return position; + } + + public String getDimensionId() { + return dimensionId; + } + + public JsonObject toJson() { + final JsonObject obj = new JsonObject(); + final JsonArray arr = new JsonArray(); + arr.add(new JsonPrimitive(position.getX())); + arr.add(new JsonPrimitive(position.getY())); + arr.add(new JsonPrimitive(position.getZ())); + obj.add("position", arr); + obj.add("dimension", new JsonPrimitive(dimensionId)); + return obj; + } + + public static ServerPosition fromJson(final JsonObject obj) { + if (obj.has("position") && obj.has("dimension")) { + final int x; + final int y; + final int z; + final JsonArray arr = obj.get("position").getAsJsonArray(); + x = arr.get(0).getAsInt(); + y = arr.get(1).getAsInt(); + z = arr.get(2).getAsInt(); + final BlockPos pos = new BlockPos(x, y, z); + return new ServerPosition(pos, obj.get("dimension").getAsString()); + } + return null; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java new file mode 100644 index 00000000..22fdf92d --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java @@ -0,0 +1,90 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public class SubRegionData { + + private boolean isModified; + private Map modificationData; // is null when isModified is false + + public SubRegionData() { + this(false, null); + } + + public SubRegionData(final boolean isModified, final Map modificationData) { + this.isModified = isModified; + this.modificationData = modificationData; + } + + public void reset() { + isModified = false; + modificationData = null; + } + + public void modify(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { + modify(new SubRegionPlacementModification(name, position, rotation, mirror)); + } + + public void modify(final SubRegionPlacementModification subRegionPlacementModification) { + if (subRegionPlacementModification == null) { + return; + } + isModified = true; + if (modificationData == null) { + modificationData = new HashMap<>(); + } + modificationData.put(subRegionPlacementModification.name, subRegionPlacementModification); + } + + public boolean isModified() { + return isModified; + } + + public Map getModificationData() { + return modificationData; + } + + public JsonElement toJson() { + return modificationDataToJson(); + } + + @NotNull + private JsonElement modificationDataToJson() { + final JsonArray arr = new JsonArray(); + + for (final Map.Entry entry : modificationData.entrySet()) { + arr.add(entry.getValue().toJson()); + } + + return arr; + } + + @NotNull + public static SubRegionData fromJson(final @NotNull JsonElement obj) { + final SubRegionData newSubRegionData = new SubRegionData(); + + newSubRegionData.isModified = true; + + for (final JsonElement modification : obj.getAsJsonArray()) { + newSubRegionData.modify(SubRegionPlacementModification.fromJson(modification.getAsJsonObject())); + } + + return newSubRegionData; + } + + @Override + public String toString() { + if (!isModified) { + return "[]"; + } + return modificationData == null ? "[ERROR:null]" : modificationData.toString(); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java new file mode 100644 index 00000000..a52e299b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java @@ -0,0 +1,65 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SubRegionPlacementModification { + + public final String name; + public final BlockPos position; + public final Rotation rotation; + public final Mirror mirror; + + SubRegionPlacementModification(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { + this.name = name; + this.position = position; + this.rotation = rotation; + this.mirror = mirror; + } + + public JsonObject toJson() { + final JsonObject obj = new JsonObject(); + + final JsonArray arr = new JsonArray(); + arr.add(position.getX()); + arr.add(position.getY()); + arr.add(position.getZ()); + obj.add("position", arr); + + obj.add("name", new JsonPrimitive(name)); + obj.add("rotation", new JsonPrimitive(rotation.name())); + obj.add("mirror", new JsonPrimitive(mirror.name())); + + return obj; + } + + @Nullable + public static SubRegionPlacementModification fromJson(final @NotNull JsonObject obj) { + if (!obj.has("name") || !obj.has("position") || !obj.has("rotation") || !obj.has("mirror")) { + return null; + } + + final String name = obj.get("name").getAsString(); + final JsonArray arr = obj.get("position").getAsJsonArray(); + if (arr.size() != 3) { + return null; + } + + final BlockPos position = new BlockPos(arr.get(0).getAsInt(), arr.get(1).getAsInt(), arr.get(2).getAsInt()); + final Rotation rotation = Rotation.valueOf(obj.get("rotation").getAsString()); + final Mirror mirror = Mirror.valueOf(obj.get("mirror").getAsString()); + + return new SubRegionPlacementModification(name, position, rotation, mirror); + } + + @Override + public String toString() { + return String.format("[name=%s, position=%s, rotation=%s, mirror=%s]", name, position, rotation, mirror); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java new file mode 100644 index 00000000..27a056b3 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java @@ -0,0 +1,108 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SyncmaticManager { + + public static final String PLACEMENTS_JSON_KEY = "placements"; + private final Map schematics = new HashMap<>(); + + public void addPlacement(final ServerPlacement placement) { + schematics.put(placement.getId(), placement); + updateServerPlacement(); + } + + public ServerPlacement getPlacement(final UUID id) { + return schematics.get(id); + } + + public Collection getAll() { + return schematics.values(); + } + + public void removePlacement(final @NotNull ServerPlacement placement) { + schematics.remove(placement.getId()); + updateServerPlacement(); + } + + public void updateServerPlacement() { + saveServer(); + } + + public void startup() { + loadServer(); + } + + private void saveServer() { + final JsonObject obj = new JsonObject(); + final JsonArray arr = new JsonArray(); + + for (final ServerPlacement p : getAll()) { + arr.add(p.toJson()); + } + + obj.add(PLACEMENTS_JSON_KEY, arr); + final File backup = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.bak"); + final File incoming = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.new"); + final File current = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json"); + + try (final FileWriter writer = new FileWriter(incoming)) { + writer.write(new GsonBuilder().setPrettyPrinting().create().toJson(obj)); + } catch (final IOException e) { + e.printStackTrace(); + return; + } + + SyncmaticaProtocol.backupAndReplace(backup.toPath(), current.toPath(), incoming.toPath()); + } + + private void loadServer() { + final File f = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json"); + if (f.exists() && f.isFile() && f.canRead()) { + JsonElement element = null; + try { + final JsonParser parser = new JsonParser(); + final FileReader reader = new FileReader(f); + + element = parser.parse(reader); + reader.close(); + + } catch (final Exception e) { + e.printStackTrace(); + } + if (element == null) { + return; + } + try { + final JsonObject obj = element.getAsJsonObject(); + if (obj == null || !obj.has(PLACEMENTS_JSON_KEY)) { + return; + } + final JsonArray arr = obj.getAsJsonArray(PLACEMENTS_JSON_KEY); + for (final JsonElement elem : arr) { + final ServerPlacement placement = ServerPlacement.fromJson(elem.getAsJsonObject()); + if (placement != null) { + schematics.put(placement.getId(), placement); + } + } + + } catch (final IllegalStateException | NullPointerException e) { + e.printStackTrace(); + } + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java new file mode 100644 index 00000000..cb5dffe8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; + +public record SyncmaticaPayload(ResourceLocation packetType, + FriendlyByteBuf data) implements LeavesCustomPayload { + + private static final ResourceLocation NETWORK_ID = new ResourceLocation(SyncmaticaProtocol.PROTOCOL_ID, "main"); + + @New + public static SyncmaticaPayload decode(ResourceLocation location, FriendlyByteBuf buf) { + return new SyncmaticaPayload(buf.readResourceLocation(), new FriendlyByteBuf(buf.readBytes(buf.readableBytes()))); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeResourceLocation(this.packetType); + buf.writeBytes(this.data.readBytes(this.data.readableBytes())); + } + + @Override + public ResourceLocation id() { + return NETWORK_ID; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java new file mode 100644 index 00000000..4ee092ce --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java @@ -0,0 +1,126 @@ +package org.leavesmc.leaves.protocol.syncmatica; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.UUID; + +public class SyncmaticaProtocol { + + public static final String PROTOCOL_ID = "syncmatica"; + public static final String PROTOCOL_VERSION = "leaves-syncmatica-1.1.0"; + + private static boolean loaded = false; + private static final File litematicFolder = new File("." + File.separator + "syncmatics"); + private static final PlayerIdentifierProvider playerIdentifierProvider = new PlayerIdentifierProvider(); + private static final CommunicationManager communicationManager = new CommunicationManager(); + private static final FeatureSet featureSet = new FeatureSet(Arrays.asList(Feature.values())); + private static final SyncmaticManager syncmaticManager = new SyncmaticManager(); + private static final FileStorage fileStorage = new FileStorage(); + + public static File getLitematicFolder() { + return litematicFolder; + } + + public static PlayerIdentifierProvider getPlayerIdentifierProvider() { + return playerIdentifierProvider; + } + + public static CommunicationManager getCommunicationManager() { + return communicationManager; + } + + public static FeatureSet getFeatureSet() { + return featureSet; + } + + public static SyncmaticManager getSyncmaticManager() { + return syncmaticManager; + } + + public static FileStorage getFileStorage() { + return fileStorage; + } + + public static void init() { + if (!loaded) { + litematicFolder.mkdirs(); + syncmaticManager.startup(); + loaded = true; + } + } + + @NotNull + public static UUID createChecksum(final @NotNull InputStream fis) throws NoSuchAlgorithmException, IOException { + final byte[] buffer = new byte[4096]; + final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + int numRead; + + do { + numRead = fis.read(buffer); + if (numRead > 0) { + messageDigest.update(buffer, 0, numRead); + } + } while (numRead != -1); + + fis.close(); + return UUID.nameUUIDFromBytes(messageDigest.digest()); + } + + private static final int[] ILLEGAL_CHARS = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; + private static final String ILLEGAL_PATTERNS = "(^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$)|(^\\.\\.*$)"; + + @NotNull + public static String sanitizeFileName(final @NotNull String badFileName) { + final StringBuilder sanitized = new StringBuilder(); + final int len = badFileName.codePointCount(0, badFileName.length()); + + for (int i = 0; i < len; i++) { + final int c = badFileName.codePointAt(i); + if (Arrays.binarySearch(ILLEGAL_CHARS, c) < 0) { + sanitized.appendCodePoint(c); + if (sanitized.length() == 255) { + break; + } + } + } + + return sanitized.toString().replaceAll(ILLEGAL_PATTERNS, "_"); + } + + public static boolean isOverQuota(int sent) { + return org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaQuota && sent > org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaQuotaLimit; + } + + public static void backupAndReplace(final Path backup, final Path current, final Path incoming) { + if (!Files.exists(incoming)) { + return; + } + if (overwrite(backup, current, 2) && !overwrite(current, incoming, 4)) { + overwrite(current, backup, 8); + } + } + + private static boolean overwrite(final Path backup, final Path current, final int tries) { + if (!Files.exists(current)) { + return true; + } + try { + Files.deleteIfExists(backup); + Files.move(current, backup); + } catch (final IOException exception) { + if (tries <= 0) { + return false; + } + return overwrite(backup, current, tries - 1); + } + return true; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java new file mode 100644 index 00000000..b06ffeac --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java @@ -0,0 +1,66 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import net.minecraft.network.FriendlyByteBuf; +import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager; +import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; + +import java.util.UUID; + +public abstract class AbstractExchange implements Exchange { + + private boolean success = false; + private boolean finished = false; + private final ExchangeTarget partner; + + protected AbstractExchange(final ExchangeTarget partner) { + this.partner = partner; + } + + @Override + public ExchangeTarget getPartner() { + return partner; + } + + @Override + public boolean isFinished() { + return finished; + } + + @Override + public boolean isSuccessful() { + return success; + } + + @Override + public void close(final boolean notifyPartner) { + finished = true; + success = false; + onClose(); + if (notifyPartner) { + sendCancelPacket(); + } + } + + public CommunicationManager getManager() { + return SyncmaticaProtocol.getCommunicationManager(); + } + + protected void sendCancelPacket() { + } + + protected void onClose() { + } + + protected void succeed() { + finished = true; + success = true; + onClose(); + } + + protected static boolean checkUUID(final FriendlyByteBuf sourceBuf, final UUID targetId) { + final int r = sourceBuf.readerIndex(); + final UUID sourceId = sourceBuf.readUUID(); + sourceBuf.readerIndex(r); + return sourceId.equals(targetId); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java new file mode 100644 index 00000000..b0463dc8 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java @@ -0,0 +1,125 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.syncmatica.MessageType; +import org.leavesmc.leaves.protocol.syncmatica.PacketType; +import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; +import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +public class DownloadExchange extends AbstractExchange { + + private final ServerPlacement toDownload; + private final OutputStream outputStream; + private final MessageDigest md5; + private final File downloadFile; + private int bytesSent; + + public DownloadExchange(final ServerPlacement syncmatic, final File downloadFile, final ExchangeTarget partner) throws IOException, NoSuchAlgorithmException { + super(partner); + this.downloadFile = downloadFile; + final OutputStream os = new FileOutputStream(downloadFile); + toDownload = syncmatic; + md5 = MessageDigest.getInstance("MD5"); + outputStream = new DigestOutputStream(os, md5); + } + + @Override + public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + if (id.equals(PacketType.SEND_LITEMATIC.identifier) + || id.equals(PacketType.FINISHED_LITEMATIC.identifier) + || id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { + return checkUUID(packetBuf, toDownload.getId()); + } + return false; + } + + @Override + public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { + packetBuf.readUUID(); + if (id.equals(PacketType.SEND_LITEMATIC.identifier)) { + final int size = packetBuf.readInt(); + bytesSent += size; + if (SyncmaticaProtocol.isOverQuota(bytesSent)) { + close(true); + SyncmaticaProtocol.getCommunicationManager().sendMessage( + getPartner(), + MessageType.ERROR, + "syncmatica.error.cancelled_transmit_exceed_quota" + ); + } + try { + packetBuf.readBytes(outputStream, size); + } catch (final IOException e) { + close(true); + e.printStackTrace(); + return; + } + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(toDownload.getId()); + getPartner().sendPacket(PacketType.RECEIVED_LITEMATIC.identifier, FriendlyByteBuf); + return; + } + if (id.equals(PacketType.FINISHED_LITEMATIC.identifier)) { + try { + outputStream.flush(); + } catch (final IOException e) { + close(false); + e.printStackTrace(); + return; + } + final UUID downloadHash = UUID.nameUUIDFromBytes(md5.digest()); + if (downloadHash.equals(toDownload.getHash())) { + succeed(); + } else { + close(false); + } + return; + } + if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { + close(false); + } + } + + @Override + public void init() { + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(toDownload.getId()); + getPartner().sendPacket(PacketType.REQUEST_LITEMATIC.identifier, FriendlyByteBuf); + } + + @Override + protected void onClose() { + getManager().setDownloadState(toDownload, false); + try { + outputStream.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + if (!isSuccessful() && downloadFile.exists()) { + downloadFile.delete(); + } + } + + @Override + protected void sendCancelPacket() { + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(toDownload.getId()); + getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf); + } + + public ServerPlacement getPlacement() { + return toDownload; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java new file mode 100644 index 00000000..0f45ef7f --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java @@ -0,0 +1,20 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +public interface Exchange { + ExchangeTarget getPartner(); + + boolean checkPacket(ResourceLocation id, FriendlyByteBuf packetBuf); + + void handle(ResourceLocation id, FriendlyByteBuf packetBuf); + + boolean isFinished(); + + boolean isSuccessful(); + + void close(boolean notifyPartner); + + void init(); +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java new file mode 100644 index 00000000..b0da7abf --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java @@ -0,0 +1,39 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; +import org.leavesmc.leaves.protocol.syncmatica.FeatureSet; +import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaPayload; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ExchangeTarget { + + private final List ongoingExchanges = new ArrayList<>(); + private final ServerGamePacketListenerImpl client; + private FeatureSet features; + + public ExchangeTarget(final ServerGamePacketListenerImpl client) { + this.client = client; + } + + public void sendPacket(final ResourceLocation id, final FriendlyByteBuf packetBuf) { + ProtocolUtils.sendPayloadPacket(client.player, new SyncmaticaPayload(id, packetBuf)); + } + + public FeatureSet getFeatureSet() { + return features; + } + + public void setFeatureSet(final FeatureSet f) { + features = f; + } + + public Collection getExchanges() { + return ongoingExchanges; + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java new file mode 100644 index 00000000..45fc0191 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java @@ -0,0 +1,48 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.syncmatica.FeatureSet; +import org.leavesmc.leaves.protocol.syncmatica.PacketType; +import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; + +public abstract class FeatureExchange extends AbstractExchange { + + protected FeatureExchange(final ExchangeTarget partner) { + super(partner); + } + + @Override + public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + return id.equals(PacketType.FEATURE_REQUEST.identifier) + || id.equals(PacketType.FEATURE.identifier); + } + + @Override + public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + if (id.equals(PacketType.FEATURE_REQUEST.identifier)) { + sendFeatures(); + } else if (id.equals(PacketType.FEATURE.identifier)) { + final FeatureSet fs = FeatureSet.fromString(packetBuf.readUtf(32767)); + getPartner().setFeatureSet(fs); + onFeatureSetReceive(); + } + } + + protected void onFeatureSetReceive() { + succeed(); + } + + public void requestFeatureSet() { + getPartner().sendPacket(PacketType.FEATURE_REQUEST.identifier, new FriendlyByteBuf(Unpooled.buffer())); + } + + private void sendFeatures() { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + final FeatureSet fs = SyncmaticaProtocol.getFeatureSet(); + buf.writeUtf(fs.toString(), 32767); + getPartner().sendPacket(PacketType.FEATURE.identifier, buf); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java new file mode 100644 index 00000000..c691201f --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java @@ -0,0 +1,81 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.syncmatica.PacketType; +import org.leavesmc.leaves.protocol.syncmatica.PlayerIdentifier; +import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; +import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; + +import java.util.UUID; + +public class ModifyExchangeServer extends AbstractExchange { + + private final ServerPlacement placement; + UUID placementId; + + public ModifyExchangeServer(final UUID placeId, final ExchangeTarget partner) { + super(partner); + placementId = placeId; + placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId); + } + + @Override + public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + return id.equals(PacketType.MODIFY_FINISH.identifier) && checkUUID(packetBuf, placement.getId()); + } + + @Override + public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { + packetBuf.readUUID(); + if (id.equals(PacketType.MODIFY_FINISH.identifier)) { + SyncmaticaProtocol.getCommunicationManager().receivePositionData(placement, packetBuf, getPartner()); + final PlayerIdentifier identifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet( + getPartner() + ); + placement.setLastModifiedBy(identifier); + SyncmaticaProtocol.getSyncmaticManager().updateServerPlacement(); + succeed(); + } + } + + @Override + public void init() { + if (getPlacement() == null || SyncmaticaProtocol.getCommunicationManager().getModifier(placement) != null) { + close(true); + } else { + if (SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(this.getPartner()).uuid.equals(placement.getOwner().uuid)) { + accept(); + } else { + close(true); + } + } + } + + private void accept() { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeUUID(placement.getId()); + getPartner().sendPacket(PacketType.MODIFY_REQUEST_ACCEPT.identifier, buf); + SyncmaticaProtocol.getCommunicationManager().setModifier(placement, this); + } + + @Override + protected void sendCancelPacket() { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeUUID(placementId); + getPartner().sendPacket(PacketType.MODIFY_REQUEST_DENY.identifier, buf); + } + + public ServerPlacement getPlacement() { + return placement; + } + + @Override + protected void onClose() { + if (SyncmaticaProtocol.getCommunicationManager().getModifier(placement) == this) { + SyncmaticaProtocol.getCommunicationManager().setModifier(placement, null); + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java new file mode 100644 index 00000000..065a3994 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java @@ -0,0 +1,101 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.syncmatica.PacketType; +import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +public class UploadExchange extends AbstractExchange { + + private static final int BUFFER_SIZE = 16384; + + private final ServerPlacement toUpload; + private final InputStream inputStream; + private final byte[] buffer = new byte[BUFFER_SIZE]; + + public UploadExchange(final ServerPlacement syncmatic, final File uploadFile, final ExchangeTarget partner) throws FileNotFoundException { + super(partner); + toUpload = syncmatic; + inputStream = new FileInputStream(uploadFile); + } + + @Override + public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier) + || id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { + return checkUUID(packetBuf, toUpload.getId()); + } + return false; + } + + @Override + public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { + packetBuf.readUUID(); + if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier)) { + send(); + } + if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { + close(false); + } + } + + private void send() { + final int bytesRead; + try { + bytesRead = inputStream.read(buffer); + } catch (final IOException e) { + close(true); + e.printStackTrace(); + return; + } + if (bytesRead == -1) { + sendFinish(); + } else { + sendData(bytesRead); + } + } + + private void sendData(final int bytesRead) { + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(toUpload.getId()); + FriendlyByteBuf.writeInt(bytesRead); + FriendlyByteBuf.writeBytes(buffer, 0, bytesRead); + getPartner().sendPacket(PacketType.SEND_LITEMATIC.identifier, FriendlyByteBuf); + } + + private void sendFinish() { + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(toUpload.getId()); + getPartner().sendPacket(PacketType.FINISHED_LITEMATIC.identifier, FriendlyByteBuf); + succeed(); + } + + @Override + public void init() { + send(); + } + + @Override + protected void onClose() { + try { + inputStream.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + @Override + protected void sendCancelPacket() { + final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf.writeUUID(toUpload.getId()); + getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java new file mode 100644 index 00000000..cb7aed6c --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java @@ -0,0 +1,66 @@ +package org.leavesmc.leaves.protocol.syncmatica.exchange; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager; +import org.leavesmc.leaves.protocol.syncmatica.FeatureSet; +import org.leavesmc.leaves.protocol.syncmatica.PacketType; +import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; +import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; + +import java.util.Collection; + +public class VersionHandshakeServer extends FeatureExchange { + + public VersionHandshakeServer(final ExchangeTarget partner) { + super(partner); + } + + @Override + public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + return id.equals(PacketType.REGISTER_VERSION.identifier) + || super.checkPacket(id, packetBuf); + } + + @Override + public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { + if (id.equals(PacketType.REGISTER_VERSION.identifier)) { + String partnerVersion = packetBuf.readUtf(); + if (partnerVersion.equals("0.0.1")) { + close(false); + return; + } + final FeatureSet fs = FeatureSet.fromVersionString(partnerVersion); + if (fs == null) { + requestFeatureSet(); + } else { + getPartner().setFeatureSet(fs); + onFeatureSetReceive(); + } + } else { + super.handle(id, packetBuf); + } + + } + + @Override + public void onFeatureSetReceive() { + final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); + final Collection l = SyncmaticaProtocol.getSyncmaticManager().getAll(); + newBuf.writeInt(l.size()); + for (final ServerPlacement p : l) { + CommunicationManager.putMetaData(p, newBuf, getPartner()); + } + getPartner().sendPacket(PacketType.CONFIRM_USER.identifier, newBuf); + succeed(); + } + + @Override + public void init() { + final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); + newBuf.writeUtf(SyncmaticaProtocol.PROTOCOL_VERSION); + getPartner().sendPacket(PacketType.REGISTER_VERSION.identifier, newBuf); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java new file mode 100644 index 00000000..e67ff063 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.replay; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Checksum; + +public class DigestOutputStream extends OutputStream { + + private final Checksum sum; + private final OutputStream out; + + public DigestOutputStream(OutputStream out, Checksum sum) { + this.out = out; + this.sum = sum; + } + + @Override + public void close() throws IOException { + out.close(); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void write(int b) throws IOException { + sum.update(b); + out.write(b); + } + + @Override + public void write(byte @NotNull [] b) throws IOException { + sum.update(b); + out.write(b); + } + + @Override + public void write(byte @NotNull [] b, int off, int len) throws IOException { + sum.update(b, off, len); + out.write(b, off, len); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java new file mode 100644 index 00000000..5a3ea3e1 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java @@ -0,0 +1,23 @@ +package org.leavesmc.leaves.replay; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class RecordMetaData { + + public static final int CURRENT_FILE_FORMAT_VERSION = 14; + + public boolean singleplayer = false; + public String serverName = "Leaf"; + public int duration = 0; + public long date; + public String mcversion; + public String fileFormat = "MCPR"; + public int fileFormatVersion; + public int protocol; + public String generator; + public int selfId = -1; + + public Set players = new HashSet<>(); +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/Recorder.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/Recorder.java new file mode 100644 index 00000000..d1fb2f08 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/Recorder.java @@ -0,0 +1,285 @@ +package org.leavesmc.leaves.replay; + +import com.mojang.serialization.DynamicOps; +import io.netty.channel.local.LocalChannel; +import net.minecraft.SharedConstants; +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.core.RegistrySynchronization; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.PacketSendListener; +import net.minecraft.network.protocol.BundlePacket; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ClientboundDisconnectPacket; +import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket; +import net.minecraft.network.protocol.common.ClientboundServerLinksPacket; +import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; +import net.minecraft.network.protocol.common.custom.BrandPayload; +import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket; +import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket; +import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks; +import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; +import net.minecraft.network.protocol.game.ClientboundSetTimePacket; +import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; +import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.packs.repository.KnownPack; +import net.minecraft.tags.TagNetworkSerialization; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.flag.FeatureFlags; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesLogger; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class Recorder extends Connection { + + private static final LeavesLogger LOGGER = LeavesLogger.LOGGER; + + private final ReplayFile replayFile; + private final ServerPhotographer photographer; + private final RecorderOption recorderOption; + private final RecordMetaData metaData; + + private final ExecutorService saveService = Executors.newSingleThreadExecutor(); + + private boolean stopped = false; + private boolean paused = false; + private boolean resumeOnNextPacket = true; + + private long startTime; + private long lastPacket; + private long timeShift = 0; + + private boolean isSaved; + private boolean isSaving; + private ConnectionProtocol state = ConnectionProtocol.LOGIN; + + public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException { + super(PacketFlow.CLIENTBOUND); + + this.photographer = photographer; + this.recorderOption = recorderOption; + this.metaData = new RecordMetaData(); + this.replayFile = new ReplayFile(replayFile); + this.channel = new LocalChannel(); + } + + public void start() { + startTime = System.currentTimeMillis(); + + metaData.singleplayer = false; + metaData.serverName = recorderOption.serverName; + metaData.date = startTime; + metaData.mcversion = SharedConstants.getCurrentVersion().getName(); + + // TODO start event + this.savePacket(new ClientboundLoginFinishedPacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN); + this.startConfiguration(); + + if (recorderOption.forceWeather != null) { + setWeather(recorderOption.forceWeather); + } + } + + public void startConfiguration() { + this.state = ConnectionProtocol.CONFIGURATION; + MinecraftServer server = MinecraftServer.getServer(); + + this.savePacket(new ClientboundCustomPayloadPacket(new BrandPayload(server.getServerModName())), ConnectionProtocol.CONFIGURATION); + this.savePacket(new ClientboundServerLinksPacket(server.serverLinks().untrust()), ConnectionProtocol.CONFIGURATION); + this.savePacket(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(server.getWorldData().enabledFeatures())), ConnectionProtocol.CONFIGURATION); + + List knownPackslist = server.getResourceManager().listPacks().flatMap((iresourcepack) -> iresourcepack.location().knownPackInfo().stream()).toList(); + this.savePacket(new ClientboundSelectKnownPacks(knownPackslist), ConnectionProtocol.CONFIGURATION); + + server.getServerResourcePack().ifPresent((info) -> this.savePacket(new ClientboundResourcePackPushPacket( + info.id(), info.url(), info.hash(), info.isRequired(), Optional.ofNullable(info.prompt()) + ))); + + LayeredRegistryAccess layeredregistryaccess = server.registries(); + DynamicOps dynamicOps = layeredregistryaccess.compositeAccess().createSerializationContext(NbtOps.INSTANCE); + RegistrySynchronization.packRegistries(dynamicOps, layeredregistryaccess.getAccessFrom(RegistryLayer.WORLDGEN), Set.copyOf(knownPackslist), + (key, entries) -> + this.savePacket(new ClientboundRegistryDataPacket(key, entries), ConnectionProtocol.CONFIGURATION) + ); + this.savePacket(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(layeredregistryaccess)), ConnectionProtocol.CONFIGURATION); + + this.savePacket(ClientboundFinishConfigurationPacket.INSTANCE, ConnectionProtocol.CONFIGURATION); + state = ConnectionProtocol.PLAY; + } + + @Override + public void flushChannel() { + } + + public void stop() { + stopped = true; + } + + public void pauseRecording() { + resumeOnNextPacket = false; + paused = true; + } + + public void resumeRecording() { + resumeOnNextPacket = true; + } + + public void setWeather(RecorderOption.RecordWeather weather) { + weather.getPackets().forEach(this::savePacket); + } + + public long getRecordedTime() { + final long base = System.currentTimeMillis() - startTime; + return base - timeShift; + } + + private synchronized long getCurrentTimeAndUpdate() { + long now = getRecordedTime(); + if (paused) { + if (resumeOnNextPacket) { + paused = false; + } + timeShift += now - lastPacket; + return lastPacket; + } + return lastPacket = now; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { + if (!stopped) { + if (packet instanceof BundlePacket packet1) { + packet1.subPackets().forEach(subPacket -> send(subPacket, null)); + return; + } + + if (packet instanceof ClientboundAddEntityPacket packet1) { + if (packet1.getType() == EntityType.PLAYER) { + metaData.players.add(packet1.getUUID()); + saveMetadata(); + } + } + + if (packet instanceof ClientboundDisconnectPacket) { + return; + } + + if (recorderOption.forceDayTime != -1 && packet instanceof ClientboundSetTimePacket packet1) { + packet = new ClientboundSetTimePacket(packet1.dayTime(), recorderOption.forceDayTime, false); + } + + if (recorderOption.forceWeather != null && packet instanceof ClientboundGameEventPacket packet1) { + ClientboundGameEventPacket.Type type = packet1.getEvent(); + if (type == ClientboundGameEventPacket.START_RAINING || type == ClientboundGameEventPacket.STOP_RAINING || type == ClientboundGameEventPacket.RAIN_LEVEL_CHANGE || type == ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE) { + return; + } + } + + if (recorderOption.ignoreChat && (packet instanceof ClientboundSystemChatPacket || packet instanceof ClientboundPlayerChatPacket)) { + return; + } + + savePacket(packet); + } + } + + private void saveMetadata() { + saveService.submit(() -> { + try { + replayFile.saveMetaData(metaData); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private void savePacket(Packet packet) { + this.savePacket(packet, state); + } + + private void savePacket(Packet packet, final ConnectionProtocol protocol) { + try { + final long timestamp = getCurrentTimeAndUpdate(); + saveService.submit(() -> { + try { + replayFile.savePacket(timestamp, packet, protocol); + } catch (Exception e) { + LOGGER.severe("Error saving packet"); + e.printStackTrace(); + } + }); + } catch (Exception e) { + LOGGER.severe("Error saving packet"); + e.printStackTrace(); + } + } + + public boolean isSaved() { + return isSaved; + } + + public CompletableFuture saveRecording(File dest, boolean save) { + isSaved = true; + if (!isSaving) { + isSaving = true; + metaData.duration = (int) lastPacket; + return CompletableFuture.runAsync(() -> { + saveMetadata(); + saveService.shutdown(); + boolean interrupted = false; + try { + saveService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + interrupted = true; + } + try { + if (save) { + replayFile.closeAndSave(dest); + } else { + replayFile.closeNotSave(); + } + } catch (IOException e) { + e.printStackTrace(); + throw new CompletionException(e); + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + }, runnable -> { + final Thread thread = new Thread(runnable, "Recording file save thread"); + thread.start(); + }); + } else { + LOGGER.warning("saveRecording() called twice"); + return CompletableFuture.supplyAsync(() -> { + throw new IllegalStateException("saveRecording() called twice"); + }); + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java new file mode 100644 index 00000000..e1c32a60 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java @@ -0,0 +1,57 @@ +package org.leavesmc.leaves.replay; + +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class RecorderOption { + + public int recordDistance = -1; + public String serverName = "Leaf"; + public RecordWeather forceWeather = null; + public int forceDayTime = -1; + public boolean ignoreChat = false; + public boolean ignoreItem = false; + + @NotNull + @Contract(" -> new") + public static RecorderOption createDefaultOption() { + return new RecorderOption(); + } + + @NotNull + public static RecorderOption createFromBukkit(@NotNull BukkitRecorderOption bukkitRecorderOption) { + RecorderOption recorderOption = new RecorderOption(); + // recorderOption.recordDistance = bukkitRecorderOption.recordDistance; + // recorderOption.ignoreItem = bukkitRecorderOption.ignoreItem; + recorderOption.serverName = bukkitRecorderOption.serverName; + recorderOption.ignoreChat = bukkitRecorderOption.ignoreChat; + recorderOption.forceDayTime = bukkitRecorderOption.forceDayTime; + recorderOption.forceWeather = switch (bukkitRecorderOption.forceWeather) { + case RAIN -> RecordWeather.RAIN; + case CLEAR -> RecordWeather.CLEAR; + case THUNDER -> RecordWeather.THUNDER; + case NULL -> null; + }; + return recorderOption; + } + + public enum RecordWeather { + CLEAR(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)), + RAIN(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)), + THUNDER(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 1)); + + private final List> packets; + + private RecordWeather(Packet... packets) { + this.packets = List.of(packets); + } + + public List> getPackets() { + return packets; + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java new file mode 100644 index 00000000..c6bb5431 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java @@ -0,0 +1,198 @@ +package org.leavesmc.leaves.replay; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.minecraft.SharedConstants; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.ProtocolInfo; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.configuration.ConfigurationProtocols; +import net.minecraft.network.protocol.game.GameProtocols; +import net.minecraft.network.protocol.login.LoginProtocols; +import net.minecraft.network.protocol.status.StatusProtocols; +import net.minecraft.server.MinecraftServer; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; +import org.leavesmc.leaves.util.UUIDSerializer; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class ReplayFile { + + private static final String RECORDING_FILE = "recording.tmcpr"; + private static final String RECORDING_FILE_CRC32 = "recording.tmcpr.crc32"; + private static final String MARKER_FILE = "markers.json"; + private static final String META_FILE = "metaData.json"; + + private static final Gson MARKER_GSON = new GsonBuilder().registerTypeAdapter(ReplayMarker.class, new ReplayMarker.Serializer()).create(); + private static final Gson META_GSON = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDSerializer()).create(); + + private final File tmpDir; + private final DataOutputStream packetStream; + private final CRC32 crc32 = new CRC32(); + + private final File markerFile; + private final File metaFile; + + private final Map> protocols; + + public ReplayFile(@NotNull File name) throws IOException { + this.tmpDir = new File(name.getParentFile(), name.getName() + ".tmp"); + if (tmpDir.exists()) { + if (!ReplayFile.deleteDir(tmpDir)) { + throw new IOException("Recording file " + name + " already exists!"); + } + } + + if (!tmpDir.mkdirs()) { + throw new IOException("Failed to create temp directory for recording " + tmpDir); + } + + File packetFile = new File(tmpDir, RECORDING_FILE); + this.metaFile = new File(tmpDir, META_FILE); + this.markerFile = new File(tmpDir, MARKER_FILE); + + this.packetStream = new DataOutputStream(new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(packetFile)), crc32)); + + this.protocols = Map.of( + ConnectionProtocol.STATUS, StatusProtocols.CLIENTBOUND, + ConnectionProtocol.LOGIN, LoginProtocols.CLIENTBOUND, + ConnectionProtocol.CONFIGURATION, ConfigurationProtocols.CLIENTBOUND, + ConnectionProtocol.PLAY, GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess())) + ); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private byte @NotNull [] getPacketBytes(Packet packet, ConnectionProtocol state) { + ProtocolInfo protocol = this.protocols.get(state); + if (protocol == null) { + throw new IllegalArgumentException("Unknown protocol state " + state); + } + + ByteBuf buf = Unpooled.buffer(); + protocol.codec().encode(buf, packet); + + buf.readerIndex(0); + byte[] ret = new byte[buf.readableBytes()]; + buf.readBytes(ret); + buf.release(); + return ret; + } + + public void saveMarkers(List markers) throws IOException { + try (Writer writer = new OutputStreamWriter(new FileOutputStream(markerFile), StandardCharsets.UTF_8)) { + writer.write(MARKER_GSON.toJson(markers)); + } + } + + public void saveMetaData(@NotNull RecordMetaData data) throws IOException { + data.fileFormat = "MCPR"; + data.fileFormatVersion = RecordMetaData.CURRENT_FILE_FORMAT_VERSION; + data.protocol = SharedConstants.getCurrentVersion().getProtocolVersion(); + data.generator = ProtocolUtils.buildProtocolVersion("replay"); + + try (Writer writer = new OutputStreamWriter(new FileOutputStream(metaFile), StandardCharsets.UTF_8)) { + writer.write(META_GSON.toJson(data)); + } + } + + public void savePacket(long timestamp, Packet packet, ConnectionProtocol protocol) throws Exception { + byte[] data = getPacketBytes(packet, protocol); + packetStream.writeInt((int) timestamp); + packetStream.writeInt(data.length); + packetStream.write(data); + } + + public synchronized void closeAndSave(File file) throws IOException { + packetStream.close(); + + String[] files = tmpDir.list(); + if (files == null) { + return; + } + + try (ZipOutputStream os = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { + for (String fileName : files) { + os.putNextEntry(new ZipEntry(fileName)); + File f = new File(tmpDir, fileName); + copy(new FileInputStream(f), os); + } + + os.putNextEntry(new ZipEntry(RECORDING_FILE_CRC32)); + Writer writer = new OutputStreamWriter(os); + writer.write(Long.toString(crc32.getValue())); + writer.flush(); + } + + for (String fileName : files) { + File f = new File(tmpDir, fileName); + Files.delete(f.toPath()); + } + Files.delete(tmpDir.toPath()); + } + + public synchronized void closeNotSave() throws IOException { + packetStream.close(); + + String[] files = tmpDir.list(); + if (files == null) { + return; + } + + for (String fileName : files) { + File f = new File(tmpDir, fileName); + Files.delete(f.toPath()); + } + Files.delete(tmpDir.toPath()); + } + + private void copy(@NotNull InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[8192]; + int len; + while ((len = in.read(buffer)) > -1) { + out.write(buffer, 0, len); + } + in.close(); + } + + private static boolean deleteDir(File dir) { + if (dir == null || !dir.exists()) { + return false; + } + + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDir(file); + } else { + if (!file.delete()) { + return false; + } + } + } + } + + return dir.delete(); + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java new file mode 100644 index 00000000..1568f692 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.replay; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; + +public class ReplayMarker { + + public int time; + public String name; + public double x = 0; + public double y = 0; + public double z = 0; + public float phi = 0; + public float theta = 0; + public float varphi = 0; + + public static class Serializer implements JsonSerializer { + @Override + public JsonElement serialize(ReplayMarker src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject ret = new JsonObject(); + JsonObject value = new JsonObject(); + JsonObject position = new JsonObject(); + ret.add("realTimestamp", new JsonPrimitive(src.time)); + ret.add("value", value); + + value.add("name", new JsonPrimitive(src.name)); + value.add("position", position); + + position.add("x", new JsonPrimitive(src.x)); + position.add("y", new JsonPrimitive(src.y)); + position.add("z", new JsonPrimitive(src.z)); + position.add("yaw", new JsonPrimitive(src.phi)); + position.add("pitch", new JsonPrimitive(src.theta)); + position.add("roll", new JsonPrimitive(src.varphi)); + return ret; + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java new file mode 100644 index 00000000..d8f8f071 --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java @@ -0,0 +1,222 @@ +package org.leavesmc.leaves.replay; + +import com.mojang.authlib.GameProfile; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.bot.BotStatsCounter; +import org.leavesmc.leaves.entity.CraftPhotographer; +import org.leavesmc.leaves.entity.Photographer; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ServerPhotographer extends ServerPlayer { + + private static final List photographers = new CopyOnWriteArrayList<>(); + + public PhotographerCreateState createState; + private ServerPlayer followPlayer; + private Recorder recorder; + private File saveFile; + private Vec3 lastPos; + + private final ServerStatsCounter stats; + + private ServerPhotographer(MinecraftServer server, ServerLevel world, GameProfile profile) { + super(server, world, profile, ClientInformation.createDefault()); + this.gameMode = new ServerPhotographerGameMode(this); + this.followPlayer = null; + this.stats = new BotStatsCounter(server); + this.lastPos = this.position(); + } + + public static ServerPhotographer createPhotographer(@NotNull PhotographerCreateState state) throws IOException { + if (!isCreateLegal(state.id)) { + throw new IllegalArgumentException(state.id + " is a invalid photographer id"); + } + + MinecraftServer server = MinecraftServer.getServer(); + + ServerLevel world = ((CraftWorld) state.loc.getWorld()).getHandle(); + GameProfile profile = new GameProfile(UUID.randomUUID(), state.id); + + ServerPhotographer photographer = new ServerPhotographer(server, world, profile); + photographer.recorder = new Recorder(photographer, state.option, new File("replay", state.id)); + photographer.saveFile = new File("replay", state.id + ".mcpr"); + photographer.createState = state; + + photographer.recorder.start(); + MinecraftServer.getServer().getPlayerList().placeNewPhotographer(photographer.recorder, photographer, world, state.loc); + photographer.serverLevel().chunkSource.move(photographer); + photographer.setInvisible(true); + photographers.add(photographer); + + LeavesLogger.LOGGER.info("Photographer " + state.id + " created"); + + // TODO record distance + + return photographer; + } + + @Override + public void tick() { + super.tick(); + super.doTick(); + + if (this.server.getTickCount() % 10 == 0) { + connection.resetPosition(); + this.serverLevel().chunkSource.move(this); + } + + if (this.followPlayer != null) { + if (this.getCamera() == this || this.getCamera().level() != this.level()) { + this.getBukkitPlayer().teleport(this.getCamera().getBukkitEntity().getLocation()); + this.setCamera(followPlayer); + } + if (lastPos.distanceToSqr(this.position()) > 1024D) { + this.getBukkitPlayer().teleport(this.getCamera().getBukkitEntity().getLocation()); + } + } + + lastPos = this.position(); + } + + @Override + public void die(@NotNull DamageSource damageSource) { + super.die(damageSource); + remove(true); + } + + @Override + public boolean isInvulnerableTo(@NotNull ServerLevel world, @NotNull DamageSource damageSource) { + return true; + } + + @Override + public boolean hurtServer(@NotNull ServerLevel world, @NotNull DamageSource source, float amount) { + return false; + } + + @Override + public void setHealth(float health) { + } + + @NotNull + @Override + public ServerStatsCounter getStats() { + return stats; + } + + public void remove(boolean async) { + this.remove(async, true); + } + + public void remove(boolean async, boolean save) { + super.remove(RemovalReason.KILLED); + photographers.remove(this); + this.recorder.stop(); + this.server.getPlayerList().removePhotographer(this); + + LeavesLogger.LOGGER.info("Photographer " + createState.id + " removed"); + + if (!recorder.isSaved()) { + CompletableFuture future = recorder.saveRecording(saveFile, save); + if (!async) { + future.join(); + } + } + } + + public void setFollowPlayer(ServerPlayer followPlayer) { + this.setCamera(followPlayer); + this.followPlayer = followPlayer; + } + + public void setSaveFile(File saveFile) { + this.saveFile = saveFile; + } + + public void pauseRecording() { + this.recorder.pauseRecording(); + } + + public void resumeRecording() { + this.recorder.resumeRecording(); + } + + public static ServerPhotographer getPhotographer(String id) { + for (ServerPhotographer photographer : photographers) { + if (photographer.createState.id.equals(id)) { + return photographer; + } + } + return null; + } + + public static ServerPhotographer getPhotographer(UUID uuid) { + for (ServerPhotographer photographer : photographers) { + if (photographer.getUUID().equals(uuid)) { + return photographer; + } + } + return null; + } + + public static List getPhotographers() { + return photographers; + } + + public Photographer getBukkitPlayer() { + return getBukkitEntity(); + } + + @Override + @NotNull + public CraftPhotographer getBukkitEntity() { + return (CraftPhotographer) super.getBukkitEntity(); + } + + public static boolean isCreateLegal(@NotNull String name) { + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { + return false; + } + + return Bukkit.getPlayerExact(name) == null && ServerPhotographer.getPhotographer(name) == null; + } + + public static class PhotographerCreateState { + + public RecorderOption option; + public Location loc; + public final String id; + + public PhotographerCreateState(Location loc, String id, RecorderOption option) { + this.loc = loc; + this.id = id; + this.option = option; + } + + public ServerPhotographer createSync() { + try { + return createPhotographer(this); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java new file mode 100644 index 00000000..c612215b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java @@ -0,0 +1,35 @@ +package org.leavesmc.leaves.replay; + +import net.kyori.adventure.text.Component; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.world.level.GameType; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ServerPhotographerGameMode extends ServerPlayerGameMode { + + public ServerPhotographerGameMode(ServerPhotographer photographer) { + super(photographer); + super.setGameModeForPlayer(GameType.SPECTATOR, null); + } + + @Override + public boolean changeGameModeForPlayer(@NotNull GameType gameMode) { + return false; + } + + @Nullable + @Override + public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) { + return null; + } + + @Override + protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) { + } + + @Override + public void tick() { + } +} diff --git a/Leaf-Server/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java b/Leaf-Server/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java new file mode 100644 index 00000000..b0834f4b --- /dev/null +++ b/Leaf-Server/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java @@ -0,0 +1,17 @@ +package org.leavesmc.leaves.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Type; +import java.util.UUID; + +public class UUIDSerializer implements JsonSerializer { + @Override + public JsonElement serialize(@NotNull UUID src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toString()); + } +} diff --git a/Leaf-Server/src/main/java/org/purpurmc/purpur/command/AFKCommand.java b/Leaf-Server/src/main/java/org/purpurmc/purpur/command/AFKCommand.java new file mode 100644 index 00000000..1b8eee9c --- /dev/null +++ b/Leaf-Server/src/main/java/org/purpurmc/purpur/command/AFKCommand.java @@ -0,0 +1,37 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Collection; +import java.util.Collections; + +public class AFKCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("afk") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.afk")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.afk.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean afk = player.isCommandAfk + ? !player.commandAfkStatus + : !player.isAfk(); + + if (afk) player.setAfk(true); + + player.isCommandAfk = false; + } + + return targets.size(); + } +} diff --git a/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java new file mode 100644 index 00000000..8eba172b --- /dev/null +++ b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java @@ -0,0 +1,56 @@ +package org.stupidcraft.linearpaper.region; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +public enum EnumRegionFileExtension { + LINEAR(".linear"), + MCA(".mca"), + UNKNOWN(null); + + private final String extensionName; + + EnumRegionFileExtension(String extensionName) { + this.extensionName = extensionName; + } + + public String getExtensionName() { + return this.extensionName; + } + + @Contract(pure = true) + public static EnumRegionFileExtension fromName(@NotNull String name) { + switch (name.toUpperCase(Locale.ROOT)) { + default -> { + return UNKNOWN; + } + + case "MCA" -> { + return MCA; + } + + case "LINEAR" -> { + return LINEAR; + } + } + } + + @Contract(pure = true) + public static EnumRegionFileExtension fromExtension(@NotNull String name) { + switch (name.toLowerCase()) { + case "mca" -> { + return MCA; + } + + case "linear" -> { + return LINEAR; + } + + default -> { + return UNKNOWN; + } + } + } +} diff --git a/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java new file mode 100644 index 00000000..1d4395ed --- /dev/null +++ b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java @@ -0,0 +1,28 @@ +package org.stupidcraft.linearpaper.region; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Path; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.ChunkPos; + +public interface IRegionFile extends AutoCloseable { + Path getPath(); + void flush() throws IOException; + void clear(ChunkPos pos) throws IOException; + void close() throws IOException; + void setOversized(int x, int z, boolean b) throws IOException; + void write(ChunkPos pos, ByteBuffer buffer) throws IOException; + + boolean hasChunk(ChunkPos pos); + boolean doesChunkExist(ChunkPos pos) throws Exception; + boolean isOversized(int x, int z); + boolean recalculateHeader() throws IOException; + + DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException; + DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException; + CompoundTag getOversizedData(int x, int z) throws IOException; +} diff --git a/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java new file mode 100644 index 00000000..70552d63 --- /dev/null +++ b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java @@ -0,0 +1,52 @@ +package org.stupidcraft.linearpaper.region; + +import java.io.IOException; +import java.nio.file.Path; + +import net.minecraft.world.level.chunk.storage.RegionFile; +import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import net.minecraft.world.level.chunk.storage.RegionStorageInfo; +import org.dreeam.leaf.config.modules.misc.RegionFormatConfig; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +public class IRegionFileFactory { + @Contract("_, _, _, _ -> new") + public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { + return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); + } + + @Contract("_, _, _, _, _ -> new") + public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, boolean canRecalcHeader) throws IOException { + return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); + } + + @Contract("_, _, _, _, _ -> new") + public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { + return getAbstractRegionFile(storageKey, path, directory, compressionFormat, dsync, true); + } + + @Contract("_, _, _, _, _, _ -> new") + public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, @NotNull Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, boolean canRecalcHeader) throws IOException { + final String fullFileName = path.getFileName().toString(); + final String[] fullNameSplit = fullFileName.split("\\."); + final String extensionName = fullNameSplit[fullNameSplit.length - 1]; + switch (EnumRegionFileExtension.fromExtension(extensionName)) { + case UNKNOWN -> { + if (RegionFormatConfig.throwOnUnknownExtension) { + throw new IllegalArgumentException("Unknown region file extension for file: " + fullFileName + "!"); + } + + return new RegionFile(storageKey, path, directory, compressionFormat, dsync); + } + + case LINEAR -> { + return new LinearRegionFile(path, RegionFormatConfig.linearCompressionLevel); + } + + default -> { + return new RegionFile(storageKey, path, directory, compressionFormat, dsync); + } + } + } +} diff --git a/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java new file mode 100644 index 00000000..d2cbdd75 --- /dev/null +++ b/Leaf-Server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java @@ -0,0 +1,299 @@ +package org.stupidcraft.linearpaper.region; + +import com.github.luben.zstd.ZstdInputStream; +import com.github.luben.zstd.ZstdOutputStream; +import com.mojang.logging.LogUtils; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.ChunkPos; +import org.dreeam.leaf.config.modules.misc.RegionFormatConfig; +import org.slf4j.Logger; + +public class LinearRegionFile implements IRegionFile, AutoCloseable { + private static final long SUPERBLOCK = -4323716122432332390L; + private static final byte VERSION = 2; + private static final int HEADER_SIZE = 32; + private static final int FOOTER_SIZE = 8; + private static final Logger LOGGER = LogUtils.getLogger(); + private static final List SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2); + private final byte[][] buffer = new byte[1024][]; + private final int[] bufferUncompressedSize = new int[1024]; + private final int[] chunkTimestamps = new int[1024]; + private final LZ4Compressor compressor; + private final LZ4FastDecompressor decompressor; + private final int compressionLevel; + public boolean closed = false; + public Path path; + private volatile long lastFlushed = System.nanoTime(); + + public LinearRegionFile(Path file, int compression) throws IOException { + this.path = file; + this.compressionLevel = compression; + this.compressor = LZ4Factory.fastestInstance().fastCompressor(); + this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + + File regionFile = new File(this.path.toString()); + + Arrays.fill(this.bufferUncompressedSize, 0); + + if (!regionFile.canRead()) return; + + try (FileInputStream fileStream = new FileInputStream(regionFile); + DataInputStream rawDataStream = new DataInputStream(fileStream)) { + + long superBlock = rawDataStream.readLong(); + if (superBlock != SUPERBLOCK) + throw new RuntimeException("Invalid superblock: " + superBlock + " in " + file); + + byte version = rawDataStream.readByte(); + if (!SUPPORTED_VERSIONS.contains(version)) + throw new RuntimeException("Invalid version: " + version + " in " + file); + + // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused. + rawDataStream.skipBytes(11); + + int dataCount = rawDataStream.readInt(); + long fileLength = file.toFile().length(); + if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) + throw new IOException("Invalid file length: " + this.path + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); + + rawDataStream.skipBytes(8); // Skip data hash (Long): Unused. + + byte[] rawCompressed = new byte[dataCount]; + rawDataStream.readFully(rawCompressed, 0, dataCount); + + superBlock = rawDataStream.readLong(); + if (superBlock != SUPERBLOCK) + throw new IOException("Footer superblock invalid " + this.path); + + try (DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) { + + int[] starts = new int[1024]; + for (int i = 0; i < 1024; i++) { + starts[i] = dataStream.readInt(); + dataStream.skipBytes(4); // Skip timestamps (Int): Unused. + } + + for (int i = 0; i < 1024; i++) { + if (starts[i] > 0) { + int size = starts[i]; + byte[] b = new byte[size]; + dataStream.readFully(b, 0, size); + + int maxCompressedLength = this.compressor.maxCompressedLength(size); + byte[] compressed = new byte[maxCompressedLength]; + int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength); + b = new byte[compressedLength]; + System.arraycopy(compressed, 0, b, 0, compressedLength); + + this.buffer[i] = b; + this.bufferUncompressedSize[i] = size; + } + } + } + } + } + + private static int getChunkIndex(int x, int z) { + return (x & 31) + ((z & 31) << 5); + } + + private static int getTimestamp() { + return (int) (System.currentTimeMillis() / 1000L); + } + + public void flush() throws IOException { + flushWrapper(); // sync + } + + public void flushWrapper() { + try { + save(); + } catch (IOException e) { + LOGGER.error("Failed to flush region file {}", path.toAbsolutePath(), e); + } + } + + public boolean doesChunkExist(ChunkPos pos) throws Exception { + throw new Exception("doesChunkExist is a stub"); + } + + private synchronized void save() throws IOException { + long timestamp = getTimestamp(); + short chunkCount = 0; + + File tempFile = new File(path.toString() + ".tmp"); + + try (FileOutputStream fileStream = new FileOutputStream(tempFile); + ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream(); + ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel); + DataOutputStream zstdDataStream = new DataOutputStream(zstdStream); + DataOutputStream dataStream = new DataOutputStream(fileStream)) { + + dataStream.writeLong(SUPERBLOCK); + dataStream.writeByte(VERSION); + dataStream.writeLong(timestamp); + dataStream.writeByte(this.compressionLevel); + + ArrayList byteBuffers = new ArrayList<>(); + for (int i = 0; i < 1024; i++) { + if (this.bufferUncompressedSize[i] != 0) { + chunkCount += 1; + byte[] content = new byte[bufferUncompressedSize[i]]; + this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]); + + byteBuffers.add(content); + } else byteBuffers.add(null); + } + for (int i = 0; i < 1024; i++) { + zstdDataStream.writeInt(this.bufferUncompressedSize[i]); // Write uncompressed size + zstdDataStream.writeInt(this.chunkTimestamps[i]); // Write timestamp + } + for (int i = 0; i < 1024; i++) { + if (byteBuffers.get(i) != null) + zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length); + } + zstdDataStream.close(); + + dataStream.writeShort(chunkCount); + + byte[] compressed = zstdByteArray.toByteArray(); + + dataStream.writeInt(compressed.length); + dataStream.writeLong(0); + + dataStream.write(compressed, 0, compressed.length); + dataStream.writeLong(SUPERBLOCK); + + dataStream.flush(); + fileStream.getFD().sync(); + fileStream.getChannel().force(true); // Ensure atomicity on Btrfs + } + Files.move(tempFile.toPath(), this.path, StandardCopyOption.REPLACE_EXISTING); + this.lastFlushed = System.nanoTime(); + } + + public synchronized void write(ChunkPos pos, ByteBuffer buffer) { + try { + byte[] b = toByteArray(new ByteArrayInputStream(buffer.array())); + int uncompressedSize = b.length; + + int maxCompressedLength = this.compressor.maxCompressedLength(b.length); + byte[] compressed = new byte[maxCompressedLength]; + int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength); + b = new byte[compressedLength]; + System.arraycopy(compressed, 0, b, 0, compressedLength); + + int index = getChunkIndex(pos.x, pos.z); + this.buffer[index] = b; + this.chunkTimestamps[index] = getTimestamp(); + this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; + } catch (IOException e) { + LOGGER.error("Chunk write IOException {} {}", e, this.path); + } + + if ((System.nanoTime() - this.lastFlushed) >= TimeUnit.NANOSECONDS.toSeconds(RegionFormatConfig.linearFlushFrequency)) { + this.flushWrapper(); + } + } + + public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { + return new DataOutputStream(new BufferedOutputStream(new ChunkBuffer(pos))); + } + + private byte[] toByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] tempBuffer = new byte[4096]; + + int length; + while ((length = in.read(tempBuffer)) >= 0) { + out.write(tempBuffer, 0, length); + } + + return out.toByteArray(); + } + + @Nullable + public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { + if (this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { + byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]]; + this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]); + return new DataInputStream(new ByteArrayInputStream(content)); + } + return null; + } + + public void clear(ChunkPos pos) { + int i = getChunkIndex(pos.x, pos.z); + this.buffer[i] = null; + this.bufferUncompressedSize[i] = 0; + this.chunkTimestamps[i] = getTimestamp(); + this.flushWrapper(); + } + + public Path getPath() { + return this.path; + } + + public boolean hasChunk(ChunkPos pos) { + return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0; + } + + public void close() throws IOException { + if (closed) return; + closed = true; + flush(); // sync + } + + public boolean recalculateHeader() { + return false; + } + + public void setOversized(int x, int z, boolean something) { + } + + public CompoundTag getOversizedData(int x, int z) throws IOException { + throw new IOException("getOversizedData is a stub " + this.path); + } + + public boolean isOversized(int x, int z) { + return false; + } + + private class ChunkBuffer extends ByteArrayOutputStream { + private final ChunkPos pos; + + public ChunkBuffer(ChunkPos chunkcoordintpair) { + super(); + this.pos = chunkcoordintpair; + } + + public void close() { + ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); + LinearRegionFile.this.write(this.pos, bytebuffer); + } + } +} diff --git a/Leaf-Server/src/main/java/su/plo/matter/Globals.java b/Leaf-Server/src/main/java/su/plo/matter/Globals.java new file mode 100644 index 00000000..d325d161 --- /dev/null +++ b/Leaf-Server/src/main/java/su/plo/matter/Globals.java @@ -0,0 +1,94 @@ +package su.plo.matter; + +import com.google.common.collect.Iterables; +import net.minecraft.server.level.ServerLevel; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Optional; + +public class Globals { + public static final int WORLD_SEED_LONGS = 16; + public static final int WORLD_SEED_BITS = WORLD_SEED_LONGS * 64; + + public static final long[] worldSeed = new long[WORLD_SEED_LONGS]; + public static final ThreadLocal dimension = ThreadLocal.withInitial(() -> 0); + + public enum Salt { + UNDEFINED, + BASTION_FEATURE, + WOODLAND_MANSION_FEATURE, + MINESHAFT_FEATURE, + BURIED_TREASURE_FEATURE, + NETHER_FORTRESS_FEATURE, + PILLAGER_OUTPOST_FEATURE, + GEODE_FEATURE, + NETHER_FOSSIL_FEATURE, + OCEAN_MONUMENT_FEATURE, + RUINED_PORTAL_FEATURE, + POTENTIONAL_FEATURE, + GENERATE_FEATURE, + JIGSAW_PLACEMENT, + STRONGHOLDS, + POPULATION, + DECORATION, + SLIME_CHUNK + } + + public static void setupGlobals(ServerLevel world) { + if (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) return; + + long[] seed = world.getServer().getWorldData().worldGenOptions().featureSeed(); + System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS); + int worldIndex = Iterables.indexOf(world.getServer().levelKeys(), it -> it == world.dimension()); + if (worldIndex == -1) + worldIndex = world.getServer().levelKeys().size(); // if we are in world construction it may not have been added to the map yet + dimension.set(worldIndex); + } + + public static long[] createRandomWorldSeed() { + long[] seed = new long[WORLD_SEED_LONGS]; + SecureRandom rand = new SecureRandom(); + for (int i = 0; i < WORLD_SEED_LONGS; i++) { + seed[i] = rand.nextLong(); + } + return seed; + } + + // 1024-bit string -> 16 * 64 long[] + public static Optional parseSeed(String seedStr) { + if (seedStr.isEmpty()) return Optional.empty(); + + if (seedStr.length() != WORLD_SEED_BITS) { + throw new IllegalArgumentException("Secure seed length must be " + WORLD_SEED_BITS + "-bit but found " + seedStr.length() + "-bit."); + } + + long[] seed = new long[WORLD_SEED_LONGS]; + + for (int i = 0; i < WORLD_SEED_LONGS; i++) { + int start = i * 64; + int end = start + 64; + String seedSection = seedStr.substring(start, end); + + BigInteger seedInDecimal = new BigInteger(seedSection, 2); + seed[i] = seedInDecimal.longValue(); + } + + return Optional.of(seed); + } + + // 16 * 64 long[] -> 1024-bit string + public static String seedToString(long[] seed) { + StringBuilder sb = new StringBuilder(); + + for (long longV : seed) { + // Convert to 64-bit binary string per long + // Use format to keep 64-bit length, and use 0 to complete space + String binaryStr = String.format("%64s", Long.toBinaryString(longV)).replace(' ', '0'); + + sb.append(binaryStr); + } + + return sb.toString(); + } +} diff --git a/Leaf-Server/src/main/java/su/plo/matter/Hashing.java b/Leaf-Server/src/main/java/su/plo/matter/Hashing.java new file mode 100644 index 00000000..ec7a57c6 --- /dev/null +++ b/Leaf-Server/src/main/java/su/plo/matter/Hashing.java @@ -0,0 +1,73 @@ +package su.plo.matter; + +public class Hashing { + // https://en.wikipedia.org/wiki/BLAKE_(hash_function) + // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java + + private final static long[] blake2b_IV = { + 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, + 0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, + 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L + }; + + private final static byte[][] blake2b_sigma = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3} + }; + + public static long[] hashWorldSeed(long[] worldSeed) { + long[] result = blake2b_IV.clone(); + result[0] ^= 0x01010040; + hash(worldSeed, result, new long[16], 0, false); + return result; + } + + public static void hash(long[] message, long[] chainValue, long[] internalState, long messageOffset, boolean isFinal) { + assert message.length == 16; + assert chainValue.length == 8; + assert internalState.length == 16; + + System.arraycopy(chainValue, 0, internalState, 0, chainValue.length); + System.arraycopy(blake2b_IV, 0, internalState, chainValue.length, 4); + internalState[12] = messageOffset ^ blake2b_IV[4]; + internalState[13] = blake2b_IV[5]; + if (isFinal) internalState[14] = ~blake2b_IV[6]; + internalState[15] = blake2b_IV[7]; + + for (int round = 0; round < 12; round++) { + G(message[blake2b_sigma[round][0]], message[blake2b_sigma[round][1]], 0, 4, 8, 12, internalState); + G(message[blake2b_sigma[round][2]], message[blake2b_sigma[round][3]], 1, 5, 9, 13, internalState); + G(message[blake2b_sigma[round][4]], message[blake2b_sigma[round][5]], 2, 6, 10, 14, internalState); + G(message[blake2b_sigma[round][6]], message[blake2b_sigma[round][7]], 3, 7, 11, 15, internalState); + G(message[blake2b_sigma[round][8]], message[blake2b_sigma[round][9]], 0, 5, 10, 15, internalState); + G(message[blake2b_sigma[round][10]], message[blake2b_sigma[round][11]], 1, 6, 11, 12, internalState); + G(message[blake2b_sigma[round][12]], message[blake2b_sigma[round][13]], 2, 7, 8, 13, internalState); + G(message[blake2b_sigma[round][14]], message[blake2b_sigma[round][15]], 3, 4, 9, 14, internalState); + } + + for (int i = 0; i < 8; i++) { + chainValue[i] ^= internalState[i] ^ internalState[i + 8]; + } + } + + private static void G(long m1, long m2, int posA, int posB, int posC, int posD, long[] internalState) { + internalState[posA] = internalState[posA] + internalState[posB] + m1; + internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32); + internalState[posC] = internalState[posC] + internalState[posD]; + internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE + internalState[posA] = internalState[posA] + internalState[posB] + m2; + internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16); + internalState[posC] = internalState[posC] + internalState[posD]; + internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE + } +} diff --git a/Leaf-Server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java b/Leaf-Server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java new file mode 100644 index 00000000..42f4ca3f --- /dev/null +++ b/Leaf-Server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java @@ -0,0 +1,159 @@ +package su.plo.matter; + +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.WorldgenRandom; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +public class WorldgenCryptoRandom extends WorldgenRandom { + // hash the world seed to guard against badly chosen world seeds + private static final long[] HASHED_ZERO_SEED = Hashing.hashWorldSeed(new long[Globals.WORLD_SEED_LONGS]); + private static final ThreadLocal LAST_SEEN_WORLD_SEED = ThreadLocal.withInitial(() -> new long[Globals.WORLD_SEED_LONGS]); + private static final ThreadLocal HASHED_WORLD_SEED = ThreadLocal.withInitial(() -> HASHED_ZERO_SEED); + + private final long[] worldSeed = new long[Globals.WORLD_SEED_LONGS]; + private final long[] randomBits = new long[8]; + private int randomBitIndex; + private static final int MAX_RANDOM_BIT_INDEX = 64 * 8; + private static final int LOG2_MAX_RANDOM_BIT_INDEX = 9; + private long counter; + private final long[] message = new long[16]; + private final long[] cachedInternalState = new long[16]; + + public WorldgenCryptoRandom(int x, int z, Globals.Salt typeSalt, long salt) { + super(org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); + if (typeSalt != null) { + this.setSecureSeed(x, z, typeSalt, salt); + } + } + + public void setSecureSeed(int x, int z, Globals.Salt typeSalt, long salt) { + System.arraycopy(Globals.worldSeed, 0, this.worldSeed, 0, Globals.WORLD_SEED_LONGS); + message[0] = ((long) x << 32) | ((long) z & 0xffffffffL); + message[1] = ((long) Globals.dimension.get() << 32) | ((long) salt & 0xffffffffL); + message[2] = typeSalt.ordinal(); + message[3] = counter = 0; + randomBitIndex = MAX_RANDOM_BIT_INDEX; + } + + private long[] getHashedWorldSeed() { + if (!Arrays.equals(worldSeed, LAST_SEEN_WORLD_SEED.get())) { + HASHED_WORLD_SEED.set(Hashing.hashWorldSeed(worldSeed)); + System.arraycopy(worldSeed, 0, LAST_SEEN_WORLD_SEED.get(), 0, Globals.WORLD_SEED_LONGS); + } + return HASHED_WORLD_SEED.get(); + } + + private void moreRandomBits() { + message[3] = counter++; + System.arraycopy(getHashedWorldSeed(), 0, randomBits, 0, 8); + Hashing.hash(message, randomBits, cachedInternalState, 64, true); + } + + private long getBits(int count) { + if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) { + moreRandomBits(); + randomBitIndex -= MAX_RANDOM_BIT_INDEX; + } + + int alignment = randomBitIndex & 63; + if ((randomBitIndex >>> 6) == ((randomBitIndex + count) >>> 6)) { + long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << count) - 1); + randomBitIndex += count; + return result; + } else { + long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << (64 - alignment)) - 1); + randomBitIndex += count; + if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) { + moreRandomBits(); + randomBitIndex -= MAX_RANDOM_BIT_INDEX; + } + alignment = randomBitIndex & 63; + result <<= alignment; + result |= (randomBits[randomBitIndex >>> 6] >>> (64 - alignment)) & ((1L << alignment) - 1); + + return result; + } + } + + @Override + public @NotNull RandomSource fork() { + WorldgenCryptoRandom fork = new WorldgenCryptoRandom(0, 0, null, 0); + + System.arraycopy(Globals.worldSeed, 0, fork.worldSeed, 0, Globals.WORLD_SEED_LONGS); + fork.message[0] = this.message[0]; + fork.message[1] = this.message[1]; + fork.message[2] = this.message[2]; + fork.message[3] = this.message[3]; + fork.randomBitIndex = this.randomBitIndex; + fork.counter = this.counter; + fork.nextLong(); + + return fork; + } + + @Override + public int next(int bits) { + return (int) getBits(bits); + } + + @Override + public void consumeCount(int count) { + randomBitIndex += count; + if (randomBitIndex >= MAX_RANDOM_BIT_INDEX * 2) { + randomBitIndex -= MAX_RANDOM_BIT_INDEX; + counter += randomBitIndex >>> LOG2_MAX_RANDOM_BIT_INDEX; + randomBitIndex &= MAX_RANDOM_BIT_INDEX - 1; + randomBitIndex += MAX_RANDOM_BIT_INDEX; + } + } + + @Override + public int nextInt(int bound) { + int bits = Mth.ceillog2(bound); + int result; + do { + result = (int) getBits(bits); + } while (result >= bound); + + return result; + } + + @Override + public long nextLong() { + return getBits(64); + } + + @Override + public double nextDouble() { + return getBits(53) * 0x1.0p-53; + } + + @Override + public long setDecorationSeed(long worldSeed, int blockX, int blockZ) { + setSecureSeed(blockX, blockZ, Globals.Salt.POPULATION, 0); + return ((long) blockX << 32) | ((long) blockZ & 0xffffffffL); + } + + @Override + public void setFeatureSeed(long populationSeed, int index, int step) { + setSecureSeed((int) (populationSeed >> 32), (int) populationSeed, Globals.Salt.DECORATION, index + 10000L * step); + } + + @Override + public void setLargeFeatureSeed(long worldSeed, int chunkX, int chunkZ) { + super.setLargeFeatureSeed(worldSeed, chunkX, chunkZ); + } + + @Override + public void setLargeFeatureWithSalt(long worldSeed, int regionX, int regionZ, int salt) { + super.setLargeFeatureWithSalt(worldSeed, regionX, regionZ, salt); + } + + public static RandomSource seedSlimeChunk(int chunkX, int chunkZ) { + return new WorldgenCryptoRandom(chunkX, chunkZ, Globals.Salt.SLIME_CHUNK, 0); + } +} diff --git a/Leaf-api-generator/paper-patches/features/0001-Purpur-generated-api-Changes.patch b/Leaf-api-generator/paper-patches/features/0001-Purpur-generated-api-Changes.patch new file mode 100644 index 00000000..90ead649 --- /dev/null +++ b/Leaf-api-generator/paper-patches/features/0001-Purpur-generated-api-Changes.patch @@ -0,0 +1,483 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Github Actions +Date: Thu, 16 Jan 2025 11:21:11 +0000 +Subject: [PATCH] Purpur generated-api Changes + +Original license: MIT +Original project: https://github.com/PurpurMC/Purpur + +Commit: dd4143984219cea8440913b7918322b5ba59265a + +diff --git a/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f09fefe6821d8b2b8c8f055985bacc2e042ca569 +--- /dev/null ++++ b/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -0,0 +1,467 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.destroystokyo.paper.entity.RangedEntity; ++import io.papermc.paper.entity.SchoolableFish; ++import io.papermc.paper.generated.GeneratedFrom; ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.AbstractHorse; ++import org.bukkit.entity.AbstractSkeleton; ++import org.bukkit.entity.AbstractVillager; ++import org.bukkit.entity.Animals; ++import org.bukkit.entity.Bee; ++import org.bukkit.entity.Blaze; ++import org.bukkit.entity.Cat; ++import org.bukkit.entity.Creature; ++import org.bukkit.entity.Creeper; ++import org.bukkit.entity.Dolphin; ++import org.bukkit.entity.Drowned; ++import org.bukkit.entity.Enderman; ++import org.bukkit.entity.Evoker; ++import org.bukkit.entity.Fish; ++import org.bukkit.entity.Fox; ++import org.bukkit.entity.Ghast; ++import org.bukkit.entity.Guardian; ++import org.bukkit.entity.Illager; ++import org.bukkit.entity.Illusioner; ++import org.bukkit.entity.IronGolem; ++import org.bukkit.entity.Llama; ++import org.bukkit.entity.Mob; ++import org.bukkit.entity.Monster; ++import org.bukkit.entity.Ocelot; ++import org.bukkit.entity.Panda; ++import org.bukkit.entity.Parrot; ++import org.bukkit.entity.Phantom; ++import org.bukkit.entity.PolarBear; ++import org.bukkit.entity.PufferFish; ++import org.bukkit.entity.Rabbit; ++import org.bukkit.entity.Raider; ++import org.bukkit.entity.Shulker; ++import org.bukkit.entity.Silverfish; ++import org.bukkit.entity.SkeletonHorse; ++import org.bukkit.entity.Slime; ++import org.bukkit.entity.Spellcaster; ++import org.bukkit.entity.Spider; ++import org.bukkit.entity.Squid; ++import org.bukkit.entity.Strider; ++import org.bukkit.entity.Tameable; ++import org.bukkit.entity.Turtle; ++import org.bukkit.entity.Vex; ++import org.bukkit.entity.Vindicator; ++import org.bukkit.entity.WanderingTrader; ++import org.bukkit.entity.Wither; ++import org.bukkit.entity.Wolf; ++import org.bukkit.entity.Zombie; ++import org.jspecify.annotations.NullMarked; ++ ++/** ++ * Vanilla keys for Mob Goals. ++ * ++ * @apiNote The fields provided here are a direct representation of ++ * what is available from the vanilla game source. They may be ++ * changed (including removals) on any Minecraft version ++ * bump, so cross-version compatibility is not provided on the ++ * same level as it is on most of the other API. ++ */ ++@SuppressWarnings({ ++ "unused", ++ "SpellCheckingInspection" ++}) ++@GeneratedFrom("1.21.4") ++@NullMarked ++public interface VanillaGoal extends Goal { ++ GoalKey RANDOM_STAND = create("random_stand", AbstractHorse.class); ++ ++ GoalKey RUN_AROUND_LIKE_CRAZY = create("run_around_like_crazy", AbstractHorse.class); ++ ++ GoalKey ABSTRACT_SKELETON_MELEE = create("abstract_skeleton_melee", AbstractSkeleton.class); ++ ++ GoalKey LOOK_AT_TRADING_PLAYER = create("look_at_trading_player", AbstractVillager.class); ++ ++ GoalKey TRADE_WITH_PLAYER = create("trade_with_player", AbstractVillager.class); ++ ++ GoalKey BREED = create("breed", Animals.class); ++ ++ GoalKey FOLLOW_PARENT = create("follow_parent", Animals.class); ++ ++ GoalKey BEE_ATTACK = create("bee_attack", Bee.class); ++ ++ GoalKey BEE_BECOME_ANGRY = create("bee_become_angry", Bee.class); ++ ++ GoalKey BEE_ENTER_HIVE = create("bee_enter_hive", Bee.class); ++ ++ GoalKey BEE_GO_TO_HIVE = create("bee_go_to_hive", Bee.class); ++ ++ GoalKey BEE_GO_TO_KNOWN_FLOWER = create("bee_go_to_known_flower", Bee.class); ++ ++ GoalKey BEE_GROW_CROP = create("bee_grow_crop", Bee.class); ++ ++ GoalKey BEE_HURT_BY_OTHER = create("bee_hurt_by_other", Bee.class); ++ ++ GoalKey BEE_LOCATE_HIVE = create("bee_locate_hive", Bee.class); ++ ++ GoalKey BEE_POLLINATE = create("bee_pollinate", Bee.class); ++ ++ GoalKey BEE_WANDER = create("bee_wander", Bee.class); ++ ++ GoalKey VALIDATE_FLOWER = create("validate_flower", Bee.class); ++ ++ GoalKey VALIDATE_HIVE = create("validate_hive", Bee.class); ++ ++ GoalKey BLAZE_ATTACK = create("blaze_attack", Blaze.class); ++ ++ GoalKey CAT_AVOID_ENTITY = create("cat_avoid_entity", Cat.class); ++ ++ GoalKey CAT_LIE_ON_BED = create("cat_lie_on_bed", Cat.class); ++ ++ GoalKey CAT_RELAX_ON_OWNER = create("cat_relax_on_owner", Cat.class); ++ ++ GoalKey CAT_SIT_ON_BLOCK = create("cat_sit_on_block", Cat.class); ++ ++ GoalKey CAT_TEMPT = create("cat_tempt", Cat.class); ++ ++ GoalKey AVOID_ENTITY = create("avoid_entity", Creature.class); ++ ++ GoalKey BREATH_AIR = create("breath_air", Creature.class); ++ ++ GoalKey DROWNED_GO_TO_WATER = create("drowned_go_to_water", Creature.class); ++ ++ GoalKey FLEE_SUN = create("flee_sun", Creature.class); ++ ++ GoalKey FOLLOW_BOAT = create("follow_boat", Creature.class); ++ ++ GoalKey GOLEM_RANDOM_STROLL_IN_VILLAGE = create("golem_random_stroll_in_village", Creature.class); ++ ++ GoalKey HURT_BY = create("hurt_by", Creature.class); ++ ++ GoalKey MELEE_ATTACK = create("melee_attack", Creature.class); ++ ++ GoalKey MOVE_BACK_TO_VILLAGE = create("move_back_to_village", Creature.class); ++ ++ GoalKey MOVE_THROUGH_VILLAGE = create("move_through_village", Creature.class); ++ ++ GoalKey MOVE_TOWARDS = create("move_towards", Creature.class); ++ ++ GoalKey MOVE_TOWARDS_RESTRICTION = create("move_towards_restriction", Creature.class); ++ ++ GoalKey PANIC = create("panic", Creature.class); ++ ++ GoalKey PARROT_WANDER = create("parrot_wander", Creature.class); ++ ++ GoalKey RANDOM_STROLL = create("random_stroll", Creature.class); ++ ++ GoalKey RANDOM_SWIMMING = create("random_swimming", Creature.class); ++ ++ GoalKey REMOVE_BLOCK = create("remove_block", Creature.class); ++ ++ GoalKey RESTRICT_SUN = create("restrict_sun", Creature.class); ++ ++ GoalKey STROLL_THROUGH_VILLAGE = create("stroll_through_village", Creature.class); ++ ++ GoalKey TEMPT = create("tempt", Creature.class); ++ ++ GoalKey TRY_FIND_WATER = create("try_find_water", Creature.class); ++ ++ GoalKey WATER_AVOIDING_RANDOM_FLYING = create("water_avoiding_random_flying", Creature.class); ++ ++ GoalKey WATER_AVOIDING_RANDOM_STROLL = create("water_avoiding_random_stroll", Creature.class); ++ ++ GoalKey SWELL = create("swell", Creeper.class); ++ ++ GoalKey DOLPHIN_JUMP = create("dolphin_jump", Dolphin.class); ++ ++ GoalKey DOLPHIN_SWIM_TO_TREASURE = create("dolphin_swim_to_treasure", Dolphin.class); ++ ++ GoalKey DOLPHIN_SWIM_WITH_PLAYER = create("dolphin_swim_with_player", Dolphin.class); ++ ++ GoalKey PLAY_WITH_ITEMS = create("play_with_items", Dolphin.class); ++ ++ GoalKey DROWNED_ATTACK = create("drowned_attack", Drowned.class); ++ ++ GoalKey DROWNED_GO_TO_BEACH = create("drowned_go_to_beach", Drowned.class); ++ ++ GoalKey DROWNED_SWIM_UP = create("drowned_swim_up", Drowned.class); ++ ++ GoalKey ENDERMAN_FREEZE_WHEN_LOOKED_AT = create("enderman_freeze_when_looked_at", Enderman.class); ++ ++ GoalKey ENDERMAN_LEAVE_BLOCK = create("enderman_leave_block", Enderman.class); ++ ++ GoalKey ENDERMAN_LOOK_FOR_PLAYER = create("enderman_look_for_player", Enderman.class); ++ ++ GoalKey ENDERMAN_TAKE_BLOCK = create("enderman_take_block", Enderman.class); ++ ++ GoalKey EVOKER_ATTACK_SPELL = create("evoker_attack_spell", Evoker.class); ++ ++ GoalKey EVOKER_CASTING_SPELL = create("evoker_casting_spell", Evoker.class); ++ ++ GoalKey EVOKER_SUMMON_SPELL = create("evoker_summon_spell", Evoker.class); ++ ++ GoalKey EVOKER_WOLOLO_SPELL = create("evoker_wololo_spell", Evoker.class); ++ ++ GoalKey FISH_SWIM = create("fish_swim", Fish.class); ++ ++ GoalKey DEFEND_TRUSTED = create("defend_trusted", Fox.class); ++ ++ GoalKey FACEPLANT = create("faceplant", Fox.class); ++ ++ GoalKey FOX_BREED = create("fox_breed", Fox.class); ++ ++ GoalKey FOX_EAT_BERRIES = create("fox_eat_berries", Fox.class); ++ ++ GoalKey FOX_FLOAT = create("fox_float", Fox.class); ++ ++ GoalKey FOX_FOLLOW_PARENT = create("fox_follow_parent", Fox.class); ++ ++ GoalKey FOX_LOOK_AT_PLAYER = create("fox_look_at_player", Fox.class); ++ ++ GoalKey FOX_MELEE_ATTACK = create("fox_melee_attack", Fox.class); ++ ++ GoalKey FOX_PANIC = create("fox_panic", Fox.class); ++ ++ GoalKey FOX_POUNCE = create("fox_pounce", Fox.class); ++ ++ GoalKey FOX_SEARCH_FOR_ITEMS = create("fox_search_for_items", Fox.class); ++ ++ GoalKey FOX_STROLL_THROUGH_VILLAGE = create("fox_stroll_through_village", Fox.class); ++ ++ GoalKey PERCH_AND_SEARCH = create("perch_and_search", Fox.class); ++ ++ GoalKey SEEK_SHELTER = create("seek_shelter", Fox.class); ++ ++ GoalKey SLEEP = create("sleep", Fox.class); ++ ++ GoalKey STALK_PREY = create("stalk_prey", Fox.class); ++ ++ GoalKey GHAST_LOOK = create("ghast_look", Ghast.class); ++ ++ GoalKey GHAST_SHOOT_FIREBALL = create("ghast_shoot_fireball", Ghast.class); ++ ++ GoalKey RANDOM_FLOAT_AROUND = create("random_float_around", Ghast.class); ++ ++ GoalKey GUARDIAN_ATTACK = create("guardian_attack", Guardian.class); ++ ++ GoalKey HOLD_GROUND_ATTACK = create("hold_ground_attack", Illager.class); ++ ++ GoalKey RAIDER_OPEN_DOOR = create("raider_open_door", Illager.class); ++ ++ GoalKey ILLUSIONER_BLINDNESS_SPELL = create("illusioner_blindness_spell", Illusioner.class); ++ ++ GoalKey ILLUSIONER_MIRROR_SPELL = create("illusioner_mirror_spell", Illusioner.class); ++ ++ GoalKey DEFEND_VILLAGE = create("defend_village", IronGolem.class); ++ ++ GoalKey OFFER_FLOWER = create("offer_flower", IronGolem.class); ++ ++ GoalKey LLAMA_ATTACK_WOLF = create("llama_attack_wolf", Llama.class); ++ ++ GoalKey LLAMA_FOLLOW_CARAVAN = create("llama_follow_caravan", Llama.class); ++ ++ GoalKey LLAMA_HURT_BY = create("llama_hurt_by", Llama.class); ++ ++ GoalKey TRADER_LLAMA_DEFEND_WANDERING_TRADER = create("trader_llama_defend_wandering_trader", Llama.class); ++ ++ GoalKey BREAK_DOOR = create("break_door", Mob.class); ++ ++ GoalKey CLIMB_ON_TOP_OF_POWDER_SNOW = create("climb_on_top_of_powder_snow", Mob.class); ++ ++ GoalKey EAT_BLOCK = create("eat_block", Mob.class); ++ ++ GoalKey FLOAT = create("float", Mob.class); ++ ++ GoalKey FOLLOW_MOB = create("follow_mob", Mob.class); ++ ++ GoalKey INTERACT = create("interact", Mob.class); ++ ++ GoalKey LEAP_AT = create("leap_at", Mob.class); ++ ++ GoalKey LOOK_AT_PLAYER = create("look_at_player", Mob.class); ++ ++ GoalKey NEAREST_ATTACKABLE = create("nearest_attackable", Mob.class); ++ ++ GoalKey OCELOT_ATTACK = create("ocelot_attack", Mob.class); ++ ++ GoalKey OPEN_DOOR = create("open_door", Mob.class); ++ ++ GoalKey RANDOM_LOOK_AROUND = create("random_look_around", Mob.class); ++ ++ GoalKey RESET_UNIVERSAL_ANGER = create("reset_universal_anger", Mob.class); ++ ++ GoalKey USE_ITEM = create("use_item", Mob.class); ++ ++ GoalKey VINDICATOR_BREAK_DOOR = create("vindicator_break_door", Mob.class); ++ ++ GoalKey RANGED_BOW_ATTACK = create("ranged_bow_attack", Monster.class); ++ ++ GoalKey RANGED_CROSSBOW_ATTACK = create("ranged_crossbow_attack", Monster.class); ++ ++ GoalKey OCELOT_AVOID_ENTITY = create("ocelot_avoid_entity", Ocelot.class); ++ ++ GoalKey OCELOT_TEMPT = create("ocelot_tempt", Ocelot.class); ++ ++ GoalKey PANDA_ATTACK = create("panda_attack", Panda.class); ++ ++ GoalKey PANDA_AVOID = create("panda_avoid", Panda.class); ++ ++ GoalKey PANDA_BREED = create("panda_breed", Panda.class); ++ ++ GoalKey PANDA_HURT_BY = create("panda_hurt_by", Panda.class); ++ ++ GoalKey PANDA_LIE_ON_BACK = create("panda_lie_on_back", Panda.class); ++ ++ GoalKey PANDA_LOOK_AT_PLAYER = create("panda_look_at_player", Panda.class); ++ ++ GoalKey PANDA_PANIC = create("panda_panic", Panda.class); ++ ++ GoalKey PANDA_ROLL = create("panda_roll", Panda.class); ++ ++ GoalKey PANDA_SIT = create("panda_sit", Panda.class); ++ ++ GoalKey PANDA_SNEEZE = create("panda_sneeze", Panda.class); ++ ++ GoalKey LAND_ON_OWNERS_SHOULDER = create("land_on_owners_shoulder", Parrot.class); ++ ++ GoalKey PHANTOM_ATTACK_PLAYER = create("phantom_attack_player", Phantom.class); ++ ++ GoalKey PHANTOM_ATTACK_STRATEGY = create("phantom_attack_strategy", Phantom.class); ++ ++ GoalKey PHANTOM_CIRCLE_AROUND_ANCHOR = create("phantom_circle_around_anchor", Phantom.class); ++ ++ GoalKey PHANTOM_SWEEP_ATTACK = create("phantom_sweep_attack", Phantom.class); ++ ++ GoalKey POLAR_BEAR_ATTACK_PLAYERS = create("polar_bear_attack_players", PolarBear.class); ++ ++ GoalKey POLAR_BEAR_HURT_BY = create("polar_bear_hurt_by", PolarBear.class); ++ ++ GoalKey POLAR_BEAR_MELEE_ATTACK = create("polar_bear_melee_attack", PolarBear.class); ++ ++ GoalKey PUFFERFISH_PUFF = create("pufferfish_puff", PufferFish.class); ++ ++ GoalKey RABBIT_AVOID_ENTITY = create("rabbit_avoid_entity", Rabbit.class); ++ ++ GoalKey RABBIT_PANIC = create("rabbit_panic", Rabbit.class); ++ ++ GoalKey RAID_GARDEN = create("raid_garden", Rabbit.class); ++ ++ GoalKey LONG_DISTANCE_PATROL = create("long_distance_patrol", Raider.class); ++ ++ GoalKey NEAREST_ATTACKABLE_WITCH = create("nearest_attackable_witch", Raider.class); ++ ++ GoalKey NEAREST_HEALABLE_RAIDER = create("nearest_healable_raider", Raider.class); ++ ++ GoalKey OBTAIN_RAID_LEADER_BANNER = create("obtain_raid_leader_banner", Raider.class); ++ ++ GoalKey PATHFIND_TO_RAID = create("pathfind_to_raid", Raider.class); ++ ++ GoalKey RAIDER_CELEBRATION = create("raider_celebration", Raider.class); ++ ++ GoalKey RAIDER_MOVE_THROUGH_VILLAGE = create("raider_move_through_village", Raider.class); ++ ++ GoalKey DROWNED_TRIDENT_ATTACK = create("drowned_trident_attack", RangedEntity.class); ++ ++ GoalKey RANGED_ATTACK = create("ranged_attack", RangedEntity.class); ++ ++ GoalKey FOLLOW_FLOCK_LEADER = create("follow_flock_leader", SchoolableFish.class); ++ ++ GoalKey SHULKER_ATTACK = create("shulker_attack", Shulker.class); ++ ++ GoalKey SHULKER_DEFENSE_ATTACK = create("shulker_defense_attack", Shulker.class); ++ ++ GoalKey SHULKER_NEAREST_ATTACK = create("shulker_nearest_attack", Shulker.class); ++ ++ GoalKey SHULKER_PEEK = create("shulker_peek", Shulker.class); ++ ++ GoalKey SILVERFISH_MERGE_WITH_STONE = create("silverfish_merge_with_stone", Silverfish.class); ++ ++ GoalKey SILVERFISH_WAKE_UP_FRIENDS = create("silverfish_wake_up_friends", Silverfish.class); ++ ++ GoalKey SKELETON_TRAP = create("skeleton_trap", SkeletonHorse.class); ++ ++ GoalKey SLIME_ATTACK = create("slime_attack", Slime.class); ++ ++ GoalKey SLIME_FLOAT = create("slime_float", Slime.class); ++ ++ GoalKey SLIME_KEEP_ON_JUMPING = create("slime_keep_on_jumping", Slime.class); ++ ++ GoalKey SLIME_RANDOM_DIRECTION = create("slime_random_direction", Slime.class); ++ ++ GoalKey SPELLCASTER_CASTING_SPELL = create("spellcaster_casting_spell", Spellcaster.class); ++ ++ GoalKey SPIDER = create("spider", Spider.class); ++ ++ GoalKey SPIDER_ATTACK = create("spider_attack", Spider.class); ++ ++ GoalKey SQUID_FLEE = create("squid_flee", Squid.class); ++ ++ GoalKey SQUID_RANDOM_MOVEMENT = create("squid_random_movement", Squid.class); ++ ++ GoalKey STRIDER_GO_TO_LAVA = create("strider_go_to_lava", Strider.class); ++ ++ GoalKey FOLLOW_OWNER = create("follow_owner", Tameable.class); ++ ++ GoalKey NON_TAME_RANDOM = create("non_tame_random", Tameable.class); ++ ++ GoalKey OWNER_HURT = create("owner_hurt", Tameable.class); ++ ++ GoalKey OWNER_HURT_BY = create("owner_hurt_by", Tameable.class); ++ ++ GoalKey SIT_WHEN_ORDERED_TO = create("sit_when_ordered_to", Tameable.class); ++ ++ GoalKey TAMABLE_ANIMAL_PANIC = create("tamable_animal_panic", Tameable.class); ++ ++ GoalKey TURTLE_BREED = create("turtle_breed", Turtle.class); ++ ++ GoalKey TURTLE_GO_HOME = create("turtle_go_home", Turtle.class); ++ ++ GoalKey TURTLE_GO_TO_WATER = create("turtle_go_to_water", Turtle.class); ++ ++ GoalKey TURTLE_LAY_EGG = create("turtle_lay_egg", Turtle.class); ++ ++ GoalKey TURTLE_PANIC = create("turtle_panic", Turtle.class); ++ ++ GoalKey TURTLE_RANDOM_STROLL = create("turtle_random_stroll", Turtle.class); ++ ++ GoalKey TURTLE_TRAVEL = create("turtle_travel", Turtle.class); ++ ++ GoalKey VEX_CHARGE_ATTACK = create("vex_charge_attack", Vex.class); ++ ++ GoalKey VEX_COPY_OWNER = create("vex_copy_owner", Vex.class); ++ ++ GoalKey VEX_RANDOM_MOVE = create("vex_random_move", Vex.class); ++ ++ GoalKey VINDICATOR_JOHNNY_ATTACK = create("vindicator_johnny_attack", Vindicator.class); ++ ++ GoalKey WANDER_TO_POSITION = create("wander_to_position", WanderingTrader.class); ++ ++ GoalKey WITHER_DO_NOTHING = create("wither_do_nothing", Wither.class); ++ ++ GoalKey BEG = create("beg", Wolf.class); ++ ++ GoalKey WOLF_AVOID_ENTITY = create("wolf_avoid_entity", Wolf.class); ++ ++ GoalKey ZOMBIE_ATTACK = create("zombie_attack", Zombie.class); ++ ++ GoalKey ZOMBIE_ATTACK_TURTLE_EGG = create("zombie_attack_turtle_egg", Zombie.class); ++ ++ // Purpur start - Ridables ++ GoalKey MOB_HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider")); ++ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); ++ GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider")); ++ // Purpur end - Ridables ++ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms ++ GoalKey FIND_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal")); ++ GoalKey ORBIT_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal")); ++ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms ++ // Purpur start - Add option to disable zombie aggressiveness towards villagers when lagging ++ GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager")); ++ GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager")); ++ // Purpur end - Add option to disable zombie aggressiveness towards villagers when lagging ++ // Purpur start - Configurable chance for wolves to spawn rabid ++ GoalKey AVOID_RABID_WOLF = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolf")); ++ // Purpur end - Configurable chance for wolves to spawn rabid ++ // Purpur start - Iron golem poppy calms anger ++ GoalKey RECEIVE_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("receive_flower")); ++ // Purpur end - Iron golem poppy calms anger ++ ++ private static GoalKey create(final String key, final Class type) { ++ return GoalKey.of(type, NamespacedKey.minecraft(key)); ++ } ++} diff --git a/patches/server/0094-Fix-MC-65198.patch b/Leaf-archived-patches/hardfork/0094-Fix-MC-65198.patch similarity index 68% rename from patches/server/0094-Fix-MC-65198.patch rename to Leaf-archived-patches/hardfork/0094-Fix-MC-65198.patch index f4b36531..1b280edc 100644 --- a/patches/server/0094-Fix-MC-65198.patch +++ b/Leaf-archived-patches/hardfork/0094-Fix-MC-65198.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix MC-65198 Mojang issues: https://bugs.mojang.com/browse/MC-65198 -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +diff --git a/net/minecraft/world/inventory/ItemCombinerMenu.java b/net/minecraft/world/inventory/ItemCombinerMenu.java index ac9df238ef0f3d009f25976b95e0b750e963e952..b94daec80a5222468d6065cec4ac693a1de92b38 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +--- a/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/net/minecraft/world/inventory/ItemCombinerMenu.java @@ -129,6 +129,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { ItemStack itemstack1 = slot1.getItem(); @@ -26,10 +26,10 @@ index ac9df238ef0f3d009f25976b95e0b750e963e952..b94daec80a5222468d6065cec4ac693a this.activeQuickItem = null; // Purpur - Anvil API } -diff --git a/src/main/java/net/minecraft/world/inventory/ResultSlot.java b/src/main/java/net/minecraft/world/inventory/ResultSlot.java +diff --git a/net/minecraft/world/inventory/ResultSlot.java b/net/minecraft/world/inventory/ResultSlot.java index ff30071f3ef37d1b28cf86e26ce4f7477335a07a..78122f7aaa095278095a57974b9906f7999a17df 100644 ---- a/src/main/java/net/minecraft/world/inventory/ResultSlot.java -+++ b/src/main/java/net/minecraft/world/inventory/ResultSlot.java +--- a/net/minecraft/world/inventory/ResultSlot.java ++++ b/net/minecraft/world/inventory/ResultSlot.java @@ -49,7 +49,7 @@ public class ResultSlot extends Slot { @Override protected void checkTakeAchievements(ItemStack stack) { @@ -39,10 +39,10 @@ index ff30071f3ef37d1b28cf86e26ce4f7477335a07a..78122f7aaa095278095a57974b9906f7 } if (this.container instanceof RecipeCraftingHolder recipeCraftingHolder) { -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index ca65965757e6f12abc972250a04817c7547bb0bd..dae30f13f970f5006aecaca3788963fbd8b12c58 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +diff --git a/net/minecraft/world/inventory/StonecutterMenu.java b/net/minecraft/world/inventory/StonecutterMenu.java +index ca65965757e6f12abc972250a04817c7547bb0bd..675300ca10b2328be102a7cbc447e1c25ef12f82 100644 +--- a/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/net/minecraft/world/inventory/StonecutterMenu.java @@ -255,6 +255,7 @@ public class StonecutterMenu extends AbstractContainerMenu { Item item = itemstack1.getItem(); @@ -51,12 +51,15 @@ index ca65965757e6f12abc972250a04817c7547bb0bd..dae30f13f970f5006aecaca3788963fb if (slot == 1) { item.onCraftedBy(itemstack1, player.level(), player); if (!this.moveItemStackTo(itemstack1, 2, 38, true)) { -@@ -287,7 +288,7 @@ public class StonecutterMenu extends AbstractContainerMenu { +@@ -287,9 +288,9 @@ public class StonecutterMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } - slot1.onTake(player, itemstack1); + slot1.onTake(player, itemStack2); // Leaf - Fix MC-65198 if (slot == 1) { - player.drop(itemstack1, false); +- player.drop(itemstack1, false); ++ player.drop(itemStack2, false); // Leaf - Fix MC-65198 } + + this.broadcastChanges(); diff --git a/patches/server/0095-Fix-MC-200418.patch b/Leaf-archived-patches/hardfork/0095-Fix-MC-200418.patch similarity index 77% rename from patches/server/0095-Fix-MC-200418.patch rename to Leaf-archived-patches/hardfork/0095-Fix-MC-200418.patch index 7de659c0..43bd0e06 100644 --- a/patches/server/0095-Fix-MC-200418.patch +++ b/Leaf-archived-patches/hardfork/0095-Fix-MC-200418.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix-MC-200418 Related MC issue: https://bugs.mojang.com/browse/MC-200418 -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java index 160fcd1f8917d69dde01089111661337827da20a..0963ccca2f5f38a415bc733333976d9df67378a1 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java @@ -327,6 +327,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { if (!this.isSilent()) { world.levelEvent((Player) null, 1027, this.blockPosition(), 0); diff --git a/patches/server/0096-Fix-MC-119417.patch b/Leaf-archived-patches/hardfork/0096-Fix-MC-119417.patch similarity index 77% rename from patches/server/0096-Fix-MC-119417.patch rename to Leaf-archived-patches/hardfork/0096-Fix-MC-119417.patch index 7a9a2b83..935c4e4c 100644 --- a/patches/server/0096-Fix-MC-119417.patch +++ b/Leaf-archived-patches/hardfork/0096-Fix-MC-119417.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix-MC-119417 Related MC issue: https://bugs.mojang.com/browse/MC-119417 -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java index 3eb3cd1089ec46c64f82e99f25d19cee9e0cdfbe..a27b0a3895290f5abb3a8e07fb886530fdf28c75 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java @@ -2507,6 +2507,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); if (gameMode == GameType.SPECTATOR) { diff --git a/patches/server/0097-Fix-MC-223153.patch b/Leaf-archived-patches/hardfork/0097-Fix-MC-223153.patch similarity index 73% rename from patches/server/0097-Fix-MC-223153.patch rename to Leaf-archived-patches/hardfork/0097-Fix-MC-223153.patch index 361d76ac..7805cdba 100644 --- a/patches/server/0097-Fix-MC-223153.patch +++ b/Leaf-archived-patches/hardfork/0097-Fix-MC-223153.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix-MC-223153 Related MC issue: https://bugs.mojang.com/browse/MC-223153 -diff --git a/src/main/java/net/minecraft/world/level/block/Blocks.java b/src/main/java/net/minecraft/world/level/block/Blocks.java +diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java index 63d67d46d30ed8ed57cdc0e59b6cb6b75ab22c1f..539b5625ba0ef7389ff1e7041af86f538640f3d9 100644 ---- a/src/main/java/net/minecraft/world/level/block/Blocks.java -+++ b/src/main/java/net/minecraft/world/level/block/Blocks.java +--- a/net/minecraft/world/level/block/Blocks.java ++++ b/net/minecraft/world/level/block/Blocks.java @@ -6611,6 +6611,7 @@ public class Blocks { .mapColor(MapColor.COLOR_ORANGE) .instrument(NoteBlockInstrument.BASEDRUM) diff --git a/patches/server/0098-Fix-MC-177381.patch b/Leaf-archived-patches/hardfork/0098-Fix-MC-177381.patch similarity index 74% rename from patches/server/0098-Fix-MC-177381.patch rename to Leaf-archived-patches/hardfork/0098-Fix-MC-177381.patch index 39333ba9..c0c05e90 100644 --- a/patches/server/0098-Fix-MC-177381.patch +++ b/Leaf-archived-patches/hardfork/0098-Fix-MC-177381.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix MC-177381 Related MC issue: https://bugs.mojang.com/browse/MC-177381 -diff --git a/src/main/java/net/minecraft/server/commands/LocateCommand.java b/src/main/java/net/minecraft/server/commands/LocateCommand.java +diff --git a/net/minecraft/server/commands/LocateCommand.java b/net/minecraft/server/commands/LocateCommand.java index 2972f041eea95b92b37c2ab869f9f8ed3d142a27..dcdde4cd7f15d34eabba4b3802971db20e6ae9d2 100644 ---- a/src/main/java/net/minecraft/server/commands/LocateCommand.java -+++ b/src/main/java/net/minecraft/server/commands/LocateCommand.java +--- a/net/minecraft/server/commands/LocateCommand.java ++++ b/net/minecraft/server/commands/LocateCommand.java @@ -196,8 +196,10 @@ public class LocateCommand { } diff --git a/patches/server/0100-Optimize-LeavesProtocolManager-init-protocol.patch b/Leaf-archived-patches/hardfork/0100-Optimize-LeavesProtocolManager-init-protocol.patch similarity index 100% rename from patches/server/0100-Optimize-LeavesProtocolManager-init-protocol.patch rename to Leaf-archived-patches/hardfork/0100-Optimize-LeavesProtocolManager-init-protocol.patch diff --git a/patches/server/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch b/Leaf-archived-patches/hardfork/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch similarity index 86% rename from patches/server/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch rename to Leaf-archived-patches/hardfork/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch index 6db035c5..f663d92c 100644 --- a/patches/server/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch +++ b/Leaf-archived-patches/hardfork/0101-Cache-CraftEntityType-minecraftToBukkit-convert.patch @@ -7,10 +7,10 @@ The minecraftToBukkit EntityType convert call is expensive in mob spawn. This co and the results are always same, thus there is no need to do the convert process every time, just cache it. Save ~0.16ms per tick, and improve 11660ms -> 60ms in around 1 hour. -diff --git a/src/main/java/net/minecraft/util/SpawnUtil.java b/src/main/java/net/minecraft/util/SpawnUtil.java +diff --git a/net/minecraft/util/SpawnUtil.java b/net/minecraft/util/SpawnUtil.java index 34c3bf85473b3ad89355ebc21b68c59b3c683b84..a86955c3afc3468e92fb54c5ee0bf9c592f0b0cb 100644 ---- a/src/main/java/net/minecraft/util/SpawnUtil.java -+++ b/src/main/java/net/minecraft/util/SpawnUtil.java +--- a/net/minecraft/util/SpawnUtil.java ++++ b/net/minecraft/util/SpawnUtil.java @@ -38,7 +38,7 @@ public class SpawnUtil { // Paper start - PreCreatureSpawnEvent com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( @@ -20,10 +20,10 @@ index 34c3bf85473b3ad89355ebc21b68c59b3c683b84..a86955c3afc3468e92fb54c5ee0bf9c5 reason ); if (!event.callEvent()) { -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java index 002795df9c9c8d27f07f855dff148dfe353bef68..ca9459d3f8dbde237329dad1ec62f0791edb6a6c 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java +--- a/net/minecraft/world/entity/EntityType.java ++++ b/net/minecraft/world/entity/EntityType.java @@ -512,7 +512,7 @@ public class EntityType implements FeatureElement, EntityTypeT // Paper start - PreCreatureSpawnEvent com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( @@ -33,10 +33,10 @@ index 002795df9c9c8d27f07f855dff148dfe353bef68..ca9459d3f8dbde237329dad1ec62f079 spawnReason ); if (!event.callEvent()) { -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java index bb4411cfdf1bc7adc12c2f918d2eec830299f38b..b23397ae135f31abb7ac6bafd9064d7ef5e94218 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +--- a/net/minecraft/world/level/BaseSpawner.java ++++ b/net/minecraft/world/level/BaseSpawner.java @@ -137,7 +137,7 @@ public abstract class BaseSpawner { // Paper start - PreCreatureSpawnEvent com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( @@ -46,10 +46,10 @@ index bb4411cfdf1bc7adc12c2f918d2eec830299f38b..b23397ae135f31abb7ac6bafd9064d7e io.papermc.paper.util.MCUtil.toLocation(world, pos) ); if (!event.callEvent()) { -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java index 88b3715df673c6d12aea69fde075ad3caa8c51e8..cc07f55e65589549c349cc078afa3b0fa81c203d 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +--- a/net/minecraft/world/level/NaturalSpawner.java ++++ b/net/minecraft/world/level/NaturalSpawner.java @@ -368,7 +368,7 @@ public final class NaturalSpawner { // Paper start - PreCreatureSpawnEvent com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( @@ -208,30 +208,3 @@ index 4270b771ff15d398b4788cb2fe27236989077614..97a87083f651434e51a9176009230e09 + return this.getHandle().stream().map(Holder::value).map(CraftEntityType::minecraftToBukkitCached).collect(Collectors.toUnmodifiableSet()); // Leaf - Cache CraftEntityType#minecraftToBukkit convert } } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java b/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java -new file mode 100644 -index 0000000000000000000000000000000000000000..68ec35730c4d50602c85a502bc25d6a7cbc4a45b ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class EnableCachedMTBEntityTypeConvert extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".enable-cached-minecraft-to-bukkit-entitytype-convert", enabled, config.pickStringRegionBased(""" -+ Whether to cache expensive CraftEntityType#minecraftToBukkit call.""", -+ """ -+ 是否缓存Minecraft到Bukkit的实体类型转换.""")); -+ } -+} diff --git a/patches/server/0102-Configurable-player-knockback-zombie.patch b/Leaf-archived-patches/hardfork/0102-Configurable-player-knockback-zombie.patch similarity index 92% rename from patches/server/0102-Configurable-player-knockback-zombie.patch rename to Leaf-archived-patches/hardfork/0102-Configurable-player-knockback-zombie.patch index dde6fbd9..7bd0c1d8 100644 --- a/patches/server/0102-Configurable-player-knockback-zombie.patch +++ b/Leaf-archived-patches/hardfork/0102-Configurable-player-knockback-zombie.patch @@ -4,10 +4,10 @@ Date: Sun, 4 Aug 2024 19:34:29 +0800 Subject: [PATCH] Configurable player knockback zombie -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java index 7aab6970bf73108435e79a6ef39896e9fca8659e..b816d4509f5d1154fdbe462a0534a17c0d238281 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java @@ -2109,6 +2109,8 @@ public abstract class LivingEntity extends Entity implements Attackable { } diff --git a/patches/server/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch b/Leaf-archived-patches/hardfork/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch similarity index 60% rename from patches/server/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch rename to Leaf-archived-patches/hardfork/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch index 4deb89a8..a4d782dc 100644 --- a/patches/server/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch +++ b/Leaf-archived-patches/hardfork/0104-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch @@ -7,10 +7,10 @@ Original license: GPLv3 Original project: https://github.com/PaperMC/Paper Paper pull request: https://github.com/PaperMC/Paper/pull/10990 -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java index eb547af300d8ecea19b3e02e5ebe6139330c9d62..1e4729be4a245a811fd15ea1c02179b37defd67c 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java @@ -239,6 +239,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @Override public void inactiveTick() { @@ -23,10 +23,10 @@ index eb547af300d8ecea19b3e02e5ebe6139330c9d62..1e4729be4a245a811fd15ea1c02179b3 boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking this.goalSelector.tick(); -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java index fd373d98f836c057c30c4fbd5d7618cc4e757b78..50f080e91c0701b635b918ff15e07052052afb47 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java @@ -321,7 +321,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler if (this.getUnhappyCounter() > 0) { this.setUnhappyCounter(this.getUnhappyCounter() - 1); @@ -36,27 +36,3 @@ index fd373d98f836c057c30c4fbd5d7618cc4e757b78..50f080e91c0701b635b918ff15e07052 if (this.level().spigotConfig.tickInactiveVillagers) { this.customServerAiStep(this.level().getMinecraftWorld()); } else { -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java b/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f97ca316694e850e1626e223fb7c6da3f8ea9ca5 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/SkipAIForNonAwareMob.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class SkipAIForNonAwareMob extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config().getBoolean(getBasePath() + ".skip-ai-for-non-aware-mob", enabled); -+ } -+} diff --git a/patches/server/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch b/Leaf-archived-patches/hardfork/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch similarity index 88% rename from patches/server/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch rename to Leaf-archived-patches/hardfork/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch index 37a83b35..d3bde9c8 100644 --- a/patches/server/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch +++ b/Leaf-archived-patches/hardfork/0105-Paper-PR-Prevent-zombie-reinforcements-loading-chunk.patch @@ -11,10 +11,10 @@ When a zombie calls reinforcements it tries to spawn them in a random location w before spawning, it checks isSpawnPositionOk() for the position which loads the block to check if a mob can spawn on said block. This patch ensures the chunk at the random location is loaded before trying to spawn the reinforcement zombie in it. -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java index 5924509cbe36d3fee9d2f119d58e67c4b083e4c4..6efb548b6e1b466628eb70bc45ef98d09604c9df 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +--- a/net/minecraft/world/entity/monster/Zombie.java ++++ b/net/minecraft/world/entity/monster/Zombie.java @@ -405,6 +405,12 @@ public class Zombie extends Monster { int k1 = k + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1); BlockPos blockposition = new BlockPos(i1, j1, k1); diff --git a/patches/server/0106-PaperPR-Fix-some-beacon-event-issues.patch b/Leaf-archived-patches/hardfork/0106-PaperPR-Fix-some-beacon-event-issues.patch similarity index 89% rename from patches/server/0106-PaperPR-Fix-some-beacon-event-issues.patch rename to Leaf-archived-patches/hardfork/0106-PaperPR-Fix-some-beacon-event-issues.patch index c232d53b..e7b063cb 100644 --- a/patches/server/0106-PaperPR-Fix-some-beacon-event-issues.patch +++ b/Leaf-archived-patches/hardfork/0106-PaperPR-Fix-some-beacon-event-issues.patch @@ -13,10 +13,10 @@ Moves the deactivate event call into the onRemove method for the beacon block it The field I added feels a bit wrong but it works, it's to prevent the activation event being called immediately after loading, can't see any better way to differentiate between a newly placed beacon and a newly loaded one. -diff --git a/src/main/java/net/minecraft/world/level/block/BeaconBlock.java b/src/main/java/net/minecraft/world/level/block/BeaconBlock.java +diff --git a/net/minecraft/world/level/block/BeaconBlock.java b/net/minecraft/world/level/block/BeaconBlock.java index debe8dbf1d5f3e58774903c5fcdcea672274ea61..413d6978d3acd441c90cdba6128bd35411048645 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeaconBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeaconBlock.java +--- a/net/minecraft/world/level/block/BeaconBlock.java ++++ b/net/minecraft/world/level/block/BeaconBlock.java @@ -57,4 +57,16 @@ public class BeaconBlock extends BaseEntityBlock implements BeaconBeamBlock { protected RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; @@ -34,10 +34,10 @@ index debe8dbf1d5f3e58774903c5fcdcea672274ea61..413d6978d3acd441c90cdba6128bd354 + } + // Paper end } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +diff --git a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java index 2d190b3a6378b8cbadfa65510df1ccfbd5882ef8..f2f5ef254e21134bf85f10d32541c9fbf883042f 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 +--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java @@ -122,6 +122,8 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name return BeaconBlockEntity.VALID_EFFECTS.contains(effect) ? effect : null; } diff --git a/patches/server/0107-Dont-send-useless-entity-packets.patch b/Leaf-archived-patches/hardfork/0107-Dont-send-useless-entity-packets.patch similarity index 65% rename from patches/server/0107-Dont-send-useless-entity-packets.patch rename to Leaf-archived-patches/hardfork/0107-Dont-send-useless-entity-packets.patch index 19b42c36..e1db3d4f 100644 --- a/patches/server/0107-Dont-send-useless-entity-packets.patch +++ b/Leaf-archived-patches/hardfork/0107-Dont-send-useless-entity-packets.patch @@ -8,10 +8,10 @@ TODO: Add more reducers Original license: MIT Original project: https://github.com/PurpurMC/Purpur -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java index 709d9997f25369a9a0ac5af94cfe391604081ea1..bea671ae16a90e9cb9d2f312eed3c816da05b23c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java @@ -243,6 +243,8 @@ public class ServerEntity { flag4 = true; } @@ -49,27 +49,3 @@ index 709d9997f25369a9a0ac5af94cfe391604081ea1..bea671ae16a90e9cb9d2f312eed3c816 public void removePairing(ServerPlayer player) { this.entity.stopSeenByPlayer(player); player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()})); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java b/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1598298420dbfa6a2611826a4a499a41cfc3740a ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceUselessPackets.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ReduceUselessPackets extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".reduce-packets"; -+ } -+ -+ public static boolean reduceUselessEntityMovePackets = false; -+ -+ @Override -+ public void onLoaded() { -+ reduceUselessEntityMovePackets = config.getBoolean(getBasePath() + ".reduce-entity-move-packets", reduceUselessEntityMovePackets); -+ } -+} diff --git a/patches/server/0108-Don-t-spawn-if-lastSpawnState-is-null.patch b/Leaf-archived-patches/hardfork/0108-Don-t-spawn-if-lastSpawnState-is-null.patch similarity index 82% rename from patches/server/0108-Don-t-spawn-if-lastSpawnState-is-null.patch rename to Leaf-archived-patches/hardfork/0108-Don-t-spawn-if-lastSpawnState-is-null.patch index 0a9bd922..d954cbef 100644 --- a/patches/server/0108-Don-t-spawn-if-lastSpawnState-is-null.patch +++ b/Leaf-archived-patches/hardfork/0108-Don-t-spawn-if-lastSpawnState-is-null.patch @@ -4,10 +4,10 @@ Date: Tue, 27 Aug 2024 22:53:08 -0400 Subject: [PATCH] Don't spawn if lastSpawnState is null -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index 09e06dbb8e3f9ce65fb0f9010aeb3066b6c21671..c649a21b2631ed8a2abe1b8d2ff1a5fbf5f511ec 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java @@ -633,7 +633,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon ChunkPos chunkcoordintpair = chunk.getPos(); diff --git a/patches/server/0109-Multithreaded-Tracker.patch b/Leaf-archived-patches/hardfork/0109-Multithreaded-Tracker.patch similarity index 63% rename from patches/server/0109-Multithreaded-Tracker.patch rename to Leaf-archived-patches/hardfork/0109-Multithreaded-Tracker.patch index 3f1d29e8..95a63e18 100644 --- a/patches/server/0109-Multithreaded-Tracker.patch +++ b/Leaf-archived-patches/hardfork/0109-Multithreaded-Tracker.patch @@ -40,10 +40,10 @@ index e42677bb004201efe1702779a78cc8d0ca05e80f..6676be8304e9415099ed423d3315180c throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } // Leaves start - skip photographer -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java index 2b67936faa5fe058f4927610f01c4dc458117bf0..231f36edefffcbbf7256f73dcae922c17e74ae73 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java @@ -240,6 +240,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return; } @@ -208,10 +208,10 @@ index 2b67936faa5fe058f4927610f01c4dc458117bf0..231f36edefffcbbf7256f73dcae922c1 // Paper start - remove allocation of Vec3D here // Vec3 vec3d = player.position().subtract(this.entity.position()); double vec3d_dx = player.getX() - this.entity.getX(); -diff --git a/src/main/java/net/minecraft/server/level/ServerBossEvent.java b/src/main/java/net/minecraft/server/level/ServerBossEvent.java +diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java index 4f91107f9ae42f96c060c310596db9aa869a8dbc..f9889f593ed144ee8f1f5bd380e631c659b0c2b8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerBossEvent.java -+++ b/src/main/java/net/minecraft/server/level/ServerBossEvent.java +--- a/net/minecraft/server/level/ServerBossEvent.java ++++ b/net/minecraft/server/level/ServerBossEvent.java @@ -13,7 +13,9 @@ import net.minecraft.util.Mth; import net.minecraft.world.BossEvent; @@ -223,10 +223,10 @@ index 4f91107f9ae42f96c060c310596db9aa869a8dbc..f9889f593ed144ee8f1f5bd380e631c6 private final Set unmodifiablePlayers = Collections.unmodifiableSet(this.players); public boolean visible = true; -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java index bea671ae16a90e9cb9d2f312eed3c816da05b23c..36026f9f4cad3930cd45918012bf54498f2de973 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java @@ -119,7 +119,13 @@ public class ServerEntity { this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> { @@ -300,10 +300,10 @@ index bea671ae16a90e9cb9d2f312eed3c816da05b23c..36026f9f4cad3930cd45918012bf5449 } set.clear(); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 9a7a76599a44dfd51d5e9e9a0e892994528c7680..4a92789d77313e165ab1252cd469e34a8b7eb575 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java @@ -2561,7 +2561,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override @@ -313,10 +313,10 @@ index 9a7a76599a44dfd51d5e9e9a0e892994528c7680..4a92789d77313e165ab1252cd469e34a return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 0684a8e9e0d91c1724d1e066daa71030bba70904..dd6174a4b695bdaa2229a21c9680e757c6869755 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1833,7 +1833,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } @@ -326,10 +326,10 @@ index 0684a8e9e0d91c1724d1e066daa71030bba70904..dd6174a4b695bdaa2229a21c9680e757 // Paper start - Prevent teleporting dead entities if (player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java index 27a7852a5d3f8c8960f098646ff5587c50556aa5..f492a3d58e43c1ef9ef6652b40d894874471abd3 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -24,8 +24,11 @@ public class AttributeInstance { private final Map> modifiersByOperation = Maps.newEnumMap( AttributeModifier.Operation.class @@ -344,10 +344,10 @@ index 27a7852a5d3f8c8960f098646ff5587c50556aa5..f492a3d58e43c1ef9ef6652b40d89487 private double baseValue; private boolean dirty = true; private double cachedValue; -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 +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java index 7bc3a6f4dabc6411b6ff17e6dbbd190d57076cd1..4d060255d1446e65214f75fc5d03cabd4fb00576 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 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -19,11 +19,14 @@ import org.slf4j.Logger; public class AttributeMap { @@ -366,203 +366,3 @@ index 7bc3a6f4dabc6411b6ff17e6dbbd190d57076cd1..4d060255d1446e65214f75fc5d03cabd private final AttributeSupplier supplier; private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations private final net.minecraft.world.entity.LivingEntity entity; // Purpur -diff --git a/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1e7377c4f7c21300f4eba738d8f12e24004cb8b1 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java -@@ -0,0 +1,140 @@ -+package org.dreeam.leaf.async.tracker; -+ -+import ca.spottedleaf.moonrise.common.list.ReferenceList; -+import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; -+import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup; -+import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; -+import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity; -+import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.Entity; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+import java.util.concurrent.Executor; -+import java.util.concurrent.LinkedBlockingQueue; -+import java.util.concurrent.ThreadPoolExecutor; -+import java.util.concurrent.TimeUnit; -+ -+public class MultithreadedTracker { -+ -+ private static final Logger LOGGER = LogManager.getLogger("MultithreadedTracker"); -+ public static class MultithreadedTrackerThread extends Thread { -+ @Override -+ public void run() { -+ super.run(); -+ } -+ } -+ private static final Executor trackerExecutor = new ThreadPoolExecutor( -+ 1, -+ org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads, -+ org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive, TimeUnit.SECONDS, -+ new LinkedBlockingQueue<>(), -+ new ThreadFactoryBuilder() -+ .setThreadFactory( -+ r -> new MultithreadedTrackerThread() { -+ @Override -+ public void run() { -+ r.run(); -+ } -+ } -+ ) -+ .setNameFormat("Leaf Async Tracker Thread - %d") -+ .setPriority(Thread.NORM_PRIORITY - 2) -+ .build()); -+ -+ private MultithreadedTracker() { -+ } -+ -+ public static Executor getTrackerExecutor() { -+ return trackerExecutor; -+ } -+ -+ public static void tick(ChunkSystemServerLevel level) { -+ try { -+ if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { -+ tickAsync(level); -+ } else { -+ tickAsyncWithCompatMode(level); -+ } -+ } catch (Exception e) { -+ LOGGER.error("Error occurred while executing async task.", e); -+ } -+ } -+ -+ private static void tickAsync(ChunkSystemServerLevel level) { -+ final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); -+ final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); -+ -+ final ReferenceList trackerEntities = entityLookup.trackerEntities; -+ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); -+ -+ // Move tracking to off-main -+ trackerExecutor.execute(() -> { -+ for (final Entity entity : trackerEntitiesRaw) { -+ if (entity == null) continue; -+ -+ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); -+ -+ if (tracker == null) continue; -+ -+ ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); -+ tracker.serverEntity.sendChanges(); -+ } -+ }); -+ } -+ -+ private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) { -+ final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); -+ final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); -+ -+ final ReferenceList trackerEntities = entityLookup.trackerEntities; -+ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); -+ final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length]; -+ int index = 0; -+ -+ for (final Entity entity : trackerEntitiesRaw) { -+ if (entity == null) continue; -+ -+ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); -+ -+ if (tracker == null) continue; -+ -+ ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); -+ sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array -+ } -+ -+ // batch submit tasks -+ trackerExecutor.execute(() -> { -+ for (final Runnable sendChanges : sendChangesTasks) { -+ if (sendChanges == null) continue; -+ -+ sendChanges.run(); -+ } -+ }); -+ } -+ -+ // Original ChunkMap#newTrackerTick of Paper -+ // Just for diff usage for future update -+ private static void tickOriginal(ServerLevel level) { -+ final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup(); -+ -+ final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; -+ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); -+ for (int i = 0, len = trackerEntities.size(); i < len; ++i) { -+ final Entity entity = trackerEntitiesRaw[i]; -+ final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity(); -+ if (tracker == null) { -+ continue; -+ } -+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers); -+ if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers() -+ || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ tracker.serverEntity.sendChanges(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bf253dbdcff603a4ebce181bf75131dd8aa52721 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java -@@ -0,0 +1,48 @@ -+package org.dreeam.leaf.config.modules.async; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.LeafConfig; -+ -+public class MultithreadedTracker extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker"; -+ } -+ -+ public static boolean enabled = false; -+ public static boolean compatModeEnabled = false; -+ public static int asyncEntityTrackerMaxThreads = 0; -+ public static int asyncEntityTrackerKeepalive = 60; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Make entity tracking saving asynchronously, can improve performance significantly, -+ especially in some massive entities in small area situations.""", -+ """ -+ 异步实体跟踪, -+ 在实体数量多且密集的情况下效果明显."""); -+ -+ enabled = config().getBoolean(getBasePath() + ".enabled", enabled); -+ compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased(""" -+ Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed, -+ Compat mode fixed visible issue with player type NPCs of Citizens, -+ But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""", -+ """ -+ 是否启用兼容模式, -+ 如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项.""")); -+ asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads); -+ asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive); -+ -+ if (asyncEntityTrackerMaxThreads < 0) -+ asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1); -+ else if (asyncEntityTrackerMaxThreads == 0) -+ asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); -+ -+ if (!enabled) -+ asyncEntityTrackerMaxThreads = 0; -+ else -+ LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads); -+ } -+} diff --git a/Leaf-archived-patches/hardfork/0110-Nitori-Async-playerdata-Save.patch b/Leaf-archived-patches/hardfork/0110-Nitori-Async-playerdata-Save.patch new file mode 100644 index 00000000..de3d0c9e --- /dev/null +++ b/Leaf-archived-patches/hardfork/0110-Nitori-Async-playerdata-Save.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Fri, 23 Aug 2024 22:04:20 -0400 +Subject: [PATCH] Nitori: Async playerdata Save + +Original license: GPL v3 +Original project: https://github.com/Gensokyo-Reimagined/Nitori + +diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java +index cdca5ae69991cc068bfbc0686b5defb3604a5440..5ab705731209fd4d4c20cd342ee7bf1bf26f3b0c 100644 +--- a/net/minecraft/world/level/storage/LevelStorageSource.java ++++ b/net/minecraft/world/level/storage/LevelStorageSource.java +@@ -605,7 +605,11 @@ public class LevelStorageSource { + CompoundTag nbttagcompound2 = new CompoundTag(); + + nbttagcompound2.put("Data", nbttagcompound1); +- this.saveLevelData(nbttagcompound2); ++ ++ // Leaf start - Nitori - Async playerdata save ++ Runnable runnable = () -> this.saveLevelData(nbttagcompound2); ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); ++ // Leaf end - Nitori - Async playerdata save + } + + private void saveLevelData(CompoundTag nbt) { +@@ -702,7 +706,11 @@ public class LevelStorageSource { + CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(this.levelDirectory.dataFile()); + + nbtProcessor.accept(nbttagcompound.getCompound("Data")); +- this.saveLevelData(nbttagcompound); ++ ++ // Leaf start - Nitori - Async playerdata save ++ Runnable runnable = () -> this.saveLevelData(nbttagcompound); ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); ++ // Leaf end - Nitori - Async playerdata save + } + + public long makeWorldBackup() throws IOException { +diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java +index b148cf247acdd36f856d0495cde4cc5ad32b5a2f..e825d9e573a38531f5a3b3f9cdccc24570953015 100644 +--- a/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -36,6 +36,13 @@ public class PlayerDataStorage { + + public void save(Player player) { + if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot ++ ++ // Leaf start - Nitori - Async playerdata save ++ Runnable runnable = () -> save0(player); ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); ++ } ++ private void save0(Player player) { ++ // Leaf end - Nitori - Async playerdata save + try { + CompoundTag nbttagcompound = player.saveWithoutId(new CompoundTag()); + Path path = this.playerDir.toPath(); diff --git a/Leaf-archived-patches/hardfork/0111-Change-max-stack-count.patch b/Leaf-archived-patches/hardfork/0111-Change-max-stack-count.patch new file mode 100644 index 00000000..21c3511b --- /dev/null +++ b/Leaf-archived-patches/hardfork/0111-Change-max-stack-count.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: FallingKey +Date: Sun, 11 Aug 2024 14:20:37 +0800 +Subject: [PATCH] Change max stack count + +TODO - Dreeam: +- Check shulkerbox unpack whether correct +- stacked dropped shulkerbox unpack issue, need to base on box's item count +- dropped itemstack -> hopper behavior, need to transfer into hopper with correct count +- ...still testing lol + +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index 8eed7d70d5716f6d58c46b31a526b5de2a891f16..baf50c2f50325c64951dc37b1d57076b4a9c5b99 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -351,7 +351,13 @@ public class ItemEntity extends Entity implements TraceableEntity { + private boolean isMergable() { + ItemStack itemstack = this.getItem(); + +- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - Alternative item-despawn-rate ++ // Leaf start - Change max stack count ++ if (org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount < 1) { ++ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - Alternative item-despawn-rate ++ } else { ++ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount; // Paper - Alternative item-despawn-rate ++ } ++ // Leaf end - Change max stack count + } + + private void tryToMerge(ItemEntity other) { +@@ -369,11 +375,24 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + + public static boolean areMergable(ItemStack stack1, ItemStack stack2) { +- return stack2.getCount() + stack1.getCount() > stack2.getMaxStackSize() ? false : ItemStack.isSameItemSameComponents(stack1, stack2); ++ // Leaf start - Change max stack count ++ if (org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount == 0) { ++ return stack2.getCount() + stack1.getCount() > stack2.getMaxStackSize() ? false : ItemStack.isSameItemSameComponents(stack1, stack2); ++ } else { ++ return stack2.getCount() + stack1.getCount() > org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount ? false : ItemStack.isSameItemSameComponents(stack1, stack2); ++ } ++ // Leaf end - Change max stack count + } + + public static ItemStack merge(ItemStack stack1, ItemStack stack2, int maxCount) { +- int j = Math.min(Math.min(stack1.getMaxStackSize(), maxCount) - stack1.getCount(), stack2.getCount()); ++ // Leaf start - Change max stack count ++ int j; ++ if (org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount == 0) { ++ j = Math.min(Math.min(stack1.getMaxStackSize(), maxCount) - stack1.getCount(), stack2.getCount()); ++ } else { ++ j = Math.min(Math.min(org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount, maxCount) - stack1.getCount(), stack2.getCount()); ++ } ++ // Leaf end - Change max stack count + ItemStack itemstack2 = stack1.copyWithCount(stack1.getCount() + j); + + stack2.shrink(j); +@@ -381,7 +400,14 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + + private static void merge(ItemEntity targetEntity, ItemStack stack1, ItemStack stack2) { +- ItemStack itemstack2 = ItemEntity.merge(stack1, stack2, 64); ++ // Leaf start - Change max stack count ++ ItemStack itemstack2; ++ if (org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount < 1) { ++ itemstack2 = ItemEntity.merge(stack1, stack2, 64); ++ } else { ++ itemstack2 = ItemEntity.merge(stack1, stack2, org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxItemStackCount); ++ } ++ // Leaf end - Change max stack count + + targetEntity.setItem(itemstack2); + } +diff --git a/net/minecraft/world/item/ItemUtils.java b/net/minecraft/world/item/ItemUtils.java +index 0c4074ed8b4fd9d6fcb838e8843d66f6f286ed5d..4728dd8bcbfc514eb5beeee716d849e578d5a53e 100644 +--- a/net/minecraft/world/item/ItemUtils.java ++++ b/net/minecraft/world/item/ItemUtils.java +@@ -42,14 +42,32 @@ public class ItemUtils { + Level level = itemEntity.level(); + if (!level.isClientSide) { + // Paper start - call EntityDropItemEvent +- contents.forEach(stack -> { +- ItemEntity droppedItem = new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack); +- org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(itemEntity.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity()); +- if (event.callEvent()) { +- level.addFreshEntity(droppedItem); ++ // Leaf start - Change max stack count ++ if (org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxContainerDestroyCount == 0) { ++ contents.forEach(stack -> { ++ addFreshEntityWithCallEvent(itemEntity, level, stack); ++ }); ++ } else { ++ java.util.Iterator iterator = contents.iterator(); ++ for (int count = Math.min(itemEntity.getItem().getCount(), org.dreeam.leaf.config.modules.gameplay.MaxItemsStackCount.maxContainerDestroyCount); count > 0; count--) { ++ if (!iterator.hasNext()) break; ++ ++ ItemStack stack = iterator.next(); ++ addFreshEntityWithCallEvent(itemEntity, level, stack); + } +- }); ++ } ++ // Leaf end - Change max stack count + // Paper end - call EntityDropItemEvent + } + } ++ ++ // Leaf start - Change max stack count ++ private static void addFreshEntityWithCallEvent(ItemEntity itemEntity, Level level, ItemStack stack) { ++ ItemEntity droppedItem = new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack); ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(itemEntity.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity()); ++ if (event.callEvent()) { ++ level.addFreshEntity(droppedItem); ++ } ++ } ++ // Leaf end - Change max stack count + } diff --git a/patches/server/0112-Optimize-nearby-alive-players-for-spawning.patch b/Leaf-archived-patches/hardfork/0112-Optimize-nearby-alive-players-for-spawning.patch similarity index 87% rename from patches/server/0112-Optimize-nearby-alive-players-for-spawning.patch rename to Leaf-archived-patches/hardfork/0112-Optimize-nearby-alive-players-for-spawning.patch index 94e51197..06e59bfa 100644 --- a/patches/server/0112-Optimize-nearby-alive-players-for-spawning.patch +++ b/Leaf-archived-patches/hardfork/0112-Optimize-nearby-alive-players-for-spawning.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimize nearby alive players for spawning Use SpottedLeaf's nearby players system to avoid iterating over all online players and reduce the cost on predicate test -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +diff --git a/net/minecraft/world/entity/EntitySelector.java b/net/minecraft/world/entity/EntitySelector.java index 59c4d3753c7084e92402608b7fb3c4adbc6c2f65..68b59afe54fa1dcc1b24e90fb0cdcff83d898232 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +--- a/net/minecraft/world/entity/EntitySelector.java ++++ b/net/minecraft/world/entity/EntitySelector.java @@ -44,7 +44,7 @@ public final class EntitySelector { private EntitySelector() {} // Paper start - Affects Spawning API @@ -19,10 +19,10 @@ index 59c4d3753c7084e92402608b7fb3c4adbc6c2f65..68b59afe54fa1dcc1b24e90fb0cdcff8 }; // Paper end - Affects Spawning API -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +diff --git a/net/minecraft/world/entity/monster/Zombie.java b/net/minecraft/world/entity/monster/Zombie.java index 6efb548b6e1b466628eb70bc45ef98d09604c9df..d053019e3a1fb2d15ad231e31f761d136dca8417 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +--- a/net/minecraft/world/entity/monster/Zombie.java ++++ b/net/minecraft/world/entity/monster/Zombie.java @@ -413,7 +413,7 @@ public class Zombie extends Monster { if (SpawnPlacements.isSpawnPositionOk(entitytypes, world, blockposition) && SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.REINFORCEMENT, blockposition, world.random)) { @@ -32,10 +32,10 @@ index 6efb548b6e1b466628eb70bc45ef98d09604c9df..d053019e3a1fb2d15ad231e31f761d13 entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, (SpawnGroupData) null); world.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java index b23397ae135f31abb7ac6bafd9064d7ef5e94218..37c98981c71b73daa078c49319e124c20628fcc8 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +--- a/net/minecraft/world/level/BaseSpawner.java ++++ b/net/minecraft/world/level/BaseSpawner.java @@ -60,7 +60,7 @@ public abstract class BaseSpawner { public boolean isNearPlayer(Level world, BlockPos pos) { @@ -45,10 +45,10 @@ index b23397ae135f31abb7ac6bafd9064d7ef5e94218..37c98981c71b73daa078c49319e124c2 } public void clientTick(Level world, BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java index 6b2cda6d578a0983b2401ea20629275431018433..47f80547a4f2285dc097c6f73954419848cfe895 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +--- a/net/minecraft/world/level/EntityGetter.java ++++ b/net/minecraft/world/level/EntityGetter.java @@ -180,6 +180,89 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst } return false; diff --git a/patches/server/0113-Cache-blockstate-cache.patch b/Leaf-archived-patches/hardfork/0113-Cache-blockstate-cache.patch similarity index 88% rename from patches/server/0113-Cache-blockstate-cache.patch rename to Leaf-archived-patches/hardfork/0113-Cache-blockstate-cache.patch index 84d7d29d..5a7474c2 100644 --- a/patches/server/0113-Cache-blockstate-cache.patch +++ b/Leaf-archived-patches/hardfork/0113-Cache-blockstate-cache.patch @@ -4,10 +4,10 @@ Date: Tue, 22 Oct 2024 17:07:36 +0800 Subject: [PATCH] Cache blockstate cache -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 +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java index 9b94d8bf3415734776c81297d5d34eea46ad7e78..65d8ac795282117ba88003e7a703ee649a359473 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 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -1438,6 +1438,10 @@ public abstract class BlockBehaviour implements FeatureElement { private static final Direction[] DIRECTIONS = Direction.values(); diff --git a/patches/server/0114-Asynchronous-locator.patch b/Leaf-archived-patches/hardfork/0114-Asynchronous-locator.patch similarity index 55% rename from patches/server/0114-Asynchronous-locator.patch rename to Leaf-archived-patches/hardfork/0114-Asynchronous-locator.patch index 26cf3f78..fc0c93cd 100644 --- a/patches/server/0114-Asynchronous-locator.patch +++ b/Leaf-archived-patches/hardfork/0114-Asynchronous-locator.patch @@ -40,10 +40,10 @@ index 6676be8304e9415099ed423d3315180cafebd928..30b56382e9574004e344c1c8289d7dcb throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } // Leaves start - skip photographer -diff --git a/src/main/java/net/minecraft/server/commands/LocateCommand.java b/src/main/java/net/minecraft/server/commands/LocateCommand.java +diff --git a/net/minecraft/server/commands/LocateCommand.java b/net/minecraft/server/commands/LocateCommand.java index dcdde4cd7f15d34eabba4b3802971db20e6ae9d2..e33f31ae83edc4e04ad1f3fa3216b90219d902dc 100644 ---- a/src/main/java/net/minecraft/server/commands/LocateCommand.java -+++ b/src/main/java/net/minecraft/server/commands/LocateCommand.java +--- a/net/minecraft/server/commands/LocateCommand.java ++++ b/net/minecraft/server/commands/LocateCommand.java @@ -105,6 +105,37 @@ public class LocateCommand { BlockPos blockPos = BlockPos.containing(source.getPosition()); ServerLevel serverLevel = source.getLevel(); @@ -82,10 +82,10 @@ index dcdde4cd7f15d34eabba4b3802971db20e6ae9d2..e33f31ae83edc4e04ad1f3fa3216b902 Pair> pair = serverLevel.getChunkSource() .getGenerator() .findNearestMapStructure(serverLevel, holderSet, blockPos, 100, false); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +diff --git a/net/minecraft/world/entity/animal/Dolphin.java b/net/minecraft/world/entity/animal/Dolphin.java index c1842894f96a567707992d8ff938dbf689dd0df6..0792629152937b5107dbf444ce7f67e747f30c10 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +--- a/net/minecraft/world/entity/animal/Dolphin.java ++++ b/net/minecraft/world/entity/animal/Dolphin.java @@ -494,6 +494,8 @@ public class Dolphin extends AgeableWaterCreature { private final Dolphin dolphin; @@ -154,10 +154,10 @@ index c1842894f96a567707992d8ff938dbf689dd0df6..0792629152937b5107dbf444ce7f67e7 Level world = this.dolphin.level(); if (this.dolphin.closeToNextPos() || this.dolphin.getNavigation().isDone()) { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +diff --git a/net/minecraft/world/entity/projectile/EyeOfEnder.java b/net/minecraft/world/entity/projectile/EyeOfEnder.java index fd1f5de7dc151dfd187d23e022b2c5435ed8accc..35037b0d7d243d614aa6945330ae7186a6f20af5 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +--- a/net/minecraft/world/entity/projectile/EyeOfEnder.java ++++ b/net/minecraft/world/entity/projectile/EyeOfEnder.java @@ -30,6 +30,7 @@ public class EyeOfEnder extends Entity implements ItemSupplier { public double tz; public int life; @@ -178,10 +178,10 @@ index fd1f5de7dc151dfd187d23e022b2c5435ed8accc..35037b0d7d243d614aa6945330ae7186 Vec3 vec3d = this.getDeltaMovement(); double d0 = this.getX() + vec3d.x; double d1 = this.getY() + vec3d.y; -diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +diff --git a/net/minecraft/world/item/EnderEyeItem.java b/net/minecraft/world/item/EnderEyeItem.java index c71a426c47e0ebc57ecb8c9c1d171737a084ccab..0edd6efc7a6dc7f62f07691fdd73fbb212c82173 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +--- a/net/minecraft/world/item/EnderEyeItem.java ++++ b/net/minecraft/world/item/EnderEyeItem.java @@ -113,7 +113,14 @@ public class EnderEyeItem extends Item { user.startUsingItem(hand); if (world instanceof ServerLevel) { @@ -248,217 +248,3 @@ index c71a426c47e0ebc57ecb8c9c1d171737a084ccab..0edd6efc7a6dc7f62f07691fdd73fbb2 } return InteractionResult.SUCCESS_SERVER; -diff --git a/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java b/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fcede5af1f1352a8c8c089993040838d1e7c3042 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java -@@ -0,0 +1,164 @@ -+package org.dreeam.leaf.async.locate; -+ -+import ca.spottedleaf.moonrise.common.util.TickThread; -+import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import com.mojang.datafixers.util.Pair; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Holder; -+import net.minecraft.core.HolderSet; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.tags.TagKey; -+import net.minecraft.world.level.chunk.ChunkGenerator; -+import net.minecraft.world.level.levelgen.structure.Structure; -+ -+import java.util.concurrent.*; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; -+ -+// Original project: https://github.com/thebrightspark/AsyncLocator -+public class AsyncLocator { -+ private static final ExecutorService LOCATING_EXECUTOR_SERVICE; -+ -+ private AsyncLocator() {} -+ -+ public static class AsyncLocatorThread extends TickThread { -+ private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0); -+ public AsyncLocatorThread(Runnable run, String name) { -+ super(run, name, THREAD_COUNTER.incrementAndGet()); -+ } -+ -+ @Override -+ public void run() { -+ super.run(); -+ } -+ } -+ -+ static { -+ int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads; -+ LOCATING_EXECUTOR_SERVICE = new ThreadPoolExecutor( -+ 1, -+ threads, -+ org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive, -+ TimeUnit.SECONDS, -+ new LinkedBlockingQueue<>(), -+ new ThreadFactoryBuilder() -+ .setThreadFactory( -+ r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") { -+ @Override -+ public void run() { -+ r.run(); -+ } -+ } -+ ) -+ .setNameFormat("Leaf Async Locator Thread - %d") -+ .setPriority(Thread.NORM_PRIORITY - 2) -+ .build() -+ ); -+ } -+ -+ public static void shutdownExecutorService() { -+ if (LOCATING_EXECUTOR_SERVICE != null) { -+ LOCATING_EXECUTOR_SERVICE.shutdown(); -+ } -+ } -+ -+ /** -+ * Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} -+ * and returns a {@link LocateTask} with the futures for it. -+ */ -+ public static LocateTask locate( -+ ServerLevel level, -+ TagKey structureTag, -+ BlockPos pos, -+ int searchRadius, -+ boolean skipKnownStructures -+ ) { -+ CompletableFuture completableFuture = new CompletableFuture<>(); -+ Future future = LOCATING_EXECUTOR_SERVICE.submit( -+ () -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures) -+ ); -+ return new LocateTask<>(level.getServer(), completableFuture, future); -+ } -+ -+ /** -+ * Queues a task to locate a feature using -+ * {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a -+ * {@link LocateTask} with the futures for it. -+ */ -+ public static LocateTask>> locate( -+ ServerLevel level, -+ HolderSet structureSet, -+ BlockPos pos, -+ int searchRadius, -+ boolean skipKnownStructures -+ ) { -+ CompletableFuture>> completableFuture = new CompletableFuture<>(); -+ Future future = LOCATING_EXECUTOR_SERVICE.submit( -+ () -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures) -+ ); -+ return new LocateTask<>(level.getServer(), completableFuture, future); -+ } -+ -+ private static void doLocateLevel( -+ CompletableFuture completableFuture, -+ ServerLevel level, -+ TagKey structureTag, -+ BlockPos pos, -+ int searchRadius, -+ boolean skipExistingChunks -+ ) { -+ BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks); -+ completableFuture.complete(foundPos); -+ } -+ -+ private static void doLocateChunkGenerator( -+ CompletableFuture>> completableFuture, -+ ServerLevel level, -+ HolderSet structureSet, -+ BlockPos pos, -+ int searchRadius, -+ boolean skipExistingChunks -+ ) { -+ Pair> foundPair = level.getChunkSource().getGenerator() -+ .findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks); -+ completableFuture.complete(foundPair); -+ } -+ -+ /** -+ * Holder of the futures for an async locate task as well as providing some helper functions. -+ * The completableFuture will be completed once the call to -+ * {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the -+ * result of it. -+ * The taskFuture is the future for the {@link Runnable} itself in the executor service. -+ */ -+ public record LocateTask(MinecraftServer server, CompletableFuture completableFuture, Future taskFuture) { -+ /** -+ * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action. -+ * Bear in mind that the action will be executed from the task's thread. If you intend to change any game data, -+ * it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed -+ * on the main server thread instead. -+ */ -+ public LocateTask then(Consumer action) { -+ completableFuture.thenAccept(action); -+ return this; -+ } -+ -+ /** -+ * Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server -+ * thread. -+ */ -+ public LocateTask thenOnServerThread(Consumer action) { -+ completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos))); -+ return this; -+ } -+ -+ /** -+ * Helper function that cancels both completableFuture and taskFuture. -+ */ -+ public void cancel() { -+ taskFuture.cancel(true); -+ completableFuture.cancel(false); -+ } -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d741e4d6e7a39cf3da0f40816daa0405122a90a ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncLocator.java -@@ -0,0 +1,37 @@ -+package org.dreeam.leaf.config.modules.async; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.LeafConfig; -+ -+public class AsyncLocator extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator"; -+ } -+ -+ public static boolean enabled = false; -+ public static int asyncLocatorThreads = 0; -+ public static int asyncLocatorKeepalive = 60; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Whether or not asynchronous locator should be enabled. -+ This offloads structure locating to other threads. -+ Only for locate command, dolphin treasure finding and eye of ender currently.""", -+ """ -+ 是否启用异步结构搜索. -+ 目前可用于 /locate 指令, 海豚寻宝和末影之眼."""); -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled); -+ asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads); -+ asyncLocatorKeepalive = config.getInt(getBasePath() + ".keepalive", asyncLocatorKeepalive); -+ -+ if (asyncLocatorThreads <= 0) -+ asyncLocatorThreads = 1; -+ if (!enabled) -+ asyncLocatorThreads = 0; -+ else -+ LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads); -+ } -+} diff --git a/patches/server/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch b/Leaf-archived-patches/hardfork/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch similarity index 93% rename from patches/server/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch rename to Leaf-archived-patches/hardfork/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch index 4041daea..9da29aec 100644 --- a/patches/server/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch +++ b/Leaf-archived-patches/hardfork/0115-Smart-sort-entities-in-NearestLivingEntitySensor.patch @@ -12,10 +12,10 @@ When entity count reached the threshold, Bucket Sort will be used. This offers a 10~15% performance improvement in average. In best situation, this can give an up to 50% improvement. -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +diff --git a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java index 23494aebfa51e7181fb06d123dad429e68ebf922..a5e85005ba9a1d85084c8e54124df9e4227e5273 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +--- a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java @@ -13,17 +13,78 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; import net.minecraft.world.phys.AABB; diff --git a/Leaf-archived-patches/hardfork/0116-Further-reduce-memory-footprint-of-CompoundTag.patch b/Leaf-archived-patches/hardfork/0116-Further-reduce-memory-footprint-of-CompoundTag.patch new file mode 100644 index 00000000..7a9d5f65 --- /dev/null +++ b/Leaf-archived-patches/hardfork/0116-Further-reduce-memory-footprint-of-CompoundTag.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Sat, 26 Oct 2024 22:38:30 +0800 +Subject: [PATCH] Further reduce memory footprint of CompoundTag + +Original license: GPLv3 +Original project: https://github.com/embeddedt/ModernFix + +diff --git a/net/minecraft/nbt/CompoundTag.java b/net/minecraft/nbt/CompoundTag.java +index ea48637234fdb1e5f54342590def30e11b6a5df0..a7a5e1c70d1e4f7df6533d0531a200fe50b38b7a 100644 +--- a/net/minecraft/nbt/CompoundTag.java ++++ b/net/minecraft/nbt/CompoundTag.java +@@ -49,7 +49,7 @@ public class CompoundTag implements Tag { + + private static CompoundTag loadCompound(DataInput input, NbtAccounter tracker) throws IOException { + tracker.accountBytes(48L); +- it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag ++ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new org.dreeam.leaf.util.map.StringCanonizingOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag // Leaf - Further reduce memory footprint of CompoundTag + + byte b; + while ((b = input.readByte()) != 0) { +@@ -166,7 +166,7 @@ public class CompoundTag implements Tag { + } + + public CompoundTag() { +- this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - Reduce memory footprint of CompoundTag ++ this(new org.dreeam.leaf.util.map.StringCanonizingOpenHashMap<>(8, 0.8f)); // Paper - Reduce memory footprint of CompoundTag // Leaf - Further reduce memory footprint of CompoundTag + } + + @Override +@@ -497,6 +497,11 @@ public class CompoundTag implements Tag { + + @Override + public CompoundTag copy() { ++ // Leaf start - Further reduce memory footprint of CompoundTag ++ if (this.tags instanceof org.dreeam.leaf.util.map.StringCanonizingOpenHashMap stringCanonizingTags) { ++ return new CompoundTag(org.dreeam.leaf.util.map.StringCanonizingOpenHashMap.deepCopy(stringCanonizingTags, Tag::copy)); ++ } ++ // Leaf end - Further reduce memory footprint of CompoundTag + // Paper start - Reduce memory footprint of CompoundTag + 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(); diff --git a/patches/server/0117-Optimize-Entity-distanceToSqr.patch b/Leaf-archived-patches/hardfork/0117-Optimize-Entity-distanceToSqr.patch similarity index 94% rename from patches/server/0117-Optimize-Entity-distanceToSqr.patch rename to Leaf-archived-patches/hardfork/0117-Optimize-Entity-distanceToSqr.patch index c5ffb49a..21ee190f 100644 --- a/patches/server/0117-Optimize-Entity-distanceToSqr.patch +++ b/Leaf-archived-patches/hardfork/0117-Optimize-Entity-distanceToSqr.patch @@ -7,10 +7,10 @@ This patch optimizes Entity#distanceToSqr call by using Math#fma which is around avoids multiple casting in Entity#distanceTo, using Math#sqrt directly instead of Mojang's Mth#sqrt. Additionally, this patch makes these methods more able to be inlined by the JIT compiler. -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java index d203ab8cd02432e05d6e3b8766d5a733e570173d..c1a7799dc4c44e5988b15f878633dafb20e257eb 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java @@ -2310,33 +2310,41 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return new Vec3(this.xOld, this.yOld, this.zOld); } diff --git a/Leaf-archived-patches/hardfork/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch b/Leaf-archived-patches/hardfork/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch new file mode 100644 index 00000000..6394462a --- /dev/null +++ b/Leaf-archived-patches/hardfork/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 7 Nov 2017 00:01:04 -0500 +Subject: [PATCH] EMC: Don't use snapshots for TileEntity::getOwner + +Original license: MIT +Original project: https://github.com/starlis/empirecraft + +Also see Leaf's EMC-Default-don-t-use-blockstate-snapshots.patch + +diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java +index eaa6ece956f90632831f0558924eaf18680a252b..8a20b0ef9ea684a4a5e79b42f11834e3fe78b4fd 100644 +--- a/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -399,7 +399,7 @@ public abstract class BlockEntity { + // CraftBukkit start - add method + public InventoryHolder getOwner() { + // Paper start +- return getOwner(true); ++ return getOwner(org.dreeam.leaf.config.modules.opt.TileEntitySnapshotCreation.enabled); // Leaf - EMC - don't use snapshots + } + public InventoryHolder getOwner(boolean useSnapshot) { + // Paper end diff --git a/Leaf-archived-patches/hardfork/0119-EMC-Default-don-t-use-blockstate-snapshots.patch b/Leaf-archived-patches/hardfork/0119-EMC-Default-don-t-use-blockstate-snapshots.patch new file mode 100644 index 00000000..f8caa0df --- /dev/null +++ b/Leaf-archived-patches/hardfork/0119-EMC-Default-don-t-use-blockstate-snapshots.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 Jun 2018 22:13:44 -0400 +Subject: [PATCH] EMC: Default don't use blockstate snapshots + +Original license: MIT +Original project: https://github.com/starlis/empirecraft + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 5cb69d0b822e11a99a96aef4f59986d083b079f4..e9d43d9c4ad7cc1e12880e671f42e32dda85f17b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -331,7 +331,7 @@ public class CraftBlock implements Block { + + @Override + public BlockState getState() { +- return CraftBlockStates.getBlockState(this); ++ return CraftBlockStates.getBlockState(this, org.dreeam.leaf.config.modules.opt.TileEntitySnapshotCreation.enabled); // Leaf - EMC - default to not use snapshots + } + + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..99f9335e6e36bb97710b30135648c9dbf72d833b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -238,7 +238,7 @@ public final class CraftBlockStates { + + public static BlockState getBlockState(Block block) { + // Paper start +- return CraftBlockStates.getBlockState(block, true); ++ return CraftBlockStates.getBlockState(block, org.dreeam.leaf.config.modules.opt.TileEntitySnapshotCreation.enabled); // Leaf - default to not use snapshots + } + public static BlockState getBlockState(Block block, boolean useSnapshot) { + // Paper end diff --git a/patches/server/0120-Cache-tile-entity-position.patch b/Leaf-archived-patches/hardfork/0120-Cache-tile-entity-position.patch similarity index 89% rename from patches/server/0120-Cache-tile-entity-position.patch rename to Leaf-archived-patches/hardfork/0120-Cache-tile-entity-position.patch index 8d2186f9..63f9171e 100644 --- a/patches/server/0120-Cache-tile-entity-position.patch +++ b/Leaf-archived-patches/hardfork/0120-Cache-tile-entity-position.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Cache tile entity position Check if there is a way to cache isRemoved without problem -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java index b8246d7255bffc7e12a67772df2ceac1925b2a05..2ac51b5ed6fe50746f4f64f94e289f5ad75fd715 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -1038,13 +1038,16 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p private class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { diff --git a/Leaf-archived-patches/hardfork/0121-TT20-Lag-compensation.patch b/Leaf-archived-patches/hardfork/0121-TT20-Lag-compensation.patch new file mode 100644 index 00000000..38e2ca3f --- /dev/null +++ b/Leaf-archived-patches/hardfork/0121-TT20-Lag-compensation.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Mon, 1 Nov 2077 00:00:00 +0800 +Subject: [PATCH] TT20 Lag compensation + +Original license: AGPL-3.0 +Original project: https://github.com/snackbag/TT20 + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 244db7e0ae0eb785deb94558eff74714d979d3de..59d5b758471fc00b09ecf84dc1757f543866d480 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1668,6 +1668,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 7 Nov 2024 19:45:31 +0100 +Subject: [PATCH] C2ME: Reduce Allocations + +This patch is based on the following mixin: +"com/ishland/c2me/opts/allocs/mixin/object_pooling_caching/MixinOreFeature.java" +By: ishland +As part of: C2ME (https://github.com/RelativityMC/C2ME-fabric) +Licensed under: MIT (https://opensource.org/licenses/MIT) + +diff --git a/net/minecraft/world/level/levelgen/feature/OreFeature.java b/net/minecraft/world/level/levelgen/feature/OreFeature.java +index 506b2afd099c9b7e9ac3f6f2fcea8e523fae396b..df119e67ffdd73a7c2c93dfb35985d0c850f9e64 100644 +--- a/net/minecraft/world/level/levelgen/feature/OreFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/OreFeature.java +@@ -69,7 +69,7 @@ public class OreFeature extends Feature { + int verticalSize + ) { + int i = 0; +- BitSet bitSet = new BitSet(horizontalSize * verticalSize * horizontalSize); ++ BitSet bitSet = org.dreeam.leaf.util.cache.CachedOrNewBitsGetter.getCachedOrNewBitSet(horizontalSize * verticalSize * horizontalSize); // Leaf - C2ME - Reduce Allocations + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + int j = config.size; + double[] ds = new double[j * 4]; diff --git a/patches/server/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch b/Leaf-archived-patches/hardfork/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch similarity index 85% rename from patches/server/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch rename to Leaf-archived-patches/hardfork/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch index 814362b7..22a216a0 100644 --- a/patches/server/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch +++ b/Leaf-archived-patches/hardfork/0123-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch @@ -11,10 +11,10 @@ By: 2No2Name <2No2Name@web.de> As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java index 004cecfe99d279a51c21d610833bbea62c8ff25f..2b8cc1cfeda50721c063429a7d31623dc93089ea 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java @@ -2866,6 +2866,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } diff --git a/patches/server/0124-Lithium-fast-util.patch b/Leaf-archived-patches/hardfork/0124-Lithium-fast-util.patch similarity index 87% rename from patches/server/0124-Lithium-fast-util.patch rename to Leaf-archived-patches/hardfork/0124-Lithium-fast-util.patch index 53f4719a..92ea8cac 100644 --- a/patches/server/0124-Lithium-fast-util.patch +++ b/Leaf-archived-patches/hardfork/0124-Lithium-fast-util.patch @@ -10,10 +10,10 @@ By: 2No2Name <2No2Name@web.de> As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) -diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +diff --git a/net/minecraft/core/Direction.java b/net/minecraft/core/Direction.java index 6548302d4983bf48cc6bc2b7f4833dc76b59fa5e..dde522cfe64674d9cfc8d26601cad61816a6eaf5 100644 ---- a/src/main/java/net/minecraft/core/Direction.java -+++ b/src/main/java/net/minecraft/core/Direction.java +--- a/net/minecraft/core/Direction.java ++++ b/net/minecraft/core/Direction.java @@ -217,7 +217,7 @@ public enum Direction implements StringRepresentable, ca.spottedleaf.moonrise.pa } @@ -32,10 +32,10 @@ index 6548302d4983bf48cc6bc2b7f4833dc76b59fa5e..dde522cfe64674d9cfc8d26601cad618 } public static Direction getApproximateNearest(double x, double y, double z) { -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java index e74866e5195a5eeae7666ad7be750edac5947094..1958656ee368513c96de6635dfe7c969407400ec 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java +--- a/net/minecraft/world/phys/AABB.java ++++ b/net/minecraft/world/phys/AABB.java @@ -18,6 +18,15 @@ public class AABB { public final double maxY; public final double maxZ; diff --git a/Leaf-archived-patches/hardfork/0125-Lithium-CompactSineLUT.patch b/Leaf-archived-patches/hardfork/0125-Lithium-CompactSineLUT.patch new file mode 100644 index 00000000..2eb93cc7 --- /dev/null +++ b/Leaf-archived-patches/hardfork/0125-Lithium-CompactSineLUT.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 7 Nov 2024 23:51:51 +0100 +Subject: [PATCH] Lithium: CompactSineLUT + +This patch is based on the following mixin: +"net/caffeinemc/mods/lithium/mixin/math/sine_lut/MthMixin.java" +By: 2No2Name <2No2Name@web.de> +As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/net/minecraft/util/Mth.java b/net/minecraft/util/Mth.java +index 34bfbbabe3dfbf033f4a4e22a049323213fb23f3..c79bf9ea9456ac01533e8aa0326eb2f231626a49 100644 +--- a/net/minecraft/util/Mth.java ++++ b/net/minecraft/util/Mth.java +@@ -29,7 +29,7 @@ public class Mth { + public static final Vector3f Y_AXIS = new Vector3f(0.0F, 1.0F, 0.0F); + public static final Vector3f X_AXIS = new Vector3f(1.0F, 0.0F, 0.0F); + public static final Vector3f Z_AXIS = new Vector3f(0.0F, 0.0F, 1.0F); +- private static final float[] SIN = Util.make(new float[65536], sineTable -> { ++ public static final float[] SIN = Util.make(new float[65536], sineTable -> { // Leaf - Lithium - private -> public + for (int ix = 0; ix < sineTable.length; ix++) { + sineTable[ix] = (float)Math.sin((double)ix * Math.PI * 2.0 / 65536.0); + } +@@ -46,11 +46,11 @@ public class Mth { + private static final double[] COS_TAB = new double[257]; + + public static float sin(float value) { +- return SIN[(int)(value * 10430.378F) & 65535]; ++ return org.dreeam.leaf.util.math.CompactSineLUT.sin(value); // Leaf - Lithium - CompactSineLUT + } + + public static float cos(float value) { +- return SIN[(int)(value * 10430.378F + 16384.0F) & 65535]; ++ return org.dreeam.leaf.util.math.CompactSineLUT.cos(value); // Leaf - Lithium - CompactSineLUT + } + + public static float sqrt(float value) { diff --git a/Leaf-archived-patches/hardfork/0126-Lithium-IterateOutwardsCache.patch b/Leaf-archived-patches/hardfork/0126-Lithium-IterateOutwardsCache.patch new file mode 100644 index 00000000..a979d204 --- /dev/null +++ b/Leaf-archived-patches/hardfork/0126-Lithium-IterateOutwardsCache.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 8 Nov 2024 00:06:34 +0100 +Subject: [PATCH] Lithium: IterateOutwardsCache + +By: 2No2Name <2No2Name@web.de> +As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/net/minecraft/core/BlockPos.java b/net/minecraft/core/BlockPos.java +index 21ea63da99c5b3e2e1ab9cc1049c903bba6cf288..350a5d47cca11c0e49437a4b05029ba5c29b7ee5 100644 +--- a/net/minecraft/core/BlockPos.java ++++ b/net/minecraft/core/BlockPos.java +@@ -344,7 +344,19 @@ public class BlockPos extends Vec3i { + }; + } + ++ // JettPack start - lithium: cached iterate outwards ++ private static final org.dreeam.leaf.util.cache.IterateOutwardsCache ITERATE_OUTWARDS_CACHE = new org.dreeam.leaf.util.cache.IterateOutwardsCache(50); ++ private static final it.unimi.dsi.fastutil.longs.LongList HOGLIN_PIGLIN_CACHE = ITERATE_OUTWARDS_CACHE.getOrCompute(8, 4, 8); ++ // JettPack end ++ ++ + public static Iterable withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) { ++ // JettPack start - lithium: cached iterate outwards ++ if (center != org.dreeam.leaf.util.cache.IterateOutwardsCache.POS_ZERO) { ++ final it.unimi.dsi.fastutil.longs.LongList positions = rangeX == 8 && rangeY == 4 && rangeZ == 8 ? HOGLIN_PIGLIN_CACHE : ITERATE_OUTWARDS_CACHE.getOrCompute(rangeX, rangeY, rangeZ); ++ return new org.dreeam.leaf.util.cache.LongList2BlockPosMutableIterable(center, positions); ++ } ++ // JettPack end + int i = rangeX + rangeY + rangeZ; + // Paper start - rename variables to fix conflict with anonymous class (remap fix) + int centerX = center.getX(); diff --git a/Leaf-archived-patches/hardfork/0127-Lithium-HashedList.patch b/Leaf-archived-patches/hardfork/0127-Lithium-HashedList.patch new file mode 100644 index 00000000..3126642f --- /dev/null +++ b/Leaf-archived-patches/hardfork/0127-Lithium-HashedList.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 8 Nov 2024 00:14:03 +0100 +Subject: [PATCH] Lithium: HashedList + +This patch is based on the following mixins: +* "me/jellysquid/mods/lithium/mixin/world/block_entity_ticking/collections/WorldMixin.java" (1.16.x/dev branch) +* "net/caffeinemc/mods/lithium/common/util/collections/HashedReferenceList.java" +By: 2No2Name <2No2Name@web.de> +As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 859708f1ab4b9f1bd318ca08c73cb67b1c3fe010..c1a3dbefc8d0ef0c36b8cc40a4cfd3c1015d8509 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -116,9 +116,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- public final List blockEntityTickers = Lists.newArrayList(); // Paper - public ++ public final List blockEntityTickers = new org.dreeam.leaf.util.HashedReferenceList<>(Lists.newArrayList()); // Paper - public // Leaf - Lithium - hashed list + protected final NeighborUpdater neighborUpdater; +- private final List pendingBlockEntityTickers = Lists.newArrayList(); ++ private final List pendingBlockEntityTickers = new org.dreeam.leaf.util.HashedReferenceList<>(Lists.newArrayList()); // Leaf - Lithium - hashed list + private boolean tickingBlockEntities; + public final Thread thread; + private final boolean isDebug; diff --git a/patches/server/0128-Smooth-teleport-config.patch b/Leaf-archived-patches/hardfork/0128-Smooth-teleport-config.patch similarity index 70% rename from patches/server/0128-Smooth-teleport-config.patch rename to Leaf-archived-patches/hardfork/0128-Smooth-teleport-config.patch index 8238294e..b7b0aad8 100644 --- a/patches/server/0128-Smooth-teleport-config.patch +++ b/Leaf-archived-patches/hardfork/0128-Smooth-teleport-config.patch @@ -8,10 +8,10 @@ triggering typical respawn packets. All of natural state of chunk resends, entit happen but the visual "refresh" of a world change is hidden. Depending on the destination location/world, this can act as a "smooth teleport" to a world if the new world is very similar looking to the old one. -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java index a27b0a3895290f5abb3a8e07fb886530fdf28c75..94288122eccd23b145e19f0e82750b7d610ea49b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java @@ -1681,7 +1681,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds LevelData worlddata = worldserver.getLevelData(); @@ -34,10 +34,10 @@ index a27b0a3895290f5abb3a8e07fb886530fdf28c75..94288122eccd23b145e19f0e82750b7d this.connection.resetPosition(); worldserver.addDuringTeleport(this); this.triggerDimensionChangeTriggers(worldserver1); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java index 2ccecc0a81c62d96978906aec2124563f3be7541..c6d57c6016b8e410bceddf19827ec43eb2a06229 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java @@ -1021,10 +1021,10 @@ public abstract class PlayerList { ServerLevel worldserver1 = entityplayer1.serverLevel(); LevelData worlddata = worldserver1.getLevelData(); @@ -51,38 +51,3 @@ index 2ccecc0a81c62d96978906aec2124563f3be7541..c6d57c6016b8e410bceddf19827ec43e entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle())); entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); entityplayer1.connection.send(new ClientboundSetExperiencePacket(entityplayer1.experienceProgress, entityplayer1.totalExperience, entityplayer1.experienceLevel)); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java b/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java -new file mode 100644 -index 0000000000000000000000000000000000000000..afed2f9210a1016afd5b7db0a3541ecc8c712d32 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java -@@ -0,0 +1,29 @@ -+package org.dreeam.leaf.config.modules.gameplay; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.annotations.Experimental; -+ -+public class SmoothTeleport extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".smooth-teleport"; -+ } -+ -+ @Experimental -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased( -+ """ -+ **Experimental feature, report any bugs you encounter!** -+ Whether to make a "smooth teleport" when players changing dimension. -+ This requires original world and target world have same logical height to work.""", -+ """ -+ **实验性功能, 请及时反馈你遇到的问题!** -+ 是否在玩家切换世界时尝试使用 "平滑传送". -+ 此项要求源世界和目标世界逻辑高度相同才会生效.""" -+ )); -+ } -+} diff --git a/patches/server/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch b/Leaf-archived-patches/hardfork/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch similarity index 91% rename from patches/server/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch rename to Leaf-archived-patches/hardfork/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch index b1ff791d..724b7f10 100644 --- a/patches/server/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch +++ b/Leaf-archived-patches/hardfork/0129-Use-faster-and-thread-safe-ban-list-date-format-pars.patch @@ -22,10 +22,10 @@ In the end, DateTimeFormatter is also fastest in three implementations in any wa Wether there is a high frequnently calls or not. And also thread-safe. So there is a better solution, why not using it? -diff --git a/src/main/java/net/minecraft/server/players/BanListEntry.java b/src/main/java/net/minecraft/server/players/BanListEntry.java +diff --git a/net/minecraft/server/players/BanListEntry.java b/net/minecraft/server/players/BanListEntry.java index 8b1da1fb5ca27432a39aff6dbc452b793268dab5..46188a8b8598c36ccb0f5e037a80eb1741c68491 100644 ---- a/src/main/java/net/minecraft/server/players/BanListEntry.java -+++ b/src/main/java/net/minecraft/server/players/BanListEntry.java +--- a/net/minecraft/server/players/BanListEntry.java ++++ b/net/minecraft/server/players/BanListEntry.java @@ -10,7 +10,9 @@ import net.minecraft.network.chat.Component; public abstract class BanListEntry extends StoredUserEntry { @@ -105,10 +105,10 @@ index 8b1da1fb5ca27432a39aff6dbc452b793268dab5..46188a8b8598c36ccb0f5e037a80eb17 + } + // Leaf end - Use faster and thread-safe ban list date format parsing } -diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +diff --git a/net/minecraft/server/players/OldUsersConverter.java b/net/minecraft/server/players/OldUsersConverter.java index 653856d0b8dcf2baf4cc77a276f17c8cc1fa717e..6416bc12f3d7733a4bd89d208a320f1b68983788 100644 ---- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java -+++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +--- a/net/minecraft/server/players/OldUsersConverter.java ++++ b/net/minecraft/server/players/OldUsersConverter.java @@ -516,8 +516,10 @@ public class OldUsersConverter { Date date1; diff --git a/patches/server/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch b/Leaf-archived-patches/hardfork/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch similarity index 90% rename from patches/server/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch rename to Leaf-archived-patches/hardfork/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch index e0edd6de..d0d5f1eb 100644 --- a/patches/server/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch +++ b/Leaf-archived-patches/hardfork/0130-Collect-then-startEachNonRunningBehavior-in-Brain.patch @@ -4,10 +4,10 @@ Date: Tue, 9 Nov 2077 00:00:00 +0800 Subject: [PATCH] Collect then startEachNonRunningBehavior in Brain -diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java index 65bd8c2cccd0a4a68984ea8ff4cd3cf365330630..aeb74b96704e0f187178e2ae106e0a3f6d95717c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/Brain.java -+++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java @@ -466,20 +466,30 @@ public class Brain { } diff --git a/patches/server/0131-Lithium-equipment-tracking.patch b/Leaf-archived-patches/hardfork/0131-Lithium-equipment-tracking.patch similarity index 66% rename from patches/server/0131-Lithium-equipment-tracking.patch rename to Leaf-archived-patches/hardfork/0131-Lithium-equipment-tracking.patch index d0988d63..759a9516 100644 --- a/patches/server/0131-Lithium-equipment-tracking.patch +++ b/Leaf-archived-patches/hardfork/0131-Lithium-equipment-tracking.patch @@ -23,253 +23,10 @@ By: 2No2Name <2No2Name@web.de> As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) -diff --git a/src/main/java/net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java b/src/main/java/net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java -new file mode 100644 -index 0000000000000000000000000000000000000000..05604b8f149c13454983d29a2c4dfe1e11dec1fb ---- /dev/null -+++ b/src/main/java/net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java -@@ -0,0 +1,16 @@ -+package net.caffeinemc.mods.lithium.common.entity; -+ -+import net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber; -+import net.minecraft.world.item.ItemStack; -+ -+public interface EquipmentEntity { -+ void onEquipmentReplaced(ItemStack oldStack, ItemStack newStack); -+ -+ interface EquipmentTrackingEntity { -+ void onEquipmentChanged(); -+ } -+ -+ interface TickableEnchantmentTrackingEntity extends ChangeSubscriber.EnchantmentSubscriber { -+ void updateHasTickableEnchantments(ItemStack oldStack, ItemStack newStack); -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9a39b58e8aa3a472a9b5a5af1e5ee6d4eaa4d6e4 ---- /dev/null -+++ b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java -@@ -0,0 +1,17 @@ -+package net.caffeinemc.mods.lithium.common.util.change_tracking; -+ -+import net.minecraft.world.item.ItemStack; -+ -+public interface ChangePublisher { -+ void subscribe(ChangeSubscriber subscriber, int subscriberData); -+ -+ int unsubscribe(ChangeSubscriber subscriber); -+ -+ default void unsubscribeWithData(ChangeSubscriber subscriber, int index) { -+ throw new UnsupportedOperationException("Only implemented for ItemStacks"); -+ } -+ -+ default boolean isSubscribedWithData(ChangeSubscriber subscriber, int subscriberData) { -+ throw new UnsupportedOperationException("Only implemented for ItemStacks"); -+ } -+} -diff --git a/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef206b9ebbd555a786dad37e1ab1bc48e11961cb ---- /dev/null -+++ b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java -@@ -0,0 +1,190 @@ -+package net.caffeinemc.mods.lithium.common.util.change_tracking; -+ -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.ArrayList; -+import net.minecraft.world.item.ItemStack; -+ -+public interface ChangeSubscriber { -+ -+ static ChangeSubscriber combine(ChangeSubscriber prevSubscriber, int prevSData, @NotNull ChangeSubscriber newSubscriber, int newSData) { -+ if (prevSubscriber == null) { -+ return newSubscriber; -+ } else if (prevSubscriber instanceof Multi) { -+ ArrayList> subscribers = new ArrayList<>(((Multi) prevSubscriber).subscribers); -+ IntArrayList subscriberDatas = new IntArrayList(((Multi) prevSubscriber).subscriberDatas); -+ subscribers.add(newSubscriber); -+ subscriberDatas.add(newSData); -+ return new Multi<>(subscribers, subscriberDatas); -+ } else { -+ ArrayList> subscribers = new ArrayList<>(); -+ IntArrayList subscriberDatas = new IntArrayList(); -+ subscribers.add(prevSubscriber); -+ subscriberDatas.add(prevSData); -+ subscribers.add(newSubscriber); -+ subscriberDatas.add(newSData); -+ return new Multi<>(subscribers, subscriberDatas); -+ } -+ } -+ static ChangeSubscriber without(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber) { -+ return without(prevSubscriber, removedSubscriber, 0, false); -+ } -+ -+ static ChangeSubscriber without(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int removedSubscriberData, boolean matchData) { -+ if (prevSubscriber == removedSubscriber) { -+ return null; -+ } else if (prevSubscriber instanceof Multi multi) { -+ int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData); -+ if (index != -1) { -+ if (multi.subscribers.size() == 2) { -+ return multi.subscribers.get(1 - index); -+ } else { -+ ArrayList> subscribers = new ArrayList<>(multi.subscribers); -+ IntArrayList subscriberDatas = new IntArrayList(multi.subscriberDatas); -+ subscribers.remove(index); -+ subscriberDatas.removeInt(index); -+ -+ return new Multi<>(subscribers, subscriberDatas); -+ } -+ } else { -+ return prevSubscriber; -+ } -+ } else { -+ return prevSubscriber; -+ } -+ } -+ -+ static int dataWithout(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int subscriberData) { -+ return dataWithout(prevSubscriber, removedSubscriber, subscriberData, 0, false); -+ } -+ -+ static int dataWithout(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int subscriberData, int removedSubscriberData, boolean matchData) { -+ if (prevSubscriber instanceof Multi multi) { -+ int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData); -+ if (index != -1) { -+ if (multi.subscribers.size() == 2) { -+ return multi.subscriberDatas.getInt(1 - index); -+ } else { -+ return subscriberData; -+ } -+ } else { -+ return subscriberData; -+ } -+ } -+ return prevSubscriber == removedSubscriber ? 0 : subscriberData; -+ } -+ -+ static int dataOf(ChangeSubscriber subscribers, ChangeSubscriber subscriber, int subscriberData) { -+ return subscribers instanceof Multi multi ? multi.subscriberDatas.getInt(multi.subscribers.indexOf(subscriber)) : subscriberData; -+ } -+ -+ static boolean containsSubscriber(ChangeSubscriber subscriber, int subscriberData, ChangeSubscriber subscriber1, int subscriberData1) { -+ if (subscriber instanceof Multi multi) { -+ return multi.indexOf(subscriber1, subscriberData1, true) != -1; -+ } -+ return subscriber == subscriber1 && subscriberData == subscriberData1; -+ } -+ -+ -+ /** -+ * Notify the subscriber that the publisher will be changed immediately after this call. -+ * @param publisher The publisher that is about to change -+ * @param subscriberData The data associated with the subscriber, given when the subscriber was added -+ */ -+ void notify(@Nullable T publisher, int subscriberData); -+ -+ /** -+ * Notify the subscriber about being unsubscribed from the publisher. Used when the publisher becomes invalid. -+ * The subscriber should not attempt to unsubscribe itself from the publisher in this method. -+ * -+ * @param publisher The publisher unsubscribed from -+ * @param subscriberData The data associated with the subscriber, given when the subscriber was added -+ */ -+ void forceUnsubscribe(T publisher, int subscriberData); -+ -+ interface CountChangeSubscriber extends ChangeSubscriber { -+ -+ /** -+ * Notify the subscriber that the publisher's count data will be changed immediately after this call. -+ * @param publisher The publisher that is about to change -+ * @param subscriberData The data associated with the subscriber, given when the subscriber was added -+ * @param newCount The new count of the publisher -+ */ -+ void notifyCount(T publisher, int subscriberData, int newCount); -+ } -+ -+ interface EnchantmentSubscriber extends ChangeSubscriber { -+ -+ /** -+ * Notify the subscriber that the publisher's enchantment data has been changed immediately before this call. -+ * @param publisher The publisher that has changed -+ * @param subscriberData The data associated with the subscriber, given when the subscriber was added -+ */ -+ void notifyAfterEnchantmentChange(T publisher, int subscriberData); -+ } -+ -+ class Multi implements CountChangeSubscriber, EnchantmentSubscriber { -+ private final ArrayList> subscribers; -+ private final IntArrayList subscriberDatas; -+ -+ public Multi(ArrayList> subscribers, IntArrayList subscriberDatas) { -+ this.subscribers = subscribers; -+ this.subscriberDatas = subscriberDatas; -+ } -+ -+ @Override -+ public void notify(T publisher, int subscriberData) { -+ ArrayList> changeSubscribers = this.subscribers; -+ for (int i = 0; i < changeSubscribers.size(); i++) { -+ ChangeSubscriber subscriber = changeSubscribers.get(i); -+ subscriber.notify(publisher, this.subscriberDatas.getInt(i)); -+ } -+ } -+ -+ @Override -+ public void forceUnsubscribe(T publisher, int subscriberData) { -+ ArrayList> changeSubscribers = this.subscribers; -+ for (int i = 0; i < changeSubscribers.size(); i++) { -+ ChangeSubscriber subscriber = changeSubscribers.get(i); -+ subscriber.forceUnsubscribe(publisher, this.subscriberDatas.getInt(i)); -+ } -+ } -+ -+ @Override -+ public void notifyCount(T publisher, int subscriberData, int newCount) { -+ ArrayList> changeSubscribers = this.subscribers; -+ for (int i = 0; i < changeSubscribers.size(); i++) { -+ ChangeSubscriber subscriber = changeSubscribers.get(i); -+ if (subscriber instanceof ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { -+ countChangeSubscriber.notifyCount(publisher, this.subscriberDatas.getInt(i), newCount); -+ } -+ } -+ } -+ -+ int indexOf(ChangeSubscriber subscriber, int subscriberData, boolean matchData) { -+ if (!matchData) { -+ return this.subscribers.indexOf(subscriber); -+ } else { -+ for (int i = 0; i < this.subscribers.size(); i++) { -+ if (this.subscribers.get(i) == subscriber && this.subscriberDatas.getInt(i) == subscriberData) { -+ return i; -+ } -+ } -+ return -1; -+ } -+ } -+ -+ @Override -+ public void notifyAfterEnchantmentChange(T publisher, int subscriberData) { -+ ArrayList> changeSubscribers = this.subscribers; -+ for (int i = 0; i < changeSubscribers.size(); i++) { -+ ChangeSubscriber subscriber = changeSubscribers.get(i); -+ if (subscriber instanceof ChangeSubscriber.EnchantmentSubscriber enchantmentSubscriber) { -+ enchantmentSubscriber.notifyAfterEnchantmentChange(publisher, this.subscriberDatas.getInt(i)); -+ } -+ } -+ } -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java +diff --git a/net/minecraft/core/component/PatchedDataComponentMap.java b/net/minecraft/core/component/PatchedDataComponentMap.java index ceee6345530c3bf91cce988af2da12f0798d8f4b..1289fecee1f05abfce09672ec406caf759943b5c 100644 ---- a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java -+++ b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java +--- a/net/minecraft/core/component/PatchedDataComponentMap.java ++++ b/net/minecraft/core/component/PatchedDataComponentMap.java @@ -14,10 +14,11 @@ import java.util.Map.Entry; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -316,10 +73,10 @@ index ceee6345530c3bf91cce988af2da12f0798d8f4b..1289fecee1f05abfce09672ec406caf7 @Override public boolean equals(Object object) { if (this == object) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java index 2b8cc1cfeda50721c063429a7d31623dc93089ea..ac19c28135debebf0e1055d47571dc068f10e30e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java @@ -160,7 +160,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; // CraftBukkit end @@ -456,10 +213,10 @@ index 2b8cc1cfeda50721c063429a7d31623dc93089ea..ac19c28135debebf0e1055d47571dc06 public static record Fallsounds(SoundEvent small, SoundEvent big) { -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java index 1e4729be4a245a811fd15ea1c02179b37defd67c..5814d9b6b0fc5346c24dd268ec147a67c5acbfdb 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java @@ -98,7 +98,7 @@ import org.bukkit.event.entity.EntityUnleashEvent; import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; // CraftBukkit end @@ -526,10 +283,10 @@ index 1e4729be4a245a811fd15ea1c02179b37defd67c..5814d9b6b0fc5346c24dd268ec147a67 + } + // Leaf end - Lithium equipment tracking } -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java index a3c284976b37e865c51ee91166c4046a3c4f3a16..b34522a57cf3ee5679481ead61ae52ac9a28f6f9 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +--- a/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/net/minecraft/world/entity/decoration/ArmorStand.java @@ -54,7 +54,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerArmorStandManipulateEvent; // CraftBukkit end @@ -625,10 +382,10 @@ index a3c284976b37e865c51ee91166c4046a3c4f3a16..b34522a57cf3ee5679481ead61ae52ac + } + // Leaf end - Lithium equipment tracking } -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java index 1029499ce8fb236a23beb9dae168b82039734e59..4a5d6767bf4ead62f3c533fa33f7ce98637cce31 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java @@ -125,7 +125,7 @@ import org.bukkit.event.player.PlayerItemDamageEvent; import org.bukkit.event.world.StructureGrowEvent; // CraftBukkit end diff --git a/patches/server/0132-Faster-CraftServer-getworlds-list-creation.patch b/Leaf-archived-patches/hardfork/0132-Faster-CraftServer-getworlds-list-creation.patch similarity index 100% rename from patches/server/0132-Faster-CraftServer-getworlds-list-creation.patch rename to Leaf-archived-patches/hardfork/0132-Faster-CraftServer-getworlds-list-creation.patch diff --git a/patches/server/0133-C2ME-Optimize-world-gen-math.patch b/Leaf-archived-patches/hardfork/0133-C2ME-Optimize-world-gen-math.patch similarity index 74% rename from patches/server/0133-C2ME-Optimize-world-gen-math.patch rename to Leaf-archived-patches/hardfork/0133-C2ME-Optimize-world-gen-math.patch index f57d5737..12a94569 100644 --- a/patches/server/0133-C2ME-Optimize-world-gen-math.patch +++ b/Leaf-archived-patches/hardfork/0133-C2ME-Optimize-world-gen-math.patch @@ -12,10 +12,10 @@ Licensed under: MIT Co-authored-by: Taiyou06 -diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +diff --git a/net/minecraft/world/level/ChunkPos.java b/net/minecraft/world/level/ChunkPos.java index 0639e4565c3324d757dec1226adb4e99d841f2c0..78fdfa78ff6d2a5307a0a6959b051cd2dce442fe 100644 ---- a/src/main/java/net/minecraft/world/level/ChunkPos.java -+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +--- a/net/minecraft/world/level/ChunkPos.java ++++ b/net/minecraft/world/level/ChunkPos.java @@ -110,7 +110,12 @@ public class ChunkPos { @Override @@ -30,10 +30,10 @@ index 0639e4565c3324d757dec1226adb4e99d841f2c0..78fdfa78ff6d2a5307a0a6959b051cd2 } public int getMiddleBlockX() { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java +diff --git a/net/minecraft/world/level/levelgen/Beardifier.java b/net/minecraft/world/level/levelgen/Beardifier.java index ca93a97256350789ca56f910862c9d717ca7670b..3a1a5257e1a98cc1d520f407bb1f8c745cadd3df 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java +--- a/net/minecraft/world/level/levelgen/Beardifier.java ++++ b/net/minecraft/world/level/levelgen/Beardifier.java @@ -132,8 +132,14 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { } @@ -51,10 +51,10 @@ index ca93a97256350789ca56f910862c9d717ca7670b..3a1a5257e1a98cc1d520f407bb1f8c74 } private static double getBeardContribution(int x, int y, int z, int yy) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java index bd0eaa7d5253fd4ea9f9a6f0c7bfcf11fbc675a7..1a514189eb0b43548b075a6cfb571431892ad674 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java @@ -76,14 +76,13 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { } @@ -74,10 +74,10 @@ index bd0eaa7d5253fd4ea9f9a6f0c7bfcf11fbc675a7..1a514189eb0b43548b075a6cfb571431 } @Override -diff --git a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +diff --git a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java index 022dda9dded1bd96dcaf377b1d1a9711ea9c49e7..1b0c92f3c20a4edc85a976a0eec620d0a96b5bba 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +--- a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java @@ -215,7 +215,9 @@ public class PerlinNoise { } diff --git a/patches/server/0134-Cache-chunk-key.patch b/Leaf-archived-patches/hardfork/0134-Cache-chunk-key.patch similarity index 94% rename from patches/server/0134-Cache-chunk-key.patch rename to Leaf-archived-patches/hardfork/0134-Cache-chunk-key.patch index 1e247aa3..ce321ace 100644 --- a/patches/server/0134-Cache-chunk-key.patch +++ b/Leaf-archived-patches/hardfork/0134-Cache-chunk-key.patch @@ -106,10 +106,10 @@ index 571db5f9bf94745a8afe2cd313e593fb15db5e37..108db549eeb4a33c9a9c0c1983376613 if (valueInMap == null) { valueInMap = new ServerChunkTasks( keyInMap, ServerLightQueue.this.lightInterface, ServerLightQueue.this, priority -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 4a92789d77313e165ab1252cd469e34a8b7eb575..9469f9edd9f94f7644dc28c9be2fb1b9843e84e1 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java @@ -2638,7 +2638,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean isNaturalSpawningAllowed(ChunkPos pos) { @@ -119,10 +119,10 @@ index 4a92789d77313e165ab1252cd469e34a8b7eb575..9469f9edd9f94f7644dc28c9be2fb1b9 return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } -diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +diff --git a/net/minecraft/world/level/ChunkPos.java b/net/minecraft/world/level/ChunkPos.java index 78fdfa78ff6d2a5307a0a6959b051cd2dce442fe..1a20d1ed779caf5eba260d21cc0afdf7edff5bf6 100644 ---- a/src/main/java/net/minecraft/world/level/ChunkPos.java -+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +--- a/net/minecraft/world/level/ChunkPos.java ++++ b/net/minecraft/world/level/ChunkPos.java @@ -47,6 +47,7 @@ public class ChunkPos { public final int x; public final int z; diff --git a/patches/server/0135-Cache-random-tick-block-status.patch b/Leaf-archived-patches/hardfork/0135-Cache-random-tick-block-status.patch similarity index 90% rename from patches/server/0135-Cache-random-tick-block-status.patch rename to Leaf-archived-patches/hardfork/0135-Cache-random-tick-block-status.patch index 4549c070..c4937d4a 100644 --- a/patches/server/0135-Cache-random-tick-block-status.patch +++ b/Leaf-archived-patches/hardfork/0135-Cache-random-tick-block-status.patch @@ -4,10 +4,10 @@ Date: Sat, 23 Nov 2024 09:04:46 -0500 Subject: [PATCH] Cache random tick block status -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java index e4ae25c83ab9dd1aaa530a5456275ef63cdb8511..f19d9bba70957fc652c3222cadd247eb837c2a13 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -22,6 +22,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ short nonEmptyBlockCount; // Paper - package private private short tickingBlockCount; diff --git a/patches/server/0136-Cache-canHoldAnyFluid-result.patch b/Leaf-archived-patches/hardfork/0136-Cache-canHoldAnyFluid-result.patch similarity index 86% rename from patches/server/0136-Cache-canHoldAnyFluid-result.patch rename to Leaf-archived-patches/hardfork/0136-Cache-canHoldAnyFluid-result.patch index f4545e76..e2c5cd52 100644 --- a/patches/server/0136-Cache-canHoldAnyFluid-result.patch +++ b/Leaf-archived-patches/hardfork/0136-Cache-canHoldAnyFluid-result.patch @@ -9,10 +9,10 @@ which the contains iteration call is very expensive if called everytime In the test, it can improve ~30% performance in ~1577000 times of canHoldAnyFluid calls (~159ms -> ~111ms) -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 +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java index 65d8ac795282117ba88003e7a703ee649a359473..b0df5ac8efdca17498f7f87bb86e376122aa6fe3 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 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -836,6 +836,8 @@ public abstract class BlockBehaviour implements FeatureElement { private VoxelShape[] occlusionShapesByFace; private boolean propagatesSkylightDown; @@ -50,10 +50,10 @@ index 65d8ac795282117ba88003e7a703ee649a359473..b0df5ac8efdca17498f7f87bb86e3761 // Paper start - Protect Bedrock and End Portal/Frames from being destroyed public final boolean isDestroyable() { return getBlock().isDestroyable(); -diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java index 408e7c61d87a0e6d8502bf1f5ca76fd728c5d10c..de761ea851d47e59538b4d0d3cec5624bf37b84a 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +--- a/net/minecraft/world/level/material/FlowingFluid.java ++++ b/net/minecraft/world/level/material/FlowingFluid.java @@ -494,14 +494,14 @@ public abstract class FlowingFluid extends Fluid { return map; } diff --git a/Leaf-archived-patches/hardfork/0137-Configurable-tripwire-dupe.patch b/Leaf-archived-patches/hardfork/0137-Configurable-tripwire-dupe.patch new file mode 100644 index 00000000..8ff3cfe2 --- /dev/null +++ b/Leaf-archived-patches/hardfork/0137-Configurable-tripwire-dupe.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Tue, 24 Dec 2024 13:28:56 -0500 +Subject: [PATCH] Configurable tripwire dupe + +Bring back MC-59471, MC-129055 on 1.21.2+, which fixed in 1.21.2 snapshots 24w33a and 24w36a + +diff --git a/net/minecraft/world/level/block/TripWireHookBlock.java b/net/minecraft/world/level/block/TripWireHookBlock.java +index c2589f42c467ca672417c24076313da51bb2dcbb..fda1f2840f5a79d99217bd0fa72f4a1b1a75eb7e 100644 +--- a/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -206,7 +206,7 @@ public class TripWireHookBlock extends Block { + if (iblockdata4 != null) { + BlockState iblockdata5 = world.getBlockState(blockposition2); + +- if (iblockdata5.is(Blocks.TRIPWIRE) || iblockdata5.is(Blocks.TRIPWIRE_HOOK)) { ++ if (org.dreeam.leaf.config.modules.gameplay.ConfigurableTripWireDupe.enabled || iblockdata5.is(Blocks.TRIPWIRE) || iblockdata5.is(Blocks.TRIPWIRE_HOOK)) { // Leaf - Configurable tripwire dupe + world.setBlock(blockposition2, (BlockState) iblockdata4.trySetValue(TripWireHookBlock.ATTACHED, flag4), 3); + } + } diff --git a/patches/server/0002-Decompile-fix.patch b/Leaf-archived-patches/removed/hardfork/server/0002-Decompile-fix.patch similarity index 84% rename from patches/server/0002-Decompile-fix.patch rename to Leaf-archived-patches/removed/hardfork/server/0002-Decompile-fix.patch index 25ba14fe..2f9645db 100644 --- a/patches/server/0002-Decompile-fix.patch +++ b/Leaf-archived-patches/removed/hardfork/server/0002-Decompile-fix.patch @@ -3,11 +3,12 @@ From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Tue, 21 May 2024 19:07:47 +0800 Subject: [PATCH] Decompile fix +Removed since Paper 1.21.4, hardfork -diff --git a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +diff --git a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java index b2a4ae4388758f89fcd0dc3b0c4c03bf46fa5470..a4f9de18d40d5d85a809af3777b00eb75b1af027 100644 ---- a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java -+++ b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +--- a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java ++++ b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java @@ -34,7 +34,7 @@ public interface CustomPacketPayload { private void writeCap(B value, CustomPacketPayload.Type id, CustomPacketPayload payload) { @@ -17,10 +18,10 @@ index b2a4ae4388758f89fcd0dc3b0c4c03bf46fa5470..a4f9de18d40d5d85a809af3777b00eb7 streamCodec.encode(value, (T)payload); } -diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java index afbb027021acfbe25d534a84f1750e420bbde6e0..2ae8c9d56d88987b750e57025d313cfa9300c7e4 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/Brain.java -+++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java @@ -79,7 +79,7 @@ public class Brain { } diff --git a/patches/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch b/Leaf-archived-patches/removed/hardfork/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch similarity index 99% rename from patches/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch rename to Leaf-archived-patches/removed/hardfork/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch index a47d3808..3758b3bc 100644 --- a/patches/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch +++ b/Leaf-archived-patches/removed/hardfork/server/0049-PaperPR-Rewrite-framed-map-tracker-ticking.patch @@ -3,6 +3,8 @@ From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Mon, 14 Aug 2023 17:34:53 +0200 Subject: [PATCH] PaperPR: Rewrite framed map tracker ticking +Removed since Leaf 1.21.4, incomplete design, see original pr for more + Original license: GPLv3 Original project: - https://github.com/PaperMC/Paper/pull/9605 diff --git a/patches/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch b/Leaf-archived-patches/removed/hardfork/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch similarity index 99% rename from patches/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch rename to Leaf-archived-patches/removed/hardfork/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch index 62210652..4264fe76 100644 --- a/patches/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch +++ b/Leaf-archived-patches/removed/hardfork/server/0074-Airplane-Remove-stream-in-PoiCompetitorScan.patch @@ -3,6 +3,8 @@ From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:56:59 +0800 Subject: [PATCH] Airplane: Remove stream in PoiCompetitorScan +Removed since Paper 1.21.4 + Original license: GPLv3 Original project: https://github.com/TECHNOVE/Airplane-Experimental diff --git a/patches/server/0099-Fix-MC-150224.patch b/Leaf-archived-patches/removed/hardfork/server/0099-Fix-MC-150224.patch similarity index 97% rename from patches/server/0099-Fix-MC-150224.patch rename to Leaf-archived-patches/removed/hardfork/server/0099-Fix-MC-150224.patch index 47ff44d4..70d44b7a 100644 --- a/patches/server/0099-Fix-MC-150224.patch +++ b/Leaf-archived-patches/removed/hardfork/server/0099-Fix-MC-150224.patch @@ -3,6 +3,8 @@ From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Tue, 9 Nov 2077 00:00:00 +0800 Subject: [PATCH] Fix MC-150224 +Removed since Leaf 1.21.4, Mojang fixed in Minecraft 1.21.4 24w46a + Related MC issue: https://bugs.mojang.com/browse/MC-150224 This patch was backported from Minecraft snapshot 24w46a. diff --git a/patches/server/0103-Hide-specified-item-components-to-clients.patch b/Leaf-archived-patches/removed/hardfork/server/0103-Hide-specified-item-components-to-clients.patch similarity index 99% rename from patches/server/0103-Hide-specified-item-components-to-clients.patch rename to Leaf-archived-patches/removed/hardfork/server/0103-Hide-specified-item-components-to-clients.patch index 69d2efb1..d85d357b 100644 --- a/patches/server/0103-Hide-specified-item-components-to-clients.patch +++ b/Leaf-archived-patches/removed/hardfork/server/0103-Hide-specified-item-components-to-clients.patch @@ -3,6 +3,7 @@ From: TheFloodDragon <1610105206@qq.com> Date: Sun, 4 Aug 2024 19:36:11 +0800 Subject: [PATCH] Hide specified item components to clients +Removed since Leaf 1.21.4, replaced by Paper's ItemStack Obfuscation diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java index 8d5939e03a065197af125d95a10134abbccd07ec..0acd7a54dea269b172fb909dd28ac82f6691c319 100644 diff --git a/patches/server/0111-Change-max-stack-count.patch b/Leaf-archived-patches/removed/hardfork/server/0111-Change-max-stack-count.patch similarity index 99% rename from patches/server/0111-Change-max-stack-count.patch rename to Leaf-archived-patches/removed/hardfork/server/0111-Change-max-stack-count.patch index 31627b53..fead2aaf 100644 --- a/patches/server/0111-Change-max-stack-count.patch +++ b/Leaf-archived-patches/removed/hardfork/server/0111-Change-max-stack-count.patch @@ -3,6 +3,8 @@ From: FallingKey Date: Sun, 11 Aug 2024 14:20:37 +0800 Subject: [PATCH] Change max stack count +Removed since Leaf 1.21.4, useless + TODO - Dreeam: - Check shulkerbox unpack whether correct - stacked dropped shulkerbox unpack issue, need to base on box's item count diff --git a/patches/removed/api/0007-KTP-Allow-unknown-event-thread-execution.patch b/Leaf-archived-patches/removed/legacy/api/0007-KTP-Allow-unknown-event-thread-execution.patch similarity index 100% rename from patches/removed/api/0007-KTP-Allow-unknown-event-thread-execution.patch rename to Leaf-archived-patches/removed/legacy/api/0007-KTP-Allow-unknown-event-thread-execution.patch diff --git a/patches/removed/api/0010-Paper-PR-Optimise-color-distance-check-in-MapPalette.patch b/Leaf-archived-patches/removed/legacy/api/0010-Paper-PR-Optimise-color-distance-check-in-MapPalette.patch similarity index 100% rename from patches/removed/api/0010-Paper-PR-Optimise-color-distance-check-in-MapPalette.patch rename to Leaf-archived-patches/removed/legacy/api/0010-Paper-PR-Optimise-color-distance-check-in-MapPalette.patch diff --git a/patches/removed/server/0002-Leaf-Config-v1.patch b/Leaf-archived-patches/removed/legacy/server/0002-Leaf-Config-v1.patch similarity index 100% rename from patches/removed/server/0002-Leaf-Config-v1.patch rename to Leaf-archived-patches/removed/legacy/server/0002-Leaf-Config-v1.patch diff --git a/patches/removed/server/0003-Leaf-Config-v2.patch b/Leaf-archived-patches/removed/legacy/server/0003-Leaf-Config-v2.patch similarity index 100% rename from patches/removed/server/0003-Leaf-Config-v2.patch rename to Leaf-archived-patches/removed/legacy/server/0003-Leaf-Config-v2.patch diff --git a/patches/removed/server/0004-Leaf-Config-legacy-converter.patch b/Leaf-archived-patches/removed/legacy/server/0004-Leaf-Config-legacy-converter.patch similarity index 100% rename from patches/removed/server/0004-Leaf-Config-legacy-converter.patch rename to Leaf-archived-patches/removed/legacy/server/0004-Leaf-Config-legacy-converter.patch diff --git a/patches/removed/server/0010-Pufferfish-Entity-TTL.patch b/Leaf-archived-patches/removed/legacy/server/0010-Pufferfish-Entity-TTL.patch similarity index 100% rename from patches/removed/server/0010-Pufferfish-Entity-TTL.patch rename to Leaf-archived-patches/removed/legacy/server/0010-Pufferfish-Entity-TTL.patch diff --git a/patches/removed/server/0020-KTP-Allow-unknown-event-thread-execution.patch b/Leaf-archived-patches/removed/legacy/server/0020-KTP-Allow-unknown-event-thread-execution.patch similarity index 100% rename from patches/removed/server/0020-KTP-Allow-unknown-event-thread-execution.patch rename to Leaf-archived-patches/removed/legacy/server/0020-KTP-Allow-unknown-event-thread-execution.patch diff --git a/patches/removed/server/0036-Fix-Make-log4j-compatible-with-future-release.patch b/Leaf-archived-patches/removed/legacy/server/0036-Fix-Make-log4j-compatible-with-future-release.patch similarity index 100% rename from patches/removed/server/0036-Fix-Make-log4j-compatible-with-future-release.patch rename to Leaf-archived-patches/removed/legacy/server/0036-Fix-Make-log4j-compatible-with-future-release.patch diff --git a/patches/removed/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch b/Leaf-archived-patches/removed/legacy/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch similarity index 100% rename from patches/removed/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch rename to Leaf-archived-patches/removed/legacy/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch diff --git a/patches/removed/server/0039-NachoSpigot-Async-Explosion.patch b/Leaf-archived-patches/removed/legacy/server/0039-NachoSpigot-Async-Explosion.patch similarity index 100% rename from patches/removed/server/0039-NachoSpigot-Async-Explosion.patch rename to Leaf-archived-patches/removed/legacy/server/0039-NachoSpigot-Async-Explosion.patch diff --git a/patches/removed/server/0041-Petal-Multithreaded-Tracker.patch b/Leaf-archived-patches/removed/legacy/server/0041-Petal-Multithreaded-Tracker.patch similarity index 100% rename from patches/removed/server/0041-Petal-Multithreaded-Tracker.patch rename to Leaf-archived-patches/removed/legacy/server/0041-Petal-Multithreaded-Tracker.patch diff --git a/patches/removed/server/0048-Fix-keepalive-kicked-name.patch b/Leaf-archived-patches/removed/legacy/server/0048-Fix-keepalive-kicked-name.patch similarity index 100% rename from patches/removed/server/0048-Fix-keepalive-kicked-name.patch rename to Leaf-archived-patches/removed/legacy/server/0048-Fix-keepalive-kicked-name.patch diff --git a/patches/removed/server/0048-PandaSpigot-Configurable-knockback.patch b/Leaf-archived-patches/removed/legacy/server/0048-PandaSpigot-Configurable-knockback.patch similarity index 100% rename from patches/removed/server/0048-PandaSpigot-Configurable-knockback.patch rename to Leaf-archived-patches/removed/legacy/server/0048-PandaSpigot-Configurable-knockback.patch diff --git a/patches/removed/server/0051-Fix-MC-26678.patch b/Leaf-archived-patches/removed/legacy/server/0051-Fix-MC-26678.patch similarity index 100% rename from patches/removed/server/0051-Fix-MC-26678.patch rename to Leaf-archived-patches/removed/legacy/server/0051-Fix-MC-26678.patch diff --git a/patches/removed/server/0051-Fix-TerminalConsoleAppender-NPE-error-on-server-clos.patch b/Leaf-archived-patches/removed/legacy/server/0051-Fix-TerminalConsoleAppender-NPE-error-on-server-clos.patch similarity index 100% rename from patches/removed/server/0051-Fix-TerminalConsoleAppender-NPE-error-on-server-clos.patch rename to Leaf-archived-patches/removed/legacy/server/0051-Fix-TerminalConsoleAppender-NPE-error-on-server-clos.patch diff --git a/patches/removed/server/0053-LinearPurpur-Add-Linear-region-format.patch b/Leaf-archived-patches/removed/legacy/server/0053-LinearPurpur-Add-Linear-region-format.patch similarity index 100% rename from patches/removed/server/0053-LinearPurpur-Add-Linear-region-format.patch rename to Leaf-archived-patches/removed/legacy/server/0053-LinearPurpur-Add-Linear-region-format.patch diff --git a/patches/removed/server/0054-LinearPurpur-Just-remove-all-locks-on-region-files.patch b/Leaf-archived-patches/removed/legacy/server/0054-LinearPurpur-Just-remove-all-locks-on-region-files.patch similarity index 100% rename from patches/removed/server/0054-LinearPurpur-Just-remove-all-locks-on-region-files.patch rename to Leaf-archived-patches/removed/legacy/server/0054-LinearPurpur-Just-remove-all-locks-on-region-files.patch diff --git a/patches/removed/server/0056-Fix-MC-2025.patch b/Leaf-archived-patches/removed/legacy/server/0056-Fix-MC-2025.patch similarity index 100% rename from patches/removed/server/0056-Fix-MC-2025.patch rename to Leaf-archived-patches/removed/legacy/server/0056-Fix-MC-2025.patch diff --git a/patches/removed/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch b/Leaf-archived-patches/removed/legacy/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch similarity index 100% rename from patches/removed/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch rename to Leaf-archived-patches/removed/legacy/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch diff --git a/patches/removed/server/0060-Moonrise-Bitstorage-optimisations.patch b/Leaf-archived-patches/removed/legacy/server/0060-Moonrise-Bitstorage-optimisations.patch similarity index 100% rename from patches/removed/server/0060-Moonrise-Bitstorage-optimisations.patch rename to Leaf-archived-patches/removed/legacy/server/0060-Moonrise-Bitstorage-optimisations.patch diff --git a/patches/removed/server/0062-Moonrise-block-counting-optimisations.patch b/Leaf-archived-patches/removed/legacy/server/0062-Moonrise-block-counting-optimisations.patch similarity index 100% rename from patches/removed/server/0062-Moonrise-block-counting-optimisations.patch rename to Leaf-archived-patches/removed/legacy/server/0062-Moonrise-block-counting-optimisations.patch diff --git a/patches/removed/server/0063-Moonrise-Optimise-BiomeManager-getFiddle.patch b/Leaf-archived-patches/removed/legacy/server/0063-Moonrise-Optimise-BiomeManager-getFiddle.patch similarity index 100% rename from patches/removed/server/0063-Moonrise-Optimise-BiomeManager-getFiddle.patch rename to Leaf-archived-patches/removed/legacy/server/0063-Moonrise-Optimise-BiomeManager-getFiddle.patch diff --git a/patches/removed/server/0063-Redirect-to-Gale-s-method-to-fix-plugin-incompatibil.patch b/Leaf-archived-patches/removed/legacy/server/0063-Redirect-to-Gale-s-method-to-fix-plugin-incompatibil.patch similarity index 100% rename from patches/removed/server/0063-Redirect-to-Gale-s-method-to-fix-plugin-incompatibil.patch rename to Leaf-archived-patches/removed/legacy/server/0063-Redirect-to-Gale-s-method-to-fix-plugin-incompatibil.patch diff --git a/patches/removed/server/0064-Moonrise-Do-not-send-chunk-radius-packet-from-Player.patch b/Leaf-archived-patches/removed/legacy/server/0064-Moonrise-Do-not-send-chunk-radius-packet-from-Player.patch similarity index 100% rename from patches/removed/server/0064-Moonrise-Do-not-send-chunk-radius-packet-from-Player.patch rename to Leaf-archived-patches/removed/legacy/server/0064-Moonrise-Do-not-send-chunk-radius-packet-from-Player.patch diff --git a/patches/removed/server/0065-Fix-MC-249136-lag-when-attempting-to-locate-a-buried.patch b/Leaf-archived-patches/removed/legacy/server/0065-Fix-MC-249136-lag-when-attempting-to-locate-a-buried.patch similarity index 100% rename from patches/removed/server/0065-Fix-MC-249136-lag-when-attempting-to-locate-a-buried.patch rename to Leaf-archived-patches/removed/legacy/server/0065-Fix-MC-249136-lag-when-attempting-to-locate-a-buried.patch diff --git a/patches/removed/server/0065-Moonrise-Add-direct-lookup-by-chunk-for-NearbyPlayer.patch b/Leaf-archived-patches/removed/legacy/server/0065-Moonrise-Add-direct-lookup-by-chunk-for-NearbyPlayer.patch similarity index 100% rename from patches/removed/server/0065-Moonrise-Add-direct-lookup-by-chunk-for-NearbyPlayer.patch rename to Leaf-archived-patches/removed/legacy/server/0065-Moonrise-Add-direct-lookup-by-chunk-for-NearbyPlayer.patch diff --git a/patches/removed/server/0068-Block-log4j-rce-exploit-in-chat.patch b/Leaf-archived-patches/removed/legacy/server/0068-Block-log4j-rce-exploit-in-chat.patch similarity index 100% rename from patches/removed/server/0068-Block-log4j-rce-exploit-in-chat.patch rename to Leaf-archived-patches/removed/legacy/server/0068-Block-log4j-rce-exploit-in-chat.patch diff --git a/patches/removed/server/0068-Fix-MC-172047.patch b/Leaf-archived-patches/removed/legacy/server/0068-Fix-MC-172047.patch similarity index 100% rename from patches/removed/server/0068-Fix-MC-172047.patch rename to Leaf-archived-patches/removed/legacy/server/0068-Fix-MC-172047.patch diff --git a/patches/removed/server/0068-Moonrise-Optimise-countEntries-for-low-size-SimpleBi.patch b/Leaf-archived-patches/removed/legacy/server/0068-Moonrise-Optimise-countEntries-for-low-size-SimpleBi.patch similarity index 100% rename from patches/removed/server/0068-Moonrise-Optimise-countEntries-for-low-size-SimpleBi.patch rename to Leaf-archived-patches/removed/legacy/server/0068-Moonrise-Optimise-countEntries-for-low-size-SimpleBi.patch diff --git a/patches/removed/server/0069-Fix-NPE-during-creating-GUI-graph.patch b/Leaf-archived-patches/removed/legacy/server/0069-Fix-NPE-during-creating-GUI-graph.patch similarity index 100% rename from patches/removed/server/0069-Fix-NPE-during-creating-GUI-graph.patch rename to Leaf-archived-patches/removed/legacy/server/0069-Fix-NPE-during-creating-GUI-graph.patch diff --git a/patches/removed/server/0069-Moonrise-fluid-method-optimisations.patch b/Leaf-archived-patches/removed/legacy/server/0069-Moonrise-fluid-method-optimisations.patch similarity index 100% rename from patches/removed/server/0069-Moonrise-fluid-method-optimisations.patch rename to Leaf-archived-patches/removed/legacy/server/0069-Moonrise-fluid-method-optimisations.patch diff --git a/patches/removed/server/0070-Configurable-bamboo-collision.patch b/Leaf-archived-patches/removed/legacy/server/0070-Configurable-bamboo-collision.patch similarity index 100% rename from patches/removed/server/0070-Configurable-bamboo-collision.patch rename to Leaf-archived-patches/removed/legacy/server/0070-Configurable-bamboo-collision.patch diff --git a/patches/removed/server/0070-Moonrise-optimise-palette-reads.patch b/Leaf-archived-patches/removed/legacy/server/0070-Moonrise-optimise-palette-reads.patch similarity index 100% rename from patches/removed/server/0070-Moonrise-optimise-palette-reads.patch rename to Leaf-archived-patches/removed/legacy/server/0070-Moonrise-optimise-palette-reads.patch diff --git a/patches/removed/server/0075-Use-a-shadow-fork-that-supports-Java-21.patch b/Leaf-archived-patches/removed/legacy/server/0075-Use-a-shadow-fork-that-supports-Java-21.patch similarity index 100% rename from patches/removed/server/0075-Use-a-shadow-fork-that-supports-Java-21.patch rename to Leaf-archived-patches/removed/legacy/server/0075-Use-a-shadow-fork-that-supports-Java-21.patch diff --git a/patches/removed/server/0081-Implement-Noisium.patch b/Leaf-archived-patches/removed/legacy/server/0081-Implement-Noisium.patch similarity index 100% rename from patches/removed/server/0081-Implement-Noisium.patch rename to Leaf-archived-patches/removed/legacy/server/0081-Implement-Noisium.patch diff --git a/patches/removed/server/0083-Ignore-terminal-provider-warning.patch b/Leaf-archived-patches/removed/legacy/server/0083-Ignore-terminal-provider-warning.patch similarity index 100% rename from patches/removed/server/0083-Ignore-terminal-provider-warning.patch rename to Leaf-archived-patches/removed/legacy/server/0083-Ignore-terminal-provider-warning.patch diff --git a/patches/removed/server/0083-Tracking-Optimize-Reduce-expensive-iteration.patch b/Leaf-archived-patches/removed/legacy/server/0083-Tracking-Optimize-Reduce-expensive-iteration.patch similarity index 100% rename from patches/removed/server/0083-Tracking-Optimize-Reduce-expensive-iteration.patch rename to Leaf-archived-patches/removed/legacy/server/0083-Tracking-Optimize-Reduce-expensive-iteration.patch diff --git a/patches/removed/server/0084-Fix-console-freeze-above-JAVA-22.patch b/Leaf-archived-patches/removed/legacy/server/0084-Fix-console-freeze-above-JAVA-22.patch similarity index 100% rename from patches/removed/server/0084-Fix-console-freeze-above-JAVA-22.patch rename to Leaf-archived-patches/removed/legacy/server/0084-Fix-console-freeze-above-JAVA-22.patch diff --git a/patches/removed/server/0085-Fix-console-output-display-on-Pterodactyl-panel.patch b/Leaf-archived-patches/removed/legacy/server/0085-Fix-console-output-display-on-Pterodactyl-panel.patch similarity index 100% rename from patches/removed/server/0085-Fix-console-output-display-on-Pterodactyl-panel.patch rename to Leaf-archived-patches/removed/legacy/server/0085-Fix-console-output-display-on-Pterodactyl-panel.patch diff --git a/patches/removed/server/0090-Remove-stream-in-RecipeManager-getRecipeFor.patch b/Leaf-archived-patches/removed/legacy/server/0090-Remove-stream-in-RecipeManager-getRecipeFor.patch similarity index 100% rename from patches/removed/server/0090-Remove-stream-in-RecipeManager-getRecipeFor.patch rename to Leaf-archived-patches/removed/legacy/server/0090-Remove-stream-in-RecipeManager-getRecipeFor.patch diff --git a/patches/removed/server/0093-Optimize-check-nearby-fire-or-lava-on-entity-move.patch b/Leaf-archived-patches/removed/legacy/server/0093-Optimize-check-nearby-fire-or-lava-on-entity-move.patch similarity index 100% rename from patches/removed/server/0093-Optimize-check-nearby-fire-or-lava-on-entity-move.patch rename to Leaf-archived-patches/removed/legacy/server/0093-Optimize-check-nearby-fire-or-lava-on-entity-move.patch diff --git a/patches/removed/server/0094-Fix-Nova-compatibility.patch b/Leaf-archived-patches/removed/legacy/server/0094-Fix-Nova-compatibility.patch similarity index 100% rename from patches/removed/server/0094-Fix-Nova-compatibility.patch rename to Leaf-archived-patches/removed/legacy/server/0094-Fix-Nova-compatibility.patch diff --git a/patches/removed/server/0102-Remove-stream-in-PlacedFeature.patch b/Leaf-archived-patches/removed/legacy/server/0102-Remove-stream-in-PlacedFeature.patch similarity index 100% rename from patches/removed/server/0102-Remove-stream-in-PlacedFeature.patch rename to Leaf-archived-patches/removed/legacy/server/0102-Remove-stream-in-PlacedFeature.patch diff --git a/patches/removed/server/0110-Fix-MC-183518.patch b/Leaf-archived-patches/removed/legacy/server/0110-Fix-MC-183518.patch similarity index 100% rename from patches/removed/server/0110-Fix-MC-183518.patch rename to Leaf-archived-patches/removed/legacy/server/0110-Fix-MC-183518.patch diff --git a/patches/removed/server/0121-Paper-PR-Reduce-work-done-in-CraftMapCanvas.drawImag.patch b/Leaf-archived-patches/removed/legacy/server/0121-Paper-PR-Reduce-work-done-in-CraftMapCanvas.drawImag.patch similarity index 100% rename from patches/removed/server/0121-Paper-PR-Reduce-work-done-in-CraftMapCanvas.drawImag.patch rename to Leaf-archived-patches/removed/legacy/server/0121-Paper-PR-Reduce-work-done-in-CraftMapCanvas.drawImag.patch diff --git a/patches/removed/server/0125-Paper-Improve-performance-of-RecipeManager-removeRec.patch b/Leaf-archived-patches/removed/legacy/server/0125-Paper-Improve-performance-of-RecipeManager-removeRec.patch similarity index 100% rename from patches/removed/server/0125-Paper-Improve-performance-of-RecipeManager-removeRec.patch rename to Leaf-archived-patches/removed/legacy/server/0125-Paper-Improve-performance-of-RecipeManager-removeRec.patch diff --git a/patches/removed/server/0126-Paper-Fix-move-to-jline-terminal-ffm-on-java-22-and-.patch b/Leaf-archived-patches/removed/legacy/server/0126-Paper-Fix-move-to-jline-terminal-ffm-on-java-22-and-.patch similarity index 100% rename from patches/removed/server/0126-Paper-Fix-move-to-jline-terminal-ffm-on-java-22-and-.patch rename to Leaf-archived-patches/removed/legacy/server/0126-Paper-Fix-move-to-jline-terminal-ffm-on-java-22-and-.patch diff --git a/patches/removed/server/0151-Better-inline-world-height.patch b/Leaf-archived-patches/removed/legacy/server/0151-Better-inline-world-height.patch similarity index 100% rename from patches/removed/server/0151-Better-inline-world-height.patch rename to Leaf-archived-patches/removed/legacy/server/0151-Better-inline-world-height.patch diff --git a/Leaf-archived-patches/ver/README.md b/Leaf-archived-patches/ver/README.md new file mode 100644 index 00000000..79719212 --- /dev/null +++ b/Leaf-archived-patches/ver/README.md @@ -0,0 +1,5 @@ +# Note + +If you want to check those patches under api or server folder of a previous MC version. + +You can switch git branch to look into them. diff --git a/patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/Leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch similarity index 100% rename from patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch rename to Leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch diff --git a/patches/work/server/0042-Optimize-Minecart-collisions.patch b/Leaf-archived-patches/work/server/0042-Optimize-Minecart-collisions.patch similarity index 100% rename from patches/work/server/0042-Optimize-Minecart-collisions.patch rename to Leaf-archived-patches/work/server/0042-Optimize-Minecart-collisions.patch diff --git a/patches/work/server/0044-Faster-Natural-Spawning.patch b/Leaf-archived-patches/work/server/0044-Faster-Natural-Spawning.patch similarity index 100% rename from patches/work/server/0044-Faster-Natural-Spawning.patch rename to Leaf-archived-patches/work/server/0044-Faster-Natural-Spawning.patch diff --git a/patches/work/server/0061-Moonrise-Optimize-nearby-players-for-spawning-iterat.patch b/Leaf-archived-patches/work/server/0061-Moonrise-Optimize-nearby-players-for-spawning-iterat.patch similarity index 100% rename from patches/work/server/0061-Moonrise-Optimize-nearby-players-for-spawning-iterat.patch rename to Leaf-archived-patches/work/server/0061-Moonrise-Optimize-nearby-players-for-spawning-iterat.patch diff --git a/patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch b/Leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch similarity index 100% rename from patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch rename to Leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch diff --git a/patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch b/Leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch similarity index 100% rename from patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch rename to Leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch diff --git a/patches/work/server/0112-Reduce-object-complexity-to-make-block-isValid-calls.patch b/Leaf-archived-patches/work/server/0112-Reduce-object-complexity-to-make-block-isValid-calls.patch similarity index 100% rename from patches/work/server/0112-Reduce-object-complexity-to-make-block-isValid-calls.patch rename to Leaf-archived-patches/work/server/0112-Reduce-object-complexity-to-make-block-isValid-calls.patch diff --git a/patches/work/server/0122-Paper-PR-Throttle-failed-spawn-attempts.patch b/Leaf-archived-patches/work/server/0122-Paper-PR-Throttle-failed-spawn-attempts.patch similarity index 100% rename from patches/work/server/0122-Paper-PR-Throttle-failed-spawn-attempts.patch rename to Leaf-archived-patches/work/server/0122-Paper-PR-Throttle-failed-spawn-attempts.patch diff --git a/patches/work/server/0143-Fix-wrong-entity-behavior-in-fluid-caused-by-inconsi.patch b/Leaf-archived-patches/work/server/0143-Fix-wrong-entity-behavior-in-fluid-caused-by-inconsi.patch similarity index 100% rename from patches/work/server/0143-Fix-wrong-entity-behavior-in-fluid-caused-by-inconsi.patch rename to Leaf-archived-patches/work/server/0143-Fix-wrong-entity-behavior-in-fluid-caused-by-inconsi.patch diff --git a/patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch b/Leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch similarity index 100% rename from patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch rename to Leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch diff --git a/README.md b/README.md index 890fdd88..750be65d 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@

[![Github Releases](https://img.shields.io/badge/Download-Releases-blue?&style=for-the-badge&colorA=19201a&colorB=298046)](https://github.com/Winds-Studio/Leaf/releases)⠀ -[![Github Actions Build](https://img.shields.io/github/actions/workflow/status/Winds-Studio/Leaf/build-1213.yml?&style=for-the-badge&colorA=19201a&colorB=298046)](https://github.com/Winds-Studio/Leaf/actions)⠀ +[![Github Actions Build](https://img.shields.io/github/actions/workflow/status/Winds-Studio/Leaf/build-1214.yml?&style=for-the-badge&colorA=19201a&colorB=298046)](https://github.com/Winds-Studio/Leaf/actions)⠀ [![Discord](https://img.shields.io/discord/1145991395388162119?label=discord&style=for-the-badge&colorA=19201a&colorB=298046)](https://discord.gg/gfgAwdSEuM) [![Docs](https://img.shields.io/badge/Docs-docs.leafmc.one-blue?label=docs&style=for-the-badge&colorA=19201a&colorB=298046)](https://docs.leafmc.one) -**Leaf** is a drop-in replacement for [Paper](https://papermc.io/) servers designed to remove some checks, customized and high-performance, built on top of [Gale](https://github.com/Dreeam-qwq/Gale) with optimizations and fixes from other forks. +**Leaf** is a [Paper](https://papermc.io/) fork designed to customized and high-performance, built on top of [Gale](https://github.com/Dreeam-qwq/Gale) with optimizations and fixes from other forks.
> [!WARNING] > Leaf is an **EXPERIMENTAL** fork of [Paper](https://papermc.io/) there MAY BE issues depending on server to server, test and backup servers before switching to it. ## 🍃 Features - - **Fork of [Gale](https://github.com/Dreeam-qwq/Gale)** for better performance + - **Based on [Gale](https://github.com/Dreeam-qwq/Gale)** for better performance - **Async** pathfinding, mob spawning and entity tracker - **Various optimizations** blending from [other forks](https://github.com/Winds-Studio/Leaf#-credits) - **Fully compatible** with Bukkit, Spigot and Paper plugins @@ -39,7 +39,7 @@ If you love my work, feel free to donate :) - afdian: https://afdian.com/a/Dreeam ## 📥 Download -You can find latest successful build in [GitHub Action](https://github.com/Winds-Studio/Leaf/actions) or [Releases](https://github.com/Winds-Studio/Leaf/releases) +You can find the latest successful build in [GitHub Action](https://github.com/Winds-Studio/Leaf/actions) or [Releases](https://github.com/Winds-Studio/Leaf/releases) **Please note Java >= 21 is required.** @@ -49,7 +49,7 @@ Documentation on how to use/configure Leaf: [docs.leafmc.one](https://docs.leafm ## 📦 Building Building a Paperclip JAR for distribution: ```bash -./gradlew applyPatches && ./gradlew createMojmapPaperclipJar +./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar ``` ## 🧪 API @@ -65,7 +65,7 @@ Building a Paperclip JAR for distribution: cn.dreeam.leaf leaf-api - 1.21.3-R0.1-SNAPSHOT + 1.21.4-R0.1-SNAPSHOT provided ``` @@ -78,7 +78,7 @@ repositories { } dependencies { - compileOnly("cn.dreeam.leaf:leaf-api:1.21.3-R0.1-SNAPSHOT") + compileOnly("cn.dreeam.leaf:leaf-api:1.21.4-R0.1-SNAPSHOT") } java { diff --git a/build-data/dev-imports.txt b/build-data/dev-imports.txt index 302359e8..1d9862a9 100644 --- a/build-data/dev-imports.txt +++ b/build-data/dev-imports.txt @@ -12,4 +12,3 @@ # mc_data chat_type/chat.json # mc_data dimension_type/overworld.json # - diff --git a/build.gradle.kts b/build.gradle.kts index f409971e..03f195eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,41 +1,39 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + plugins { - java `maven-publish` - id("io.papermc.paperweight.patcher") version "1.7.8-SNAPSHOT" + id("io.papermc.paperweight.patcher") version "2.0.0-beta.14" } val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/" val leafMavenPublicUrl = "https://maven.nostal.ink/repository/maven-snapshots/" -repositories { - mavenCentral() - maven(paperMavenPublicUrl) { - content { onlyForConfigurations(configurations.paperclip.name) } - } - maven(leafMavenPublicUrl) // Quantumleaper -} - -dependencies { - remapper("net.fabricmc:tiny-remapper:0.10.4:fat") - decompiler("org.vineflower:vineflower:1.10.1") - paperclip("cn.dreeam:quantumleaper:1.0.0-SNAPSHOT") -} - -allprojects { - apply(plugin = "java") +subprojects { + apply(plugin = "java-library") apply(plugin = "maven-publish") - java { + extensions.configure { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion = JavaLanguageVersion.of(21) } } -} -subprojects { + repositories { + mavenCentral() + maven(paperMavenPublicUrl) + maven(leafMavenPublicUrl) + maven("https://ci.pluginwiki.us/plugin/repository/everything/") // Leaf Config - ConfigurationMaster-API + } + + tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + } tasks.withType { options.encoding = Charsets.UTF_8.name() - options.release.set(21) + options.release = 21 + options.isFork = true } tasks.withType { options.encoding = Charsets.UTF_8.name() @@ -43,75 +41,51 @@ subprojects { tasks.withType { filteringCharset = Charsets.UTF_8.name() } + tasks.withType { + testLogging { + showStackTraces = true + exceptionFormat = TestExceptionFormat.FULL + events(TestLogEvent.STANDARD_OUT) + } + } - repositories { - mavenCentral() - maven(paperMavenPublicUrl) - maven("https://ci.pluginwiki.us/plugin/repository/everything/") // Leaf Config - ConfigurationMaster-API + extensions.configure { + repositories { + maven(leafMavenPublicUrl) { + name = "leaf" + + credentials.username = "dreeam" + credentials.password = "dreeam123" + } + } } } paperweight { - serverProject.set(project(":leaf-server")) + upstreams.register("gale") { + repo = github("Dreeam-qwq", "Gale") + ref = providers.gradleProperty("galeCommit") - remapRepo.set(paperMavenPublicUrl) - decompileRepo.set(paperMavenPublicUrl) - - useStandardUpstream("Gale") { - url.set(github("Dreeam-qwq", "Gale")) - ref.set(providers.gradleProperty("galeCommit")) - - withStandardPatcher { - apiSourceDirPath.set("gale-api") - serverSourceDirPath.set("gale-server") - - apiPatchDir.set(layout.projectDirectory.dir("patches/api")) - apiOutputDir.set(layout.projectDirectory.dir("Leaf-API")) - - serverPatchDir.set(layout.projectDirectory.dir("patches/server")) - serverOutputDir.set(layout.projectDirectory.dir("Leaf-Server")) + patchFile { + path = "gale-server/build.gradle.kts" + outputFile = file("leaf-server/build.gradle.kts") + patchFile = file("leaf-server/build.gradle.kts.patch") } - - patchTasks.register("generatedApi") { - isBareDirectory = true - upstreamDirPath = "paper-api-generator/generated" - patchDir = layout.projectDirectory.dir("patches/generated-api") - outputDir = layout.projectDirectory.dir("paper-api-generator/generated") + patchFile { + path = "gale-api/build.gradle.kts" + outputFile = file("leaf-api/build.gradle.kts") + patchFile = file("leaf-api/build.gradle.kts.patch") + } + patchRepo("paperApi") { + upstreamPath = "paper-api" + patchesDir = file("leaf-api/paper-patches") + outputDir = file("paper-api") + } + patchDir("galeApi") { + upstreamPath = "gale-api" + excludes = listOf("build.gradle.kts", "build.gradle.kts.patch", "paper-patches") + patchesDir = file("leaf-api/gale-patches") + outputDir = file("gale-api") } } } - -tasks.generateDevelopmentBundle { - apiCoordinates = "cn.dreeam.leaf:leaf-api" - libraryRepositories.set( - listOf( - "https://repo.maven.apache.org/maven2/", - paperMavenPublicUrl, - leafMavenPublicUrl - ) - ) -} - -publishing { - if (project.providers.gradleProperty("publishDevBundle").isPresent) { - publications.create("devBundle") { - artifact(tasks.generateDevelopmentBundle) { - artifactId = "dev-bundle" - } - } - } -} - -allprojects { - publishing { - repositories { - maven { - name = "leaf" - url = uri(leafMavenPublicUrl) - - credentials.username = System.getenv("REPO_USER") - credentials.password = System.getenv("REPO_PASSWORD") - } - } - } -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bc191f44..b4929218 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,11 @@ -group = cn.dreeam.leaf -mcVersion = 1.21.3 -version = 1.21.3-R0.1-SNAPSHOT +group=cn.dreeam.leaf +mcVersion=1.21.4 +version=1.21.4-R0.1-SNAPSHOT -galeCommit = f2c8aafc2707715a1b68c95d6f179509ab1640ba +galeCommit=11e614cd6079f8485dbee0881eb4e56ed04f7f35 -org.gradle.caching = true -org.gradle.parallel = true -org.gradle.vfs.watch = false -org.gradle.jvmargs = -Xmx4G \ No newline at end of file +org.gradle.configuration-cache=true +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.vfs.watch=false +org.gradle.jvmargs=-Xmx4G diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72b..cea7a793 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/patches/api/0006-Bump-Dependencies.patch b/patches/api/0006-Bump-Dependencies.patch deleted file mode 100644 index 1a37b3e5..00000000 --- a/patches/api/0006-Bump-Dependencies.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Fri, 28 Oct 2022 17:24:16 -0400 -Subject: [PATCH] Bump Dependencies - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 5fac022f2373105df7f9cfb292642c4a399c7db4..4cc608d0a5dc80e2ef492756c7637644fd4a3821 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -9,11 +9,13 @@ java { - withJavadocJar() - } - --val annotationsVersion = "24.1.0" -+// Leaf start - Bump Dependencies -+val annotationsVersion = "26.0.1" - val bungeeCordChatVersion = "1.20-R0.2" --val adventureVersion = "4.17.0" --val slf4jVersion = "2.0.9" --val log4jVersion = "2.17.1" -+val adventureVersion = "4.18.0" -+val slf4jVersion = "2.0.16" -+val log4jVersion = "2.24.3" -+// Leaf end - Bump Dependencies - val apiAndDocs: Configuration by configurations.creating { - attributes { - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION)) -@@ -41,20 +43,24 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { - dependencies { - api("com.mojang:brigadier:1.2.9") // Paper - Brigadier command api - // api dependencies are listed transitively to API consumers -- api("com.google.guava:guava:32.1.2-jre") -- api("com.google.code.gson:gson:2.10.1") -+ api("com.google.guava:guava:33.4.0-jre") // Leaf - Bump Dependencies -+ api("com.google.code.gson:gson:2.11.0") // Leaf - Bump Dependencies - // Paper start - adventure - api("net.md-5:bungeecord-chat:$bungeeCordChatVersion-deprecated+build.18") { - exclude("com.google.guava", "guava") - } - // Paper - adventure -- api("org.yaml:snakeyaml:2.2") -- api("org.joml:joml:1.10.5") -+ api("org.yaml:snakeyaml:2.3") -+ // Leaf start - Bump Dependencies -+ api("org.joml:joml:1.10.8") { -+ isTransitive = false // https://github.com/JOML-CI/JOML/issues/352 -+ } -+ // Leaf end - Bump Dependencies - // Paper start - api("com.googlecode.json-simple:json-simple:1.1.1") { - isTransitive = false // includes junit - } -- api("it.unimi.dsi:fastutil:8.5.6") -+ api("it.unimi.dsi:fastutil:8.5.15") // Leaf - Bump Dependencies - apiAndDocs(platform("net.kyori:adventure-bom:$adventureVersion")) - apiAndDocs("net.kyori:adventure-api") - apiAndDocs("net.kyori:adventure-text-minimessage") -@@ -64,33 +70,40 @@ dependencies { - apiAndDocs("net.kyori:adventure-text-logger-slf4j") - api("org.apache.logging.log4j:log4j-api:$log4jVersion") - api("org.slf4j:slf4j-api:$slf4jVersion") -- api("io.sentry:sentry:5.4.0") // Pufferfish -+ api("io.sentry:sentry:8.0.0-rc.3") // Pufferfish // Leaf - Bump Dependencies - - implementation("org.ow2.asm:asm:9.7.1") - implementation("org.ow2.asm:asm-commons:9.7.1") - // Paper end - -- api("org.apache.maven:maven-resolver-provider:3.9.6") // Paper - make API dependency for Paper Plugins -- compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") -- compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") -+ // Leaf start - Bump Dependencies -+ api("org.apache.maven:maven-resolver-provider:3.9.9") // Paper - make API dependency for Paper Plugins -+ compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.22") // Dreeam TODO - Update to 2.0.1 -+ compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.22") // Dreeam TODO - Update to 2.0.1 -+ // Leaf end - Bump Dependencies - - val annotations = "org.jetbrains:annotations:$annotationsVersion" // Paper - we don't want Java 5 annotations... - compileOnly(annotations) - testCompileOnly(annotations) - - // Paper start - add checker -- val checkerQual = "org.checkerframework:checker-qual:3.33.0" -+ val checkerQual = "org.checkerframework:checker-qual:3.48.4" // Leaf - Bump Dependencies - compileOnlyApi(checkerQual) - testCompileOnly(checkerQual) - // Paper end - api("org.jspecify:jspecify:1.0.0") // Paper - add jspecify - -- testImplementation("org.apache.commons:commons-lang3:3.12.0") -- testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") -- testImplementation("org.hamcrest:hamcrest:2.2") -- testImplementation("org.mockito:mockito-core:5.14.1") -+ testImplementation("org.apache.commons:commons-lang3:3.17.0") -+ testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") -+ testImplementation("org.hamcrest:hamcrest:3.0") -+ testImplementation("org.mockito:mockito-core:5.15.2") - testImplementation("org.ow2.asm:asm-tree:9.7.1") -- mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions -+ mockitoAgent("org.mockito:mockito-core:5.15.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions -+ -+ // commons-lang3 is removed in maven-resolver-provider since 3.9.8 -+ // Add this because bukkit api still need it. -+ compileOnly("org.apache.commons:commons-lang3:3.17.0") -+ // Leaf end - Bump Dependencies - } - - // Paper start -@@ -182,13 +195,13 @@ tasks.withType { - options.use() - options.isDocFilesSubDirs = true - options.links( -- "https://guava.dev/releases/32.1.2-jre/api/docs/", -- "https://javadoc.io/doc/org.yaml/snakeyaml/2.2/", -+ "https://guava.dev/releases/33.4.0-jre/api/docs/", // Leaf - Bump Dependencies -+ "https://javadoc.io/doc/org.yaml/snakeyaml/2.3/", // Leaf - Bump Dependencies - "https://javadoc.io/doc/org.jetbrains/annotations/$annotationsVersion/", // Paper - we don't want Java 5 annotations - // "https://javadoc.io/doc/net.md-5/bungeecord-chat/$bungeeCordChatVersion/", // Paper - don't link to bungee chat - // Paper start - add missing javadoc links -- "https://javadoc.io/doc/org.joml/joml/1.10.5/index.html", -- "https://www.javadoc.io/doc/com.google.code.gson/gson/2.10.1", -+ "https://javadoc.io/doc/org.joml/joml/latest/index.html", // Leaf - Bump Dependencies -+ "https://www.javadoc.io/doc/com.google.code.gson/gson/2.11.0", - "https://jspecify.dev/docs/api/", - // Paper end - // Paper start -@@ -200,9 +213,9 @@ tasks.withType { - "https://jd.advntr.dev/text-serializer-plain/$adventureVersion/", - "https://jd.advntr.dev/text-logger-slf4j/$adventureVersion/", - "https://javadoc.io/doc/org.slf4j/slf4j-api/$slf4jVersion/", -- "https://javadoc.io/doc/org.apache.logging.log4j/log4j-api/$log4jVersion/", -+ "https://javadoc.io/doc/org.apache.logging.log4j/log4j-api/2.20.0", // Leaf - Bump Dependencies - // Paper end -- "https://javadoc.io/doc/org.apache.maven.resolver/maven-resolver-api/1.7.3", // Paper -+ "https://javadoc.io/doc/org.apache.maven.resolver/maven-resolver-api/1.9.22", // Paper // Leaf - Bump Dependencies - ) - options.tags("apiNote:a:API Note:") - -@@ -250,6 +263,11 @@ val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks. - jarToScan.set(tasks.jar.flatMap { it.archiveFile }) - classpath.from(configurations.compileClasspath) - } -+// Leaf start - Bump Dependencies -+repositories { -+ mavenCentral() -+} -+// Leaf end - Bump Dependencies - tasks.check { - dependsOn(scanJar) - } diff --git a/patches/server/0001-Rebrand.patch b/patches/server/0001-Rebrand.patch deleted file mode 100644 index e5791159..00000000 --- a/patches/server/0001-Rebrand.patch +++ /dev/null @@ -1,857 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Encode42 -Date: Thu, 16 Sep 2021 20:39:45 -0400 -Subject: [PATCH] Rebrand - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 579877ba9bf17f40313c05ad5b1b219cf4b59ab3..a32417619cf253f25f060266a36e895c2e8b623e 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -25,7 +25,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { - // Paper end - configure mockito agent that is needed in newer java versions - - dependencies { -- implementation(project(":gale-api")) // Gale start - project setup - Depend on own API -+ implementation(project(":leaf-api")) // Gale start - project setup - Depend on own API // Leaf - // Paper start - implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ - implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 -@@ -109,14 +109,14 @@ tasks.jar { - val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper - attributes( - "Main-Class" to "org.bukkit.craftbukkit.Main", -- "Implementation-Title" to "Gale", // Gale - branding changes -+ "Implementation-Title" to "Leaf", // Gale - branding changes // Leaf - "Implementation-Version" to implementationVersion, - "Implementation-Vendor" to date, // Paper -- "Specification-Title" to "Gale", // Gale - branding changes -+ "Specification-Title" to "Leaf", // Gale - branding changes // Leaf - "Specification-Version" to project.version, -- "Specification-Vendor" to "GaleMC Team", // Gale - branding changes -- "Brand-Id" to "galemc:gale", // Gale - branding changes -- "Brand-Name" to "Gale", // Gale - branding changes -+ "Specification-Vendor" to "Winds Studio", // Gale - branding changes // Leaf -+ "Brand-Id" to "winds-studio:leaf", // Gale - branding changes // Leaf -+ "Brand-Name" to "Leaf", // Gale - branding changes // Leaf - "Build-Number" to (build ?: ""), - "Build-Time" to Instant.now().toString(), - "Git-Branch" to gitBranch, // Paper -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index f21106b0897dd6e373970f32f5dd269418528977..6d8634caade010a31a8e1c59684e9bb0e5546380 100644 ---- a/src/main/java/com/destroystokyo/paper/Metrics.java -+++ b/src/main/java/com/destroystokyo/paper/Metrics.java -@@ -593,7 +593,7 @@ public class Metrics { - boolean logFailedRequests = config.getBoolean("logFailedRequests", false); - // Only start Metrics, if it's enabled in the config - if (config.getBoolean("enabled", true)) { -- Metrics metrics = new Metrics("Gale", serverUUID, logFailedRequests, Bukkit.getLogger()); // Gale - branding changes - metrics -+ Metrics metrics = new Metrics("Leaf", serverUUID, logFailedRequests, Bukkit.getLogger()); // Gale - branding changes - metrics // Leaf - - metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { - String minecraftVersion = Bukkit.getVersion(); -@@ -603,15 +603,15 @@ public class Metrics { - - metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); - metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); -- final String galeVersion; // Gale - branding changes - metrics -+ final String leafVersion; // Gale - branding changes - metrics // Leaf - final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion(); - if (implVersion != null) { - final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1); -- galeVersion = "git-Gale-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Gale - branding changes - metrics -+ leafVersion = "git-Leaf-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Gale - branding changes - metrics // Leaf - } else { -- galeVersion = "unknown"; // Gale - branding changes - metrics -+ leafVersion = "unknown"; // Gale - branding changes - metrics // Leaf - } -- metrics.addCustomChart(new Metrics.SimplePie("gale_version", () -> galeVersion)); // Gale - branding changes - metrics -+ metrics.addCustomChart(new Metrics.SimplePie("leaf_version", () -> leafVersion)); // Gale - branding changes - metrics // Leaf - - metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { - Map> map = new HashMap<>(2); // Gale - metrics - reduce HashMap capacity -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index 2beea17d87464683faaefa835206f2654df9bde0..c066586ce43b3ae5c64d52a3a93c8c8ee883b499 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -40,12 +40,12 @@ public class PaperVersionFetcher extends org.galemc.gale.version.AbstractPaperVe - - public PaperVersionFetcher() { - super( -- "master", -+ "ver/1.21.3", // Leaf - "https://papermc.io/downloads/paper", - "PaperMC", - "Paper", - "PaperMC", -- "Paper" -+ "Paper-archive" // Leaf - ); - } - -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -index b78828e83d8128eace986aeb73213da3b3f905e4..9869bec65f4d0fbd5ed5aff896a8956e7ea2747f 100644 ---- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -20,7 +20,7 @@ public final class PaperConsole extends SimpleTerminalConsole { - @Override - protected LineReader buildReader(LineReaderBuilder builder) { - builder -- .appName("Gale") // Gale - branding changes -+ .appName("Leaf") // Gale - branding changes // Leaf - .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) - .completer(new ConsoleCommandCompleter(this.server)) - .option(LineReader.Option.COMPLETE_IN_WORD, true); -diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -index 2596e0ee4df5b96f181e28a742ef345981fc97e3..00470a690b4b0fc8996a03ecd21af8163094184d 100644 ---- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -+++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -@@ -32,6 +32,7 @@ public record ServerBuildInfoImpl( - - private static final String BRAND_PAPER_NAME = "Paper"; - private static final String BRAND_GALE_NAME = "Gale"; // Gale - branding changes -+ private static final String BRAND_LEAF_NAME = "Leaf"; // Leaf - - private static final String BUILD_DEV = "DEV"; - -@@ -43,9 +44,9 @@ public record ServerBuildInfoImpl( - this( - getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) - .map(Key::key) -- .orElse(BRAND_GALE_ID), // Gale - branding changes -+ .orElse(BRAND_LEAF_ID), // Gale - branding changes // Leaf - getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) -- .orElse(BRAND_GALE_NAME), // Gale - branding changes -+ .orElse(BRAND_LEAF_NAME), // Gale - branding changes // Leaf - SharedConstants.getCurrentVersion().getId(), - SharedConstants.getCurrentVersion().getName(), - getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) -@@ -62,7 +63,9 @@ public record ServerBuildInfoImpl( - - @Override - public boolean isBrandCompatible(final @NotNull Key brandId) { -- return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID); // Gale - branding changes -+ return brandId.equals(this.brandId) -+ || brandId.equals(BRAND_PAPER_ID) -+ || brandId.equals(BRAND_GALE_ID); // Gale - branding changes // Leaf - } - - @Override -diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java -index b24265573fdef5d9a964bcd76146f34542c420cf..aa25fd3ee043003f359b2c67a6d0f6700a0ef893 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 boolean trackingStackTrace = true; - private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; - private final SystemReport systemReport = new SystemReport(); -+ private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER OR GALE! REPORT TO LEAF INSTEAD!", ""); // Leaf - Purpur - - public CrashReport(String message, Throwable cause) { - io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper -@@ -144,7 +145,7 @@ public class CrashReport { - } - - public String getFriendlyReport(ReportType type) { -- return this.getFriendlyReport(type, List.of()); -+ return this.getFriendlyReport(type, extraInfo); // Leaf - Purpur - } - - @Nullable -@@ -191,7 +192,7 @@ public class CrashReport { - } - - public boolean saveToFile(Path path, ReportType type) { -- return this.saveToFile(path, type, List.of()); -+ return this.saveToFile(path, type, extraInfo); // Leaf - Purpur - } - - public SystemReport getSystemReport() { -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index 4d037e899e0b5548be406ad55acd2062603b7da1..379b36944ddb149e2f48221aa39ad090bbd2faeb 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -67,7 +67,7 @@ public class DamageSource { - - public DamageSource customEventDamager(Entity entity) { - if (this.directEntity != null) { -- throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper, if you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues)"); // Gale - branding changes -+ throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper, if you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues)"); // Gale - branding changes // Leaf - } - DamageSource damageSource = this.cloneInstance(); - damageSource.customEventDamager = entity; -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 cbeebb98622025d35557125e784cd8f0b4fbb751..79aca7e7cd2f860464657e77e935391642981fad 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 -@@ -294,7 +294,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - We do not want people to report thread issues to Paper, - but we do want people to report thread issues to Gale. - */ -- org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER OR GALE - You may ask for help on Discord, but do not file an issue. These error messages can not be removed. - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues)"); -+ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER OR LEAF - You may ask for help on Discord, but do not file an issue. These error messages can not be removed. - If you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues)"); // Leaf - // Gale end - branding changes - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index f6bc955c3496b52cda1a20aabd78769803ef471f..bbeb271fb19091897e99970198a8a110a4aa0858 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -491,7 +491,7 @@ public class CraftScheduler implements BukkitScheduler { - this.parsePending(); - } else { - // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper -- task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Gale"); // Paper // Gale - branding changes -+ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Leaf"); // Paper // Gale - branding changes // Leaf - // We don't need to parse pending - // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index e57be8cbe35fdc2ae41b3a0278d244672a303059..6f9aecffd821e041f8edc8b8af419b2617c297fc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -505,7 +505,7 @@ public final class CraftMagicNumbers implements UnsafeValues { - // Paper start - @Override - public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { -- return new org.galemc.gale.version.GaleVersionFetcher(); // Gale - branding changes - version fetcher -+ return new org.dreeam.leaf.version.LeafVersionFetcher(); // Gale - branding changes - version fetcher // Leaf - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -index 0b5979723bb30f9011ac64c36d894aa41713ec9b..e220f5601f6b92b7b280ce8ebe64117d30192b0e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -@@ -11,7 +11,7 @@ public final class Versioning { - public static String getBukkitVersion() { - String result = "Unknown-Version"; - -- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.galemc.gale/gale-api/pom.properties"); // Gale - branding changes -+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/cn.dreeam.leaf/leaf-api/pom.properties"); // Gale - branding changes // Leaf - Properties properties = new Properties(); - - if (stream != null) { -diff --git a/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java b/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a02577f08929747ea6ee964ca7289508499e1a3d ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java -@@ -0,0 +1,17 @@ -+package org.dreeam.leaf.version; -+ -+import org.galemc.gale.version.AbstractPaperVersionFetcher; -+ -+public class LeafVersionFetcher extends AbstractPaperVersionFetcher { -+ -+ public LeafVersionFetcher() { -+ super( -+ "ver/1.21.3", -+ "https://github.com/Winds-Studio/Leaf", -+ "Winds-Studio", -+ "Leaf", -+ "Winds-Studio", -+ "Leaf" -+ ); -+ } -+} -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 08fa6018bcab38d8c9ca05c84e229d69184ce01b..f9351c15c8c6e2f4cbd2af0a15bdd5e1a90cb262 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -96,7 +96,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - - private WatchdogThread(long timeoutTime, boolean restart) - { -- super( "Paper Watchdog Thread" ); -+ super( "Watchdog Thread" ); // Leaf - Purpur - use a generic name - this.timeoutTime = timeoutTime; - this.restart = restart; - earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper -@@ -160,15 +160,15 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - We do not want people to report thread issues to Paper, - but we do want people to report thread issues to Gale. - */ -- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug. This could be a Gale bug." ); // Paper -+ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug. This could be a Leaf bug." ); // Paper // Leaf - // Gale end - branding changes - log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); - log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); - log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); - log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); -- log.log( Level.SEVERE, "If you are unsure or think this is a Gale bug, please report this to https://github.com/GaleMC/Gale/issues - and if you think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); // Gale - branding changes -+ log.log( Level.SEVERE, "If you are unsure or think this is a Leaf bug, please report this to https://github.com/Winds-Studio/Leaf/issues - and if you think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); // Gale - branding changes // Leaf - log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); -- log.log( Level.SEVERE, "Gale version: " + Bukkit.getServer().getVersion() ); // Gale - branding changes -+ log.log( Level.SEVERE, "Leaf version: " + Bukkit.getServer().getVersion() ); // Gale - branding changes // Leaf - // - if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) - { -@@ -195,13 +195,13 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - We do not want people to report thread issues to Paper, - but we do want people to report thread issues to Gale. - */ -- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues - THIS IS NOT A PAPER BUG OR CRASH - " + Bukkit.getServer().getVersion() + " ---"); -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues - THIS IS NOT A PAPER BUG OR CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Leaf - // Gale end - branding changes - log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); - } - // Paper end - Different message for short timeout - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Gale!):" ); // Paper // Gale - branding changes -+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Leaf!):" ); // Paper // Gale - branding changes // Leaf - ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - rewrite chunk system - this.dumpTickingInfo(); // Paper - log detailed tick information - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); -@@ -222,7 +222,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - We do not want people to report thread issues to Paper, - but we do want people to report thread issues to Gale. - */ -- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Gale bug, please report it at https://github.com/GaleMC/Gale/issues - THIS IS NOT A PAPER BUG OR CRASH ---"); -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - If you think this is a Leaf bug, please report it at https://github.com/Winds-Studio/Leaf/issues - THIS IS NOT A PAPER BUG OR CRASH ---"); // Leaf - // Gale end - branding changes - } - -diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png -index e7e9fd9a6077535b89c6c9d7b0164e8b87c54bed..9e698edad90ebcd641fc59ade84643556b427070 100644 -GIT binary patch -literal 19657 -zcmbTdWmFtZ)HOPTyE}sf_W;42;I4zaI|TP2!QI{6-EDvbm!QEJf;&Nj=JLGvd)Ie= -z-yhw*x@&cHRh>F}?{lg;N>y0~9fbr1005xN$x5ofU&sDCkPzO_bvpI=?-w|0aV2p8 -zpeX_M#S|U@K#{YRkWh6~m-$R7Cndqd!Oz3P%E`t70LVt=X?W?RJP?R%KUiXFeO|e0 -z%5VqKY#x9+ne -zm>jZ({{aA8!TNXyx;Kix02`bEFaVb&HX5AKoqwU-s&v`_$|L}k3g-|Nn5YgIW78bN -z1B?*?8Z}xhPynd_fP;_!XDUE7I^gV9P6QsXQkAkn2v{iwRUrZ5fPgFp85$U|9)Jlr -zSe*q1G6X2B6{BKkVJ{W)mIqozpr6O!0 -z`}_d3%Gf2WQx*t-Or}j|D9nGt>rDGe;&pum{r%zhkVDgFQ$j{8E95<@+>9J*T-ifd -z#ThgJfaGy*@QqJ2@$g`8=fLXF;qvxJ@tggXGnL4PKihA;u`qA|EalnDHRJvLWmuO{ -zV57%?eU(07O$%VN{b=}U9l1~s<)`msj1S+77X;B22n7}OoYt(Uk9{{rOSV{69i$}m^^2*KXT5s-;UwH=4yT7h>bKFbudnfpbHBNo3|R1^ -z|L8<@s!}P9`gEx$Nq!hl`;Bw_mGcip`YUpZ7doIeTm_=sj@UNo013PR!o*Ip7i2SXHTuh^0nu;UOZ7I4BS60CJIjgW*ZMrId -z5ve*-pg>Q~vCvcXqees(E|*6(?Q{--#AGRJ$7v) -zCr#j_38ZPI1*RQVYl7XtqDwy2sG2ok%o-!DfO2VYi|TF_@*+XaQWY7vr;4Y3M%_z2 -zL3LKmxTN3sHRQ=0y*WHgx-eX}MAN)uggEX4!3d}2ADz$3u3E6CEcTg7X8O&KXNsQi -z&!nCo{S-AM+^Vf=t&*(r4!%qSF-Dr8SNyBF@-rW1&}aNuVOd34o0jeBtm*>mc-MT_ -z`q#|PUC#;5PuDIzT=K;7Sn}=!90eXckh^)h)w;vF*F9!WL>A0XeU6duChjnfH|J#T -z9B)hR2v07TFXt$D*}0H-5{Vr+_{^`S63vh7Gi^KPuDbLzelPv8*UvbeYM*V@J=eP6J~cJQvQfKIJAM|= -zzm#jAZ`!}wyZ#avRwkAELq3KlW;A^*-G353T`B#cHd9MP%SY=@>sPJZvcW0KspGP? -zgDRt&SZOKT~6J1&m6~Xyx({UjPs0l8cu5*XD??cXW3^6=lu7k_q6wp_p@YE -zARMw~L3craS9gyoPY0LY)6yNT;i~Ud!^hgzq}D}lc{kjI*m6k64Tps<#c!`3bbf!C+RdxTsTjIR -zJp5?nX{2lP+()j`l1rDr%Om{c0-`^RBafW(tcxQevL$R}hKb!ML=;^Z#s8zn8t<*nM;)i0~8{HeLAxh3l8w&|_Rzt}q% -zI#jHfnN!gBd=4BZNK+yv9`>jo-EP+L9l{jnN?O(HRWS$@KUOk(>l5jFcgpt3>MefS -zwt72xq(%)P@b61(RPAhRS?ToF{R5>Mkm@0zj?p0WV&gDW@3!!^y_yf0w3%#9e^vdh -zqYl2d^VztLR}Y;pE0bUj(dJ64N*$kcs=w9iGRHSfvjAV(Wi07yePtp{Gp=>1t*rms -zDBXGRTU*(9$^XvBacyl&zGLG2RQ9y=)T#DCcc4MO&rW>lXZRI*9qFIAeNtmb`7QS% -zU%R{b>DK9qX;^Mk?sNUA_S*I9BA$|a-@LDelUlEJH68t{bZg54n_hl57@=hNf?JoV -zXT5FA-rDwkQUP~P^A{nT2_$Bu-2(@Ko1HRVDnIfQ{L1_g@1-tJ?cALmHW~&7nvosV -zdE&@r1zh%Xf}GcK&a=JRT=bYQhQ#*=>9@=M{dQH4ie`p4hNri)zINOpoyz(!&kGEA -z)UuX1777>jT^_}j1n-8I{lP+}Vjht$#kJZZ(J7ufLxaW_4axu23IiW(Vqhs%~5_YpNOq -zMyW?BdS`ru|CGPf&8!Dpc3yUGkM>byF}#Sqsou;znU7ZdRCy3N^nZK#`0(xKnN@~w -z_FZIBn#-yy0RX;q06@?;0O0BUQP2?p;K2?6oR|Or0+|2+p-XPJn$UXzvWu*)8vuZ5 -z`riSp6s@oY0K`w^B*itobI<$ydL4Da+mB@S1#ZsZOwIMeR*2#@8&aQJe}b@T>a>slCOa3YTI5)tk9Kko<^WR`25MOpSxg-kZnZDBkGF#>swge -zfcw+N*2e@UxrxK0w_pFB3jITY&84(t8oqxjla06Yj(UEzmk;!Ek{~1&wQ4q{yE882 -z1b+qBXf5bDgOD|0|6!Ju!RgMI$@0Ps0D9pHwJbU7oK2dBV0GSmnL!@VR(uI`>hN5$ -zDE<|*dpJ%-s$Z?6(5-0$_YztC<_Gn>G!RSGOZ%hQAij4k#oL`qsnCsg#0Z(KIYIq! -zm#Ab{R)41#%p7zl#nbyhmC*<;ml%KV9bEk{`fw)$jzm@^0RPTRYy0j -z&#aEJCY*Iu_QGn&zdm;3Dg?0{ch0r(J)C1Q`Zg1Sf*4}y -zh{GqAp*NSxpuHDs-O23fw&RYB! -zIG;O>9Rizs-o&dV38%C6-e31)MhJ6|HpA2RGmUAt5DB%`@J-=6l`MmvBju0puMd~z -z1Umza>W@%^QpzI!So_qtLa~DT)G4(8ac&q@ocPr -zwDt?BC$=PsRn~k}Sbf?58-_x6+$(qJBLp=_5MLNRAh`%GKGt8(2;f#kQU-JHnRblF -z^q@GF7DK-AUS)O!IqU|C5c<9@AG<%Xi{XXiFckyJR<%e@$&D_*%ipW}`dLIiWGeTe -zBSEL2*hx-L>;#_=x3T=PReXfbpx#XT;>>tvYZNMk5M3R!xm9&eR1b0F -zyI{4DexDV*wZUbel6_~&*_5>5i2mc0+;XLUkxOK``-ZGtrk>TN=2-@xJ>caEQ@&dJ^K>c -z0otHiWf>vJ7Y#CK;=({#e0IT2UVBj%2PSwy8O5QDuOIR;%ZC*r?3CEy`H8!!bnKLy -z5aq0g3{txK8xjiYjr*333(CJf&h%BRPiFr058>vlbmU&yQCJ5`&|VYd#u7F>k@jCI-Kb*Ko4Us&mcjd7z!8P^le6mR9iarijfh_ves -z?OcFG*ZCR0LFbs%t3RyMV$E%^=1jYbZ-}| -zN46Bj?jb!1sn#)t98z_<-o#)nwl#N!eMOjDK$%fBCK4tAxT0RELoK64NQw3x5l}G{ -z&s1HA?sjAcO`ly{T)eZn^IH*bf`_*;Gtn`yExpjz1%nY$L82dTq2V%2VoVz|7np#L -z>bKQSQlVSxq-B_hGJ)XfHqTDz&*JGO3KzWRFG;=N)h|d6&c9+ugI{;5l+7^261aj4 -z;!_AjOOG-nST0gBy9Sfw^H-r)T$p?zoJ0hi^&38rx%ycd42*jqM#yzYbH6zAF*=w9 -zT+5ONV^no?YBQ^dvSMZmW#^`Ql!r!UIg3sahm@#JDr!4nY!MQIXq}f>LN|<*DjJ)L -zH1V6F;hj?4P2HtOXAV|2HQK$Jp^eiYzp+^c`NLjef;au9R67R+LY11gm~ytX^Tq0#Un2)U -z!GUyLctgh;#sXt$uY5-~oS)m@aJ+kj3=2Npr0H5vrh*7`kR`L9%G8&6QpfS633oI* -z6tF2ffd5dh=Ccr*4&&#~S-V{Wo?Kk;w6|f|4_RDv#?=vI;x`WfGRRniLZiu+fi76B -zIfxOz4HqxA!drRM2rVlF{)chXq!+Urg;mJ;m6_|(Q8#E%$U5<`j+ -z@ASg}k56bC5#yX=>=03=sX*pvB5tsVujFNjMCz{EBn1@729JC#4=o|Y1?_3Lw!sfL -zdSkeh3YS@4P_hw8U}|Jt%OL(|$Kvbb4dwEQFw_fJzU0TRMDDq?V`-f&x&syFZ^Tns -z@DW*mgP}+h>^U4EdV~n^pIKWBy7r)bUtL<=a+F3F&0X;gx*mO$WfF7Dt5V}UR@Ra? -z4xmMIU*O-{{J`vch~;7v{#e5J^|sUXnII`3i2B$X5Uau^ZMwr@S&D@Hhd$b$* -z>J9>4Q%P+{_+7#c~xu#BH+HAA`dxJWCY#kKlMqxSUtKZV=tv -z7##J?zMIeFA5R2k41>O4V>_KJvoOZCSi~-3qBg83Z0!_myt`Bz6!IMdUWS<^9t2{% -zp@8ALu4^9%BySFQpyg6jdg@Yf0Z?9DN#HL;v6FmhY6OBlQopkDsVr?k8Md|<0{Hk4 -z+C@VjF%aUz<5$v1D=JhtG -zwOpy<{|>gq8pY5{4BTj7FSzTTS&dxalbOIE(1J$^ai2t11|4fKVCLbOjfZcnV$W{! -z!Tdi{{Rnz@9Tw-Mq|nfSkl|Rn@Q&K*)M@mc-VE}Vs&x3TC07hUDo9W8B^eBc%1yUx -zlyj`r#CJ#TX)=A6#L$kc3jV>l8Al#6jfC-~(+Nu_m%S(5Usupj>Q;9oi&_<}Jtaq5C -zRUe!UQ}gg3C}3{yhXvBVRsyaE7@zt~v~$VJL(7;uSO-L}4n{Hy4nJ?h9^@l}=JME~ -zk&WfPbOyzWE)b;k@(%HiE!0{xzs% -z%71uaA+Q{JRU{Q0N~al@8bLwQ)UU;H4Yi?Y&$bOZ1KPK6PD>DKg!ofmeIg`%cMsq7 -zhyTGiM*&th#4m$HzL^uLq5>|Bm@ZgpAEzAMKUT}@>eqN54K8g7C(v}qWV%F=uU%O{ -z`=aY-tY&_M3@>KxF$Ysy^}n)PBfY!GC&Mm&Z({?O)L!tj$G+8b(S_;vJhGkOXDzj% -z3b#Y=JM$EHaszZiL`wQ#FXpF}Rj3US6)MK?QI>m74b4U9TK12#%E6>i2o}~u+x7@6 -zBrS({HJCyO?f0UvJvaGq-$TBM(99~?Y7EHH4cWJ8tNiH3Dp+E=^r2a9S>JNuc_QN6 -zPu}UBZ!88k(Woq$1>e0!*!-g7^#ycz$RBQ<%G@#rdAUr?SbH!RORX9Tc(2fUQp7$j -z#1tY(kpuCzs^_<17c(m-k^PA9b|D0n)n3$vLQ3Cx%EEb^c4uNbm|6#mU}u7CELRH4K2r}y#tGB{F@4^F03W{ofwL!+2J)$!NER{@ -zUpB9dDnea=*SOJJ2r)1+guEaS>>z*2<8zi94`1sjWdQ}~N1zc*y@9ivqJ8J(7I^uW -z7KzJV@EmHS)#OjeMKW+KX7MtWqK&!Z(5h{RACh#Rn%))KX@VxKXgNVa#8ieRsCm0E`E9%yF_hP+T?8f8arvniH*W)(47d-WiVIpjq@FUPwSY1sTpwh_4_59 -z7C7!Q4dAt%0`nKJr*b0iYUk&}PU0lzE1q4}a;P3;Trk@2}= -zHO6XWSM=Za_?%hKl=wynMcVTKd3l&F1|Hyc<^Hv_*Q=WBHC(${H_Wf -z(lN=C(?H?{m?w4FlW94ZhpNtC(?{a+*#fhe5Ip1$MP*LN1WP_W6&zP*7! -zUA8YWmnV*&wfVg~ziEEn&+0-1o!@9@p0a(2ttst!d?&u=dgZ$ns8OGZdG;u>j$^<$SB)cKunjI -zkzo-qn|8?M@NDTHb!v1P)NbwgjQ)@A`2YGO#rzxAfiVpq!5s#Fr%Gv_py2P@FE$GW^H53xlszb16tGk5P%9b -zUPpb;5P~iIJ!G5i+Yi~q)1ZO80!@k(r9A4*w{xA+Ry%|?HX_jXHax>Fx(AjFuMZo% -z{}*-(UbAn*{&HnX@KQ*{f#z_IS>>%mgBlgY&t!M{~`rvZ-737tg -zhZn@I=hBKsE*FXbdV40XYpDjNz^gq|8yZ$kcEC^DVgwTunFVyHbU+yzDoK={R9)OP -zzJPkc2?t(dd5fwuLhQe%Lh(H|0mJOBlZQ#0iLenm$P3oJ&!YLkYqG#wRJ0;0(!=9I -zoV``Zjobe+>vfG?eOusxC<9-sjoTUGyZ0KnJ1;r7P+pV{Pb#d8-{fI-Zt`p6~;DoZ=-@UD*$LE^+{sD{_| -z2LB^*zQ`rYXDLFbvam5l_ew{`a<$VQc(NMXSW78|3;7F*7DmnRS!^3)UZ_6FVoAFN -z8thRrEz3dm*Kgs2ODtOz`*HZp4%RWT|6Ii36%@# -zSGGK#Nf-;{uv{r4GY3q)QA1QWQ3g}0_4}Cr2?shM4 -z@d_h3K!|Exwfq)a2otho>ghGzl~|WG8c-)4gG#)gc%`CaqWmF^UbV)-*z5`oD3h%W -z(P=Lbw<#Cz-@bQzrM>d>=@zj*_&+4zCur`6I3q-pEBcFQM>t!qVqSp&sg6{;^m!Yv -zwK^R>KH-#PJXFdM+B)Tn!ECy1+G>of4m1{B*H43$VbBc^Dd~J758}5D9+pq%)&^?In@!(|(wEXyRP -z9@N@K`p0x(?FdoRqh%oDx#SgQuc}(8@8O>3ffGK$aL$!csf+qS>+!a=;3lM`jxD2M -z^x*0y@!Ssq)bGr`p-=l8($ofPJ*$-~`caWC9w-CbNu7wmHUj!)QyMADEGPdb&k-kt -zl6|?PowU1V_7hm;;cNN^NaY40!&u^mwvv;tyK@J6q%|cKTyp-;0Q!?W)Iab&Q1X$i -zH?THl&>J2>5b{^n(Z?;^A(fy|_d9Yelq#+5ZN^c4Ga`7LoZh%1P;k(P#hvILaVKRwH|&5kjb6p^uppr0)cS<(-=XTlnj3658_j9 -zhu@}I2mAgYQeWpks@i!)d6SGxrwN4vs@>-Hq)S>w>Dh -z#`Yv}san4f7p!D;mqhfA>xvA0s#N8U#yRJUt7$rwd5~F&s68ehx=)4eA$?y6PrJ*N -z%OU_7JMj!;lFvz$v!$!T9kEN_u4~JXbF%dHt-6VXBp^vD6_&M>GvOHP`rH}JyxX(M -z9TQzvhHKu%4vxc52(=9guqdN$U`x?K}lD7*Tplzr6Lz_LdppBW|6DsXVcLA@Iyi6lZTz!sYLx55#maLZ*wk -zBX83O?b@mVCiqx$b<>{aM|g7=d{2*R4eU3h+SrM9DFsJ@)BN+~spvS(`8BkDMH@d! -zK(N+yHO-7YjikGypvi++wymwZz=ia}=*DJ9B3h^V(g50%8iIUMfGk#k9#Z^8`u_y$ -z==OpRE`N6{08N-K?l-C9USiJpoiZX=4|wNWlB;X#JngA*N*z_awliFW*rm}ay?-3cN?{|d-`dY%#J*P>qVH;n3y-98K##hKyL@}DDqJD6mD=1? -ztX&@WFxUwCaL*IvmJ-$;2~@J2*XN#;FDM`6*}9cFD?1jw`j_+p+m&e<$s5N-s$Mbv -zAdRvkWXnCXe#11msDNL>1e}xey~ov;ja?|}-ON8qK@$H(MoC(3AtgEDm`EPHoj7z0*?ESR -z<~3FUZW9OT&3zi5?s1A;V+rylkA}$*zfm4>j;LMGW0pb~+8uRAxZ`w@f67HHGBi*n -z?wzI83)?zGs9NZ_WMYWW3?2S9U2IF&tWAOJerbrpEcWr5FUU2I65@OLFSKHU=Q#`+ -z6gNZG8J!o2}7JXJU+atrP)*t?J>x2vF;4p_|>p$a3n< -zDeg>zlFoK+Gp{E_Y!wLtQQT**YV@2Mu{>MGl7b%5B$~!Ylxq2|I8}8`E#J&mO|#DK -z7b}xWX&>;McdLJPn_%)IyP?*UA@m~7BJ9SVsd2~`B{`(p*G*S!9sd%`Hfe+Ga -z<-do;SjTJ-hu%>>U4?NRF4H+UsbncG9xqalvU?`5fzx~!i_*J>ms(kL&6Zfw -zNW@?J>p`lr;F9oPIm@L!Bg|&Ev0t{Aj(5H(&iqX0d=eS{x^ooc&K)WUxNE5p2V02h&gN7cxyw#-A9%il4&dSl-1 -zX=k^z%wR|?H*OP9OSnRd3eR*5`CnAI9b-UHD|e_gUr&*L>lSWCp%=_3t-(kaaS -zxulr(y88@FVtpR3az_XE_!{u_WNYCVPgjZLih^d~SoEjK^ -zNxoIKW0d?1Jbq~mu|(J`DT{JNG`Je4A3REwWJuz3Mt-C;Gau(gV9dnl -zNzQ*D -zY}>?Hr)`DUyE^0tkZx#{_S`~Mf{i$hHq -z_(bZD?1M$|pf?Za>{-oyoXIb0oWVcGJ_RIKSl;59w3JHK-qQ@U*CWXSu|hn%KmUMT -zhqie@*Z-lh=L;IJIr9GgOX6PNQUJzzW4n6ar--KyQ%U_P_&XRou4+5SJjIO -zhj)MnLOlKXBH~S+M3HmliltUIH<{D);QL#VonZ-m^^k1H@$FIL$=|cNv*_{v$h^^i -z&XglLI4E^J+_EvJ@x#evb`}U(YF8t{u~`gHA&6-Sd{d{gsuw%aDR*eGCYlct*8GA- -zjTmsFxE(Yp?q5`pNJX{orr@lMs%@ -z@F!2V%{L>E%inkl00#)<3?C>&zHURa;r0(>H_8(c#3wKL<>J6{w>CA3c;#EnNNWPK -zY*@M+u1l)8Wtoz4*-+{vhi9d<2W9jP+`glcXnd^2_}wqX#Z^oZQ*c^i6aW53m@R`a2)@EHveT2M+sEvq -zL+-$i5?>k#t*z;`#UNgNzbRS2B0_E#Sa1@0AON(XhmWq`Mv@KoB?zWwCZ5(rz(vQT -ze5CiL_w!S%SXibeoLfM5SDpkJ59i5*)B<~=^Bw8~UN)eSH`C^chl54WFo>O`ZV!ZB -zLbAVAUPqx&nn -zkK;4W{oH*YtT7zQwzSZ3yoj{4aJA&hDjDzP+2KWz$fSWk_JeSeIM?j)#UcJCoHDtf -zix^#5-T5d(komj&2yq>pJ=)r55{}D8yMIYx@28}#-9siGX0QKf?&Mzldj+5Ld3_NS -z4AiKwIEh(D;yK|w(SqTE9n%8GtSPKb#h09Z4Su5}t!eD(Q79?|%yRu2XN(PaXA2*s -z71i6PgC|vpxWOcF>x1^Kb#TF0oD3Wt?mp_nB|KW5!a(bex>_ -z3BzXnSRUV4?S>U3oNUa%Oqj -z8OwH5UT5?Ojujj-vCRnqbP;E&9_ik5DSHdY9Aj-{-YonuU2_duuXI;W^WN3+orhb7 -zJRK)S$2%vtD)rKMa$$Q2xEzwD%0$f66Q|lxI-cRFU}3Y97G~cZ-ef#Wt2i;j3h}G& -zPAl4|^*Z>mC(S;^Z>wL{dH^rH(3EjjEYphZH}CB5(U%t~HjStQwWejfS1Om$osi)< -z;NTxys9&(06D1hl{#4^aRM^V0?7`uNnWj(8gqY2XtWt)pJw{&ptr%tL;5d`f>M3`$ -z+QPfTB&E~1subz8-101Fr-UcP(;OUIPo6_QTk2=W9VEW3@xXOiMdyVMFQdr@gJ`+|aOq^W3WiPPh5f2SB17BQS7y+u -znv)_G?e61!>;Z#_6P4nSYYJeGHH<_B7wt?r0+SaVPinhS7F@HQaMwp=3mYl;!&Sg_ -zxqGs`&Q|2q0HbiHSw2nW|X|CK%RDO4-gHNAMK0a~2 -z&YnyKR4pXATU@2@GP9*LB^x)xj$4?u35s!X5=FZ<@h7aVHuJO2@D-S@)`pOw@MfK= -z9CF^x_2TSZNH39)5xmnwrrNyMm8sOfi`p-XD1^(|DKb`=&w7J4jaVCe$QSidF#s%1 -z2_M?LGU>3Ie$)QQaSi->Rbx!*2n4SLU%ZDO2OP|@h;~Cf=R#6$wnx0KNib&@7hwVH -zrC0tpk%Ev>1@u%Kqac3_Z`HZc$Iif<;c6-VoDyVKD4DBALXv;GGa4A4G-u+Vd}iyx -zB?i*=Q`v98}{1Lo29R9EJ -z;#fEkn@a$Q^4g#w1#Xchdc-*jHX%^b^+Ak{jAF9otzN%Qcsl21vs-?4# -z`1Sp0U7=U@=|}k;4dj5T`z_Xdrafi|@k}WzhLHuDqeAZok%mpu#=^ujH60 -zPJz$VQfn*sbP-b{nen&g4td3f_wvG#@ny -zHYrU=OK(@s`Ss%dM~&u%iMYDd%+0Uj@-?AhB!FRbp@4n#lTQA7+zp_Jam2_GK?ALs -zC%*F~Cd1Qn%abt}1LWuT$*?EiN%gk+$O`x-%}bHW!YR(c+FY$Q1q}~&6^mS|a&cpe -z8(lG?*YtqNMA$E&nMm?RdNgGQr#zc`;D<5yE+vAfk0w5c_r -z;BdO-%vP$}c5^q!oh+8_Yqm^~Z!gI6Jd6fDR#|#0zGAzm<1@imsU?LxO(o=M;x@+A -zzie9=Ik{reUvQEqDD)iuVh}PO?mY85Yq5FH-LyaqAsSDEYO4Jh_fS*HF1!GWt3MxJ -z3zSX0M&%NMPsbcLtDVZbt?{F^-EubNzom9@B6#YLC=|vzie+da6tM0-69sovPLx>& -z4B&p$M5BsaX02rwheb}qRSBZ^Gku3fe%BX|+u>{e? -zjYIUHWA}-tWv%ghz~A(vC+Z~-BDc<30vzyffRyph?)$zb16L+~7#lRr8pW3e4i~Vh -ziMjc=N;Gf-plkDC2tqZ1V=OHODAAHMDw<--4-XJw){T901}uhrmrW;#^Ql(1ek}ux1n<4;?~H>{`F368o>iTeX3f5)-2M+3``WhXxR~Jny -ztMzgqE4jLAP?WEw3FZvPd8JJ{P^gT)uzREJX}t8GvJqLDWC$w?W<+bUK? -z!u`)q2aI`8+hd6kZCi{P+4&ybxjXK59CtjkQO~m*4D!wfb|Wjxzby{J#A<4RMQ; -zG-=jvYH6F8Ksv~*w0;snxT8g1F)3x*6En?Z;i~WPFVagD?ZW!4G!XKATt0k}O&;6vS>mnylV42j8t&gkH4lQ3`=&!F!?Sj0snCPePbg}spUx9AqMtMal4F0k^ -z@G-9Yo^_TtuXJu8Lt~Y&Klf1wK6!)xExd7W9u$^rMWti#HL;QV7AYE;f7}z+D%u-g -z#$^vgiw@bdTW<~ct&4uws{uAHt>01HoqGOU6MZfEF8z^3)DyL(yj -z*H6*U9hBoTB8O$6l{)!v{M8@FRzc4|8MzQHw&iV-HDhc`0rH}SrI9{CZ`Mbciwl3Yr^XhGUjDoyq?KiPLPTMb=A_=C~T6*Zo44WS8r645N -z`SK6=aV@P7n*%SFD-6y(!yZ9DKg|-gW46GSY7u^>$A13Sa4K{J**xDSp;v^f_ss#h -zBzr74paSr%d8iECgvB9^4R^)s;IGJ2Pb&B|myOgNg2|se635ispL<;o%VbP)pqLRC -zYt3wyh|&uQNS#rV<8l!&L^#f1Au=P^Hru8m0xPeWq!$$UV>P)SVbka*-E3W+YDXq18G7YdjIaQ}<$;{=L>TGR9&+v&mIOODh50dd75((I&#vt@K`pa8z3) -zmXHXmdQtZGT)V|r?Cg!1ppMSX&eV#Yc&tcU_-$OUi9UX`K(^ao6F$+RAA6>tmA2&Zm(a8%}eP>F-F#Wns}6b>My -zlc(TZX63`3LYH`1Rn$A^Lo -zQOpQ=11EU-M~+JiN{;GD+u#N0 -z9N=Tz6&HQ#P#_#Q)UAj#78e9!MUoS8B(p~xkZd;E69YhL`#-Z0Q>&us3UMD%Puqi{ -z98ST1IEG&+jznizO{j*vg-iqNTU307z;n(CzqjbepqF2#ilyYRf(kVLw!`+g%0Yk^ -zv~~PKIP!gN@c8&>8Lhvnyq|~JW$14NP`u?}5Kxwqbc8u5n_`{Ba6Y-Rc!Z#TM#cG; -zlf~dI%EEStFejfXVH}?AHw)=}#t>fJ~Ii#lQ4#n-Y -zG4h3?Z0fm4%KLo^l2=n`dhY}qbY;WK-~aqgwS=#zETE$Liec61RoaeSodQZ3B-Pb-b)obYYQR3&FartS|1&WVL{w21T#&W4?ihzZ-{$MNPsz=x7+QT0N-owE_WV||Kxg8jQS|BCV=#2~NqYl?5-&Up8JO)Rg1 -z8wWcb7l@#!r240*B6R9#MP>CR`VF$l$VZqy0|34_N_A-rH)O1CgzkgExG-!^uQUt| -zB_wosDOEmp=AhBm@a99NfXRq@scj2c_BRazLxdP<)F2ZQtj}HZ@4x7L)s$?Zuhmxn -zVrpOV5M}r!zveQkc-u(M13D`2+G>M8xuCDsFE@wl*6F&?XukZ9vv-vw%Ru=Did&`j -ztm?iG*He?81eYN*GBwraCTAf~skuVjCUN#w%M@rzqXxr7MVWPv-hs#?D{U6}?Gsig -zQ)c+lTH4LSygUZ*@2`-4`VJT53){H6Iwfg!fm0OCJBpz78oMJ67*Rog*4zXF$5Ov1 -zzN;9By=fyeuV#~hU-eDySRMY-jK{Piv5@&!eMxP>o2Dw1Ag1n%&-Vx;yq_!}vYQ{^ -zCAWH(`pAgNbv;;|sA1F~w_3&S#gvKUbZqphX2)jZ+k&Do#D%%Q0Ey#%O{!eO0mzoc -z#^G{6KGQa_fV?-Vx?>s&C>3KE;m<85en3=lLc}0~PwpGw6GrKUY?5!9TEME%$x<9- -z62wgGZAUMxhj6a<N2Idn)S2P6yHzAA -z31)aU`r9wL{U_VY%E>KV~XJi!9R_>pgRSjy}Wfrj71Zxad*;o=@&WupTWi^ -z^}pZ(Z`sk@6e5tZf{ayf1%nzk5&2ZzuWa#(hfXfgr%^<0=GhR9(l(5Zjr-G7)iXtd -z(|>1eM}+>3<%-m#Vg|o++=8n^ipiSY61zq}qfZdrYKcl;^+UGavu( -z{)DRNtE&-sx)hF*>+*{`ZW5pVJx3U~&VLUw5$IU(Z~es-S{?~-cm`bvAj5175jORz -z6WrL7$?Uiraez-%vGKB@{PQneQKd@juxQ%w21-+Jvk@g)8MXcBpxqm;Hn@3Oo)dja -z-HQit!FmG4!J{cI66~D3^~J39Zx46@_+gh)-DEeyJnbDiRTH&38Ux7taKOAmiCW?y0^Tq{^fWrn9=j)ORPERIlE -zg>JWtuV}k#e8aJyXr=Z0C-vbAqfUaC6EOiNr%n5@T#c_bX&*?P=Fell$4K@>76!w` -z@k@fYS;i2@6QM%vhrAz6z8S>lD@qAoK*GEilyfGTG#!v{GeL-EQ0;(=ucr!NgBftOeYeLYS#61 -z=2ddjfTl(Ea@9&XsQ{Hx`A@v2t{vh;4*2~PI~AYWT6g&dx<Pj4(R10q -zh#DdPTUrl}P$ER7G?y}t5ybwJ-9E#~r+RCey3q0rZc#U$86R(>^e85@KH?1H{rf|N -zo=BKa9k#W|rp+lB|C1RhYZNO^rM?pJ>|Y;zrpwoBQ|6@Z+xp>P5(3N`JPd@BLpr$6 -zXETmwqD5DsWqu#194?$%n)>)B?eD_%9;+|1x3Q8sW|y%DWFneUjEYr&&fkeIS|Gtp -z4}e2!aCLF)i3SpLrDDYG?z7|_aXsy%WL`Es#}o>abhqE;ek3tx$Aa*r7$&WxUBaG2 -z3WU#Dz`jpCIGgBZwbuTVgdxM*q^*<@hOdbB{}X=;fb?2Km`o>J*j(>>&ja92ReiI$ -z{v7by!}eOmbeQ`fIr>FK|9e&aZYY7nuj-n8Paq)`E+5}Wg`j=Y?dT>&--t9fv|R|h -zo9>^-%H9VP*=cj3TGR1j#P~+SSX(365s%cPT%NpIWj$Pxl!)3-RH&jlmFf+GG&>Q& -zles>;tIBr?`e#MyfpB4K14T<8zX3R@sy{>b3AG9E{gqT-EEPB`x>zcfrPl+`RP+vC -zZy-xlefTeC_o-&3zKj#~wP-a!!YOYQIhY|R87jMOg>M} -zd)4Ld;%#5j`fUFt=tn$0o7djyR2|a@7(Q2X4}TE%EMArbVQqVZy~(&&wW?KrRMD?8 -zCEq9_zjtwEhog((#fs@1@ZG>4D0*!S4g;9a&#XymPEN0-QmZfAe~oI>F;%#l$6hM}W|yGOtVFT4ODs7GDqEA(2D-6GIO^s)d6PrB*(tbk -z{hYm7;XQU%ELBy-#?BU-JLSXQ1W@JcK;LAde-ZTU18mDfp`(x>0Dr9Ls}z3K*c@#u -z&EJ_%XQ~QUOmB8yTI!!bL3tbKWtN%k3MfhTCRCCj?k6Z2x_ODzC?9UhN0P%ziBMWJ -z^^|}RNsK<%Zg8DmwByoNni{`(*-I;s-EIReHEnZUX)jV0O1=@32tj; -zfDjj#ARONKKzc}%flCtf%jan&OzLp&&mJ~ium@h3Ct-MkQXRdvXp&QqS`Dul}&kWky -z-CP>Xu_`_LsUrag8uMGD_o%1~2tRsC2h -zk+Muv-T`u_3wD1!PN>OKlOZU6!JQwoO} -zPZ@KnzLBe^Pe4f@Ol-jLcMx9bBRsnY5XtaPLRobxX2y~lWq&ggp-X7515mVJvQol? -zO+V7#bYzkV#lb{=i1=B%N8x3_NBX7pB`Lm30K5rzUco1Tzd_-noyUQW -zBS0Wy{MAf2r8n^S$(;xlg~PP}I`EI$t2{sSlSDVv0;9PO!=_Bc#I`OcB|^2R16y8= -z>iUy{uWcn)hPGNe0OBaHdmPeUb#Hyk2Z2|B-aB*J5LT-dR-+f6RFzs4rS^LrKY;R+ -zxQ`)yCj%ZxJxV&R012pO!sYe#Jbvd<48f97 -zJu;#tRthXwa^X;wYT}}mJttX$XRjp4fmf@d-UelIW6F;pKXx@C-;}yj3SZ5@Z_$hC -zXU>sh;{GP^^T7833q67TFzHwWM42*Tm769{WyA^U{DN~O@B!edxx}1K9wmnPVoB2c -zO;+tFY*iZRN+h7MDXyp_z3fym)4l{(;csi)+q~&`;#u(5+7AQd*23`pK(U1big^n#il$wfF9zVX5%j@e);8+EI8F>ESiu)u+0WhIT6lD#VIcC_L -zR0w~YDkAmEw2^?tv?XzWG7={pZbIVrAl}%xT?bOu_|DNwabFJpI$&WRwz*hzYyl!v -zJZ^jkSJXFBGcINO7dZd^W(Ugu3sx*#IjjV{fcFUl}L#!D*R2y -z7{&cE@DG^l14V=B>9|LRUQf@i`*Eh@@Tf^bZZq&YPSua;0|&V8hRi&6vSYO}IO0^R -z__Qm0q`nxOG`}Gj;@?i&ie-XP!%TcvZ&ZKMc1eB|2J8NR9OP?p>d#!@80h{G+@Tx) -zmP-^Ia7+OLPJ#@ecLHCDJ0~e$k71xvddFT8m5Bh62`BbIMj`4XgONm$dSr8leU6o= -z2sMMLZRIY3?**O<{QjJk%tH!nA7cF%g^n#i=6JZ}lfVmb-jUDyNVjtPP@XIh!nm#u -zAIgQZn{){P!uC@G+oWW=8cV4dSZQB#Bmkcm_&Wl30Po=vaIqc-I>rE*izYbN;WGr@ -zAaEBFGJZ^X=8ZyD?TNzH7D#KzqO8!Kb?5dNN8hDL3d*Em2d5vI6DLqmE -z;%N=|Cx|>9_*=jSAvI!hf~B(!a?l})%gYv%d28-r-)-V(8HT5KmLw>5iWQ-VR)c)61U&@?0EvZUZj!1xiMGKsi%n -zKh(b6JF~V2aZka15a)&ZBuh{n8a0KQ{jXRt0VWZbx{sIMwV)2)6)_1+GLm -z5u4MYd`Mx`$L>A$JGi}rbAo&h_%qzM8b6K$`1zTmThc=4(F~B7#<=fn-%WTr;EKRC -zxCbF`0v?0#c--gPZUn9cuEsqRcBKd>K(4~gt>R85UnM+kX9ov>4V>|O9(V{ijc^Ze -zHzN1o&Z9lgCA`sgsnZf5vuOf6-2C?+6s>To*kvGB0#}J}3I~!C&F>11v9E6Osxjox -zH}!7f0CJ(J|3VWt=oIcH9X`onoOiXPB|TE;k+u)Aq$MqsmH=7Ol6q(fkR>gthn4_Y -z(vo^;36Lc%sfU&TS<;evXbF%dEvbi=09n$KdT0rdB`v9kmH=7Ol6vU>2aPAsPCpqk -Qng9R*07*qoM6N<$g0t|Fy8r+H - -literal 8324 -zcma)CWl&sAu)T{c4!b~b!Xm+46P&Pk(BKXmEVw5SED%`S0>P6I65QQggS)!~celq^ -z_1^#YV{TXV?VjqMxwmKLoC#G^k;B8Lzy<&SPeC58@g#fxYnb4tXQ6IU#*@HulGk$u -z0Mw2D8mK`@;_69B?k1z__SVtT&C|@q0`T7*W32VHtpOsLDw{jRK+%dU4Hf8PDP(tEaY -zVb;TO?zg>+y0e^=)H~9ck{1xzJJsKD<2QMWE<<4w$ku+?Nz~ -z?sw*04{Ntj2t(m?vGNT6Z&4?~J_DA=4@RZ|Hy|?L2baGw-~xOA-T`VLCt&4+Es7dJ -z3rqB1K#)Ht -zM;a6WNe+K~@E)jy^0nkG4{!%88+0X^_i_yf#-MD!UOnz)0W6pOg`AvCsp#J@n!d~L -zCVJ83uVFsD4tN05H1Ti{d&m(pugW$|17?Hwda!DnL8l=`_8ey*(%(4BM7^w__ -z29jw+qAw%K`BPELftPW%&SF96Iq -z_h)d~d@n?_dd(h4B}06FF_)p)nqY@o2W0}0k!fDPNE=n60)9Z1ad$Q2*UaJYIWokv -z-YS-7Pdx#2eP~Ya<+J1YRpR69RWsr44_urWbIB`C{fwD97K&lSNUb+huWn><5yoYB -zJfUnktJt254fv(LN1LG_v8d+K$*{JMVsT|C=wGYwMq)%b#mJ%{U3S -z3eOditQ%g>1&0d*gJeo>(pn)%r<8<&V=*Y6MZ|vjseW!0G4f*AyoOjh^n**XNNygpr52-!$+5s79~Rz!atG0AJ~ -zaLfL^_!UEf*!wv|>{tUMEVChL@bJ7VVyP{&`32M_;g)srdhGDfd*c_n>)Eu<6%~5 -zXz4zr{*Ya1{haF#(VA;Dlz!_!w!vA}mSI?pFpKT}D5t6tuIgcFQ^&`ihd_avR-x

@y(HdF(KVXFH}bp|wUaw6&QXCUa*zVm->M@=&FCKVp97YM|kve_3*VCW46 -zv!#8UFWTMyY4J+6m;I<#=$7m -zvIxO0NZyc8eys_L$J5|l<8aShl<6-W9Ux5Aw;1X-A*d|~znVkzbk~W9NS>&l06W&r -z81yNRW4XxCw4Q;`80-AJ0^JHD+(Ld}1M>T0KVc9A%jak_H8K+KT&aIL$9Yj{OwBsL -zi2A95!ZqP=dZaZjR5`2~3(lW{1Vhz*b*AYMF=(OA6b`(1Ehoh!e(1-&%>?^8@^6A5 -z{;YtsV$bNqlh-7LCkxfTnr1`I0`dcMIZD$kxbZku8$`3YrKX5Uk(spqYh -zz_N=sHsL;;DZ@TN>NwJZ2}p`Ul{vd;|ID)}R50=*NyUdB^$XQk88m|MZmkHOfep#< -zzpW_boa0-TB|CQC=plYSmJNqQ$Q=9@3P7a4zeOQ{L0(gfdR_i*D4RJ}5#WFvhZr#f -zpXZ0^);*ZmA%T458)_hcfwjJ0md)%R6*(R)!cKk-p_wl0EUYhkMtkW6apETFb28WQ -z4Mr#(QduSc1>s5h0i;rz#fJT4`6?ZjPm_lwr{!mvh!p@w7-I%Z_x=86oCm?^|4<)8 -z=$={{fGneBRf<}8)D3UL@M`bMg{F5AwQ4@5G3#t45l==axYJ4x9G47q)($$J@=@VI -z-Y!i+)Uo`F$q=Q3XBA3T_D`PpsL>6Mx4o-;&M{QBLJK6f;^0Rs9W?w@U_H8UL4 -zC<<6>UoFr6a8`MftsZLN7PJe>FpW^b?mA=9bI_jUn!2_yOZ)|xt7s4cHEZ;mY&G&={=sV`v1dU>v(H>0l%EsXcuV*0XE_*?nG -zwAp1c&4Q4%zq04fe%pX0p*k30Hxp~jLW>&P`{whYJ -zzJkFrrGP>Fd{IB8M&J0sk-7EX_Al~AhU4m{BiC-N8e+EFzQE6fx~9#Xf4W -zTRkXFzM{UunB;+Os7PyK|1qdU?R#%myF?+&G(?q&(NroYmBe0wlhR*;mu+A9q3JlL -zCpSCOmC%GJA@7BvKd`Y=s>FS}ixpv(yR$(^u#@RKn_nK} -z>R!=gxS9R?c(a{+!Il+}5&fj$ubAFFpQ}FY8!o@1KFW -zgd#)3#sg)MA-L41dVrsku5bKRmnOzp`GB^EO}3v{9=z{$K;nKL;e}0IQO*28T3TAe -zLoIq^SG`zr6}x1jkj)m}_Qt#{T!{>kA|@qV2g6Y{C;imh<|m$IL$T6>&JZ=XF~(#I -ze$g&Ik%OmSei6-t%`oS8 -z!RzV810Fzdj(4EGfU`l(E9NZg=liA9sdUi;C{pZ1WO&eu8fsKqA$nu2S-r{6F&Nnr -z6}kJoajWZ8Vv;TvpNZyjo3F+F@bu7F++{E8X(N`1D^$Kv@M#m-Ur&ZOoGnwcCJRP1 -zlg4uR==108HF-X4sCF*Na?O&4)YJB7GgIJIWOn3c!WF+a9qN2ra6aFYNrz$p{DPA^ -z)%WY;exJ@RoBMSTBR<;GMA|k%Ty3fhyyaGBm}r5mxQXdja#Mt<+-?hxgXPipb$NES -zxNY84ES)}*GUXnb$wpzf>U2Lj2_O1|TnOPxe5y_)HG19%K -zX8AWbA=>wR(RNYW=oYXGnFc#$)pdBy4>m%aN24jHjQv(GH$(&rK2D#NsQwC24&&)3 -zAuxBs-+rw@L#I()e1C9B(BJH)a7Rm>xjYZe#c~>J&zJ9cyblyII+M9GK$S$tCtg3m -zE!8fjMr(0i8rt-|tA3Us(c9O{=ek<*tL)2nyKoGiw>a8WMK*P2SIzfMt%aAyJcnaf -zbFz?U&!hE&duRl@QUCy1%oxkXPamf -z6-b#Z< -z*&VlXp-A`OAe1#^JP+^Nzvz;_&X3%~9*HGij-*h*n;rcN$bOQ5j!eZ!Ho`?qxh3s( -zcd-tZdvK897|MJ09X!EX*4@jmhx&JyTh$3PR3B(A2$5#W^{=$4X|r6Stb4}2Qxo1= -zdnjJ!Kw{TUm9?aIl^ZHM&P$ABD8Va7tdd@xfPg$j -zr78six@{832n6^B^cd?G8D^1hlJM33yp{N{`B10nCk$a~0ze8HwRIS9*G!8>)f -zyge5Z;3NQ3(*O552zet#g)IzSy|Q9oC`{AXnHVb1eB4PlXafJJL0bQLMy<#o*bb~q -zj=$gp<5Q7RPt@MVQ;*b6fDpmYNO|2Bnvb@j##~nWr%3hsE6eygCmZ47G~SrVWUmHTQk+L$@rSx -z-h|f2(z7S*M!^stp6lL3)@lafb<4z!X$%p+rhHFYsC)E@V|AvBbKnQN2p4m#8_9sA -z#(~1B=-oBj<0VzlOH^Q4u~}3*wBz~vzH0IMgZQZiNUpFjP}5%|sK~GopvzEk+-Z{j -zsC_p_bGfDTG=9fU(wBY@caMwITntcn{%bS)8MI17aeCYf`G?d1ayB -zK^#510zvTMLc@vQ(#In!+qI=zr=H{p7vziWTs8ZdKN5|fZX2Rkm+~Q6#`tX(-N$-_ -z3X0Ml`BlY!M;nhXtyUi7V642)tGMLwOBuwl2l4!7!c-5vwgPN*D!ppJlBwui>ky?; -zgj&Mzd8B^7MM$fybd;$htxu(6tl!Y{{?E@;ZNi -zk{%4lR(K(-XEa*r*)HVC@SdJ2(0t{`zPt8|xci*gAESoqn`{QB$wF$Lx)V25!?01g -z6r$SafzxN4v2H^A$a2+B-0B4e4z&qxOqk}aLl~v>!)^dAI-P761Mb6&MvH>C3V|u1)BZ1@S4urMXj6hs=Yql*t(CxlVihstMW`tpgI})u -zbT*5(qxYgKWL*qeE5xD;$puMLBlhG&f<16XrC@RE@kEZDKvcAZEL;=s2d*RwqW{2k -zDE@UHLFpnFo(UE5*f!o?PPKFGoxb`XKFzc>Tq=Wqc75kBXa8C}9Tf!-dpRuY+weZ` -zm^iQdjuXcHxOa23AhH%$K3#sLGxE8p-Tm3`Yi{iRh|nDD7L6`lWkTi3 -zh_9rW7#Kp|d0?gB5DD4N2Qse3wJAo0E}9bh^zv1 -z+sNKVjan(jpP+z%TB{fCHriJ$0$MvQl@qgj2(MR4oRsmW=U3*{q~;{%5WL7p`6iUWXY2dRs$#42A=N%3!>`+bKO6F&2(ZM{HF}W#=3@Xe -ztsi6cRRnQp;Y&q|@wdL^HP=C}==mgSKN${M0@#0_rnw4v7GU7`J>zmz0hJe6wgbZB?X*Km&Krc-mLbyYGo$q^ -z6dU4AtRLFzc623x_)un!DzEGYj1@z#n1j!mZx7aNKMT$KPaow -zkpD=4!tgkzDrhU#i(o@NrKA_7Y%!!$-1g=^7eg8<9D49~=N#VuHQ@AHZO9a&YIz6j -zKEa6ISxu7bkbHx(dKsGAZBG@H^u(YQAY*LEj-h_^iw`Sp3rR?@5jh@~PEEtX?qrdTSvBWAT*>cLoa9Q)q@{{b;#^G}!*GEA=^Fh4auiL5kD6v^2 -zGr$dM&)lQr644OL%Y$!R&E085;+Dc0YI}O8{b`vvWC-JOS^Y0}?-Jb-G5r?+R!DKd -z!9vaSl40GAdbx8?lfO7_o6>LGr-ck|11ces#09DzuJgFgtlwpm2V4;^7I)+m$R8BD -zbF(z7F@uoSnEtVhb(nlzlX@1aY?{c=&cA-xWXKN`UAhFT*07BXB3qhfh>`)*S__gC -zI5vYA8JcNu=+qQyi8Rcg%yJ*ZUFUn9uM|SyCu$Y|NZKc9^ab|NYI~V_@OB>9RWbY5 -z1zR`@y!_bT{REcTV%l~J&dstsZMsk$ZaO?iIKO)!cDv2Nysg5FX`gZBp84zSmlQsW -z1a98N?P#rRr&}|%l5tfGGmgmzWPv!;GiC^X1rdSEje4p^Gdz}o6kgMg{=KEpu8Obv -zUNI;NsB{!I?^gdH03`D_9+I;D9Kj@B3&obT&#ANdkBgwplvv*IApZ!dQ4|?D`Ed-T -zf?T)*(7spYdVh1?_-nn77h(HmLStuKe_Lu4aFq7J+h7nakPFWw%>y%{OutXpvgZ{H -ziumNvw(uoEt?aYT -z^=6lWI(cfI3ocV8i}suxydCuBrc6}0vmoX^RlL-jY?|e%%K#@`zRcD_w9?gY@;Dti -zQ;7WgCz9e>^G}WdWEfz0wRq7}(g^a&BBwOV5g0IOgn1Gx3P+NhdJ84%{oI6KZ6zM1 -z<@smeE%VaxHhObCA2ZB#Sr;!AbPCYB9WU|YTP1JH{Y)(H^+TqL%fQlO%2@aJ{4R-r -zQLf7e!0fCuL1;11?e0+d;9wH6LBdIP@nZHW1kbBoQo&0tb%|brxN5jU#;hhe}L^ -z8{yrF((CTOab2hx#9%q^;kJ2tdpjbZnq~&Ib#nPQz3ORi&y{W(48CqN2i4425serF -zT0jc0XW~8tQp1c@7o{@4<#^fRaGhmyLAueWS7K_#N^q;g_B-DIrt$AB*s2UK#x3d1 -zrrRxVw8)wwx8+T+9rDQ6(J}H(k9&Dt=)LFwuy7>iS@q9WREzNN{k)7~E(e^)&j&mNH)~|l=aEqnT}X9JnpM;f -zC?Fp>HMZ&od}VsTSL8-0@C>%y2?1p850Y`EwNc&^cQQCh;39YXD8?|x_D>NAf -zR>Uqk{l2<96$2)9pfPeMj{EO^{i48!J1YO$czewd*y{V4lY08Y#af;R`DgITuCM=< -ztdai+%WaWFFKX281E;fB7ltTQ3YYkD@8O0E^_zZlT*l`zcbAx;oYIv;1o_kFs)R}R -z+}TFrlV)$2+`}^SbAeQ{QwAeUdjYy4oq|TLllcvr?tLL(FVat0@97^T{Y;iZ3^MiV -z&7SDtG~B1+_hls84daz~0{ufG)Bhf7;D5lEByLuj`8nbHWKM}kS6d7K5oL}I!FlZN -z`02c%X&FrlmlQw^$wHyu>G(kD<-zQD-+?ePsOPJk%BcrI7>FvzCUv1Z*?CN&*s8O8 -z@oUF&db`Q3@mZw7L&Q#Gq1YD!I&M{*ER5~vL2D+iAV5DTVxrjoM}L|{!Bx$Olh=)B -zu0Wnfmr%p0?fC}Vl1xd_IXFiam@s-DuJGh#ObjB6kY3d3o)}8sqxyI=6dNltGMT?S -zBBoh>u62K&DlVIKJiJ^Fl2n{%*um6*EkuhVS;Imi@zcIZ2t-CEIn%n_45A<=)7(`$ -zpg09emChaZ44Y8%OIGmoMF62&F?Gb#;1;%z{V;cluC1sU!a}woOM9%MUS7(kcH2=(5?M -zxX6|{0vM8?MzJ!47}i@^TZx21biBA`K -gx-fwL-(3LIPg|59oc((uo)mzBj0(Kywdv>o0sO6>y#N3J - diff --git a/patches/server/0003-Leaf-Config.patch b/patches/server/0003-Leaf-Config.patch deleted file mode 100644 index 3b96e29e..00000000 --- a/patches/server/0003-Leaf-Config.patch +++ /dev/null @@ -1,615 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Wed, 12 Oct 2022 10:42:15 -0400 -Subject: [PATCH] Leaf Config - -Leaf Config v3 -including load config, backup old or outdated config, and add config to spark profiler automatically. - -TODO - Dreeam: -Add per world config -Add config reload - -diff --git a/build.gradle.kts b/build.gradle.kts -index a32417619cf253f25f060266a36e895c2e8b623e..2fddb2f371b5b901be668fb60dbf871830ec571f 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -26,6 +26,13 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { - - dependencies { - implementation(project(":leaf-api")) // Gale start - project setup - Depend on own API // Leaf -+ -+ // Leaf start - Leaf Config -+ implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.2") { -+ exclude(group = "org.yaml", module = "snakeyaml") -+ } -+ // Leaf end - Leaf Config -+ - // Paper start - implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ - implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index d3219e3d36ef96bb43c2cb7d86f14ef7a702fcac..c7b5429910df7e3c4cfe1ac2f095845fd493c9b1 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -124,6 +124,7 @@ public class Main { - Bootstrap.bootStrap(); - Bootstrap.validate(); - Util.startTimerHackThread(); -+ org.dreeam.leaf.config.LeafConfig.loadConfig(); // Leaf - Path path1 = Paths.get("server.properties"); - DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 24b9711dca7223dd12b24c36cd76f50fbd96fc68..40c2dd482a8e10f1d5262d365841c968dda847cc 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1242,6 +1242,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop modules = new HashSet<>(); -+ public LeafGlobalConfig config; -+ -+ public ConfigModules() { -+ this.config = LeafConfig.config(); -+ } -+ -+ public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { -+ List> enabledExperimentalModules = new ArrayList<>(); -+ for (Class clazz : LeafConfig.getClasses(LeafConfig.I_CONFIG_PKG)) { -+ ConfigModules module = (ConfigModules) clazz.getConstructor().newInstance(); -+ module.onLoaded(); -+ -+ modules.add(module); -+ for (Field field : getAnnotatedStaticFields(clazz, Experimental.class)) { -+ Object obj = field.get(null); -+ if (!(obj instanceof Boolean)) continue; -+ boolean enabled = (Boolean) obj; -+ if (enabled) { -+ enabledExperimentalModules.add(clazz); -+ break; -+ } -+ } -+ } -+ if (!enabledExperimentalModules.isEmpty()) { -+ LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please report any bugs you found!", enabledExperimentalModules.stream().map(Class::getSimpleName).toList()); -+ } -+ } -+ -+ private static List getAnnotatedStaticFields(Class clazz, Class annotation) { -+ List fields = new ArrayList<>(); -+ for (Field field : clazz.getDeclaredFields()) { -+ if (field.isAnnotationPresent(annotation) && Modifier.isStatic(field.getModifiers())) { -+ field.setAccessible(true); -+ fields.add(field); -+ } -+ } -+ return fields; -+ } -+ -+ public abstract void onLoaded(); -+} -diff --git a/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java b/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7d99f0711c6b4f298dd296c26c11dd9d4b633264 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java -@@ -0,0 +1,26 @@ -+package org.dreeam.leaf.config; -+ -+public enum EnumConfigCategory { -+ -+ ASYNC("async"), -+ PERF("performance"), -+ FIXES("fixes"), -+ GAMEPLAY("gameplay-mechanisms"), -+ NETWORK("network"), -+ MISC("misc"); -+ -+ private final String baseKeyName; -+ private static final EnumConfigCategory[] VALUES = EnumConfigCategory.values(); -+ -+ EnumConfigCategory(String baseKeyName) { -+ this.baseKeyName = baseKeyName; -+ } -+ -+ public String getBaseKeyName() { -+ return this.baseKeyName; -+ } -+ -+ public static EnumConfigCategory[] getCategoryValues() { -+ return VALUES; -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/src/main/java/org/dreeam/leaf/config/LeafConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fdbdf3f6a5071ce629d2881e861075dbeef11b42 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/LeafConfig.java -@@ -0,0 +1,269 @@ -+package org.dreeam.leaf.config; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.File; -+import java.io.IOException; -+import java.net.JarURLConnection; -+import java.net.URL; -+import java.net.URLDecoder; -+import java.nio.charset.StandardCharsets; -+import java.nio.file.FileAlreadyExistsException; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.nio.file.StandardCopyOption; -+import java.text.SimpleDateFormat; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Date; -+import java.util.Enumeration; -+import java.util.LinkedHashSet; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.jar.JarEntry; -+import java.util.jar.JarFile; -+ -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import org.bukkit.Bukkit; -+import org.bukkit.World; -+ -+/* -+ * Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol -+ * @author: @xGinko & @MrHua269 -+ */ -+public class LeafConfig { -+ -+ public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName()); -+ protected static final File I_CONFIG_FOLDER = new File("config"); -+ protected static final String I_CONFIG_PKG = "org.dreeam.leaf.config.modules"; -+ protected static final String I_GLOBAL_CONFIG_FILE = "leaf-global.yml"; -+ protected static final String I_LEVEL_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per level config -+ -+ private static LeafGlobalConfig leafGlobalConfig; -+ -+ /* Load & Reload */ -+ -+ public static void reload() { -+ try { -+ long begin = System.nanoTime(); -+ LOGGER.info("Reloading config..."); -+ -+ loadConfig(false); -+ -+ LOGGER.info("Successfully reloaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); -+ } catch (Exception e) { -+ LOGGER.error("Failed to reload config.", e); -+ } -+ } -+ -+ @Contract(" -> new") -+ public static @NotNull CompletableFuture reloadAsync() { -+ return new CompletableFuture<>(); -+ } -+ -+ public static void loadConfig() { -+ try { -+ long begin = System.nanoTime(); -+ LOGGER.info("Loading config..."); -+ -+ purgeOutdated(); -+ loadConfig(true); -+ -+ LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); -+ } catch (Exception e) { -+ LeafConfig.LOGGER.error("Failed to load config modules!", e); -+ } -+ } -+ -+ /* Load Global Config */ -+ -+ private static void loadConfig(boolean init) throws Exception { -+ // Create config folder -+ createDirectory(LeafConfig.I_CONFIG_FOLDER); -+ -+ leafGlobalConfig = new LeafGlobalConfig(init); -+ -+ // Load config modules -+ ConfigModules.initModules(); -+ -+ // Save config to disk -+ leafGlobalConfig.saveConfig(); -+ } -+ -+ public static LeafGlobalConfig config() { -+ return leafGlobalConfig; -+ } -+ -+ /* Create config folder */ -+ -+ protected static void createDirectory(File dir) throws IOException { -+ try { -+ Files.createDirectories(dir.toPath()); -+ } catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory -+ if (dir.delete()) createDirectory(dir); -+ } -+ } -+ -+ /* Scan classes under package */ -+ -+ public static @NotNull Set> getClasses(String pack) { -+ Set> classes = new LinkedHashSet<>(); -+ String packageDirName = pack.replace('.', '/'); -+ Enumeration dirs; -+ -+ try { -+ dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); -+ while (dirs.hasMoreElements()) { -+ URL url = dirs.nextElement(); -+ String protocol = url.getProtocol(); -+ if ("file".equals(protocol)) { -+ String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); -+ findClassesInPackageByFile(pack, filePath, classes); -+ } else if ("jar".equals(protocol)) { -+ JarFile jar; -+ try { -+ jar = ((JarURLConnection) url.openConnection()).getJarFile(); -+ Enumeration entries = jar.entries(); -+ findClassesInPackageByJar(pack, entries, packageDirName, classes); -+ } catch (IOException e) { -+ throw new RuntimeException(e); -+ } -+ } -+ } -+ } catch (IOException e) { -+ throw new RuntimeException(e); -+ } -+ -+ return classes; -+ } -+ -+ private static void findClassesInPackageByFile(String packageName, String packagePath, Set> classes) { -+ File dir = new File(packagePath); -+ -+ if (!dir.exists() || !dir.isDirectory()) { -+ return; -+ } -+ -+ File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class")); -+ if (dirfiles != null) { -+ for (File file : dirfiles) { -+ if (file.isDirectory()) { -+ findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); -+ } else { -+ String className = file.getName().substring(0, file.getName().length() - 6); -+ try { -+ classes.add(Class.forName(packageName + '.' + className)); -+ } catch (ClassNotFoundException e) { -+ throw new RuntimeException(e); -+ } -+ } -+ } -+ } -+ } -+ -+ private static void findClassesInPackageByJar(String packageName, Enumeration entries, String packageDirName, Set> classes) { -+ while (entries.hasMoreElements()) { -+ JarEntry entry = entries.nextElement(); -+ String name = entry.getName(); -+ -+ if (name.charAt(0) == '/') { -+ name = name.substring(1); -+ } -+ -+ if (name.startsWith(packageDirName)) { -+ int idx = name.lastIndexOf('/'); -+ -+ if (idx != -1) { -+ packageName = name.substring(0, idx).replace('/', '.'); -+ } -+ -+ if (name.endsWith(".class") && !entry.isDirectory()) { -+ String className = name.substring(packageName.length() + 1, name.length() - 6); -+ try { -+ classes.add(Class.forName(packageName + '.' + className)); -+ } catch (ClassNotFoundException e) { -+ throw new RuntimeException(e); -+ } -+ } -+ } -+ } -+ } -+ -+ /* Register Spark profiler extra server configurations */ -+ -+ private static List buildSparkExtraConfigs() { -+ List extraConfigs = new ArrayList<>(Arrays.asList( -+ "config/leaf-global.yml", -+ "config/gale-global.yml", -+ "config/gale-world-defaults.yml" -+ )); -+ -+ for (World world : Bukkit.getWorlds()) { -+ extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config -+ } -+ -+ return extraConfigs; -+ } -+ -+ private static String[] buildSparkHiddenPaths() { -+ return new String[]{ -+ }; -+ } -+ -+ public static void regSparkExtraConfig() { -+ if (GlobalConfiguration.get().spark.enabled || Bukkit.getServer().getPluginManager().getPlugin("spark") != null) { -+ String extraConfigs = String.join(",", buildSparkExtraConfigs()); -+ String hiddenPaths = String.join(",", buildSparkHiddenPaths()); -+ -+ System.setProperty("spark.serverconfigs.extra", extraConfigs); -+ System.setProperty("spark.serverconfigs.hiddenpaths", hiddenPaths); -+ } -+ } -+ -+ /* Purge and backup old Leaf config & Pufferfish config */ -+ -+ private static void purgeOutdated() { -+ boolean foundLegacy = false; -+ String pufferfishConfig = "pufferfish.yml"; -+ String leafConfigV1 = "leaf.yml"; -+ String leafConfigV2 = "leaf_config"; -+ -+ Date date = new Date(); -+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss"); -+ String backupDir = "config/backup" + dateFormat.format(date) + "/"; -+ -+ File pufferfishConfigFile = new File(pufferfishConfig); -+ File leafConfigV1File = new File(leafConfigV1); -+ File leafConfigV2File = new File(leafConfigV2); -+ File backupDirFile = new File(backupDir); -+ -+ try { -+ if (pufferfishConfigFile.exists() && pufferfishConfigFile.isFile()) { -+ createDirectory(backupDirFile); -+ Files.move(pufferfishConfigFile.toPath(), Path.of(backupDir + pufferfishConfig), StandardCopyOption.REPLACE_EXISTING); -+ foundLegacy = true; -+ } -+ if (leafConfigV1File.exists() && leafConfigV1File.isFile()) { -+ createDirectory(backupDirFile); -+ Files.move(leafConfigV1File.toPath(), Path.of(backupDir + leafConfigV1), StandardCopyOption.REPLACE_EXISTING); -+ foundLegacy = true; -+ } -+ if (leafConfigV2File.exists() && leafConfigV2File.isDirectory()) { -+ createDirectory(backupDirFile); -+ Files.move(leafConfigV2File.toPath(), Path.of(backupDir + leafConfigV2), StandardCopyOption.REPLACE_EXISTING); -+ foundLegacy = true; -+ } -+ -+ if (foundLegacy) { -+ LOGGER.warn("Found legacy Leaf config files, move to backup directory: {}", backupDir); -+ LOGGER.warn("New Leaf config located at config/ folder, You need to transfer config to the new one manually and restart the server!"); -+ } -+ } catch (IOException e) { -+ LOGGER.error("Failed to purge old configs.", e); -+ } -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..794bc822d987ee2bb69b953ea70ae3ee08124ac5 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java -@@ -0,0 +1,137 @@ -+package org.dreeam.leaf.config; -+ -+import io.github.thatsmusic99.configurationmaster.api.ConfigFile; -+import io.github.thatsmusic99.configurationmaster.api.ConfigSection; -+ -+import java.io.File; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+ -+public class LeafGlobalConfig { -+ -+ protected static ConfigFile configFile; -+ private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT); // It will be in uppercase by default, just make sure -+ protected static final boolean isCN = CURRENT_REGION.equals("CN"); -+ -+ public LeafGlobalConfig(boolean init) throws Exception { -+ configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE)); -+ configFile.set("config-version", 3.0); -+ configFile.addComments("config-version", pickStringRegionBased(""" -+ Leaf Config -+ GitHub Repo: https://github.com/Winds-Studio/Leaf -+ Discord: https://discord.com/invite/gfgAwdSEuM""", -+ """ -+ Leaf Config -+ GitHub Repo: https://github.com/Winds-Studio/Leaf -+ QQ Group: 619278377""")); -+ -+ // Pre-structure to force order -+ structureConfig(); -+ } -+ -+ protected void structureConfig() { -+ for (EnumConfigCategory configCate : EnumConfigCategory.getCategoryValues()) { -+ createTitledSection(configCate.name(), configCate.getBaseKeyName()); -+ } -+ } -+ -+ public void saveConfig() throws Exception { -+ configFile.save(); -+ } -+ -+ // Config Utilities -+ -+ public void createTitledSection(String title, String path) { -+ configFile.addSection(title); -+ configFile.addDefault(path, null); -+ } -+ -+ public boolean getBoolean(String path, boolean def, String comment) { -+ configFile.addDefault(path, def, comment); -+ return configFile.getBoolean(path, def); -+ } -+ -+ public boolean getBoolean(String path, boolean def) { -+ configFile.addDefault(path, def); -+ return configFile.getBoolean(path, def); -+ } -+ -+ public String getString(String path, String def, String comment) { -+ configFile.addDefault(path, def, comment); -+ return configFile.getString(path, def); -+ } -+ -+ public String getString(String path, String def) { -+ configFile.addDefault(path, def); -+ return configFile.getString(path, def); -+ } -+ -+ public double getDouble(String path, double def, String comment) { -+ configFile.addDefault(path, def, comment); -+ return configFile.getDouble(path, def); -+ } -+ -+ public double getDouble(String path, double def) { -+ configFile.addDefault(path, def); -+ return configFile.getDouble(path, def); -+ } -+ -+ public int getInt(String path, int def, String comment) { -+ configFile.addDefault(path, def, comment); -+ return configFile.getInteger(path, def); -+ } -+ -+ public int getInt(String path, int def) { -+ configFile.addDefault(path, def); -+ return configFile.getInteger(path, def); -+ } -+ -+ public List getList(String path, List def, String comment) { -+ configFile.addDefault(path, def, comment); -+ return configFile.getStringList(path); -+ } -+ -+ public List getList(String path, List def) { -+ configFile.addDefault(path, def); -+ return configFile.getStringList(path); -+ } -+ -+ public ConfigSection getConfigSection(String path, Map defaultKeyValue) { -+ configFile.addDefault(path, null); -+ configFile.makeSectionLenient(path); -+ defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); -+ return configFile.getConfigSection(path); -+ } -+ -+ public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) { -+ configFile.addDefault(path, null, comment); -+ configFile.makeSectionLenient(path); -+ defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); -+ return configFile.getConfigSection(path); -+ } -+ -+ public void addComment(String path, String comment) { -+ configFile.addComment(path, comment); -+ } -+ -+ public void addCommentIfCN(String path, String comment) { -+ if (isCN) { -+ configFile.addComment(path, comment); -+ } -+ } -+ -+ public void addCommentIfNonCN(String path, String comment) { -+ if (!isCN) { -+ configFile.addComment(path, comment); -+ } -+ } -+ -+ public void addCommentRegionBased(String path, String en, String cn) { -+ configFile.addComment(path, isCN ? cn : en); -+ } -+ -+ public String pickStringRegionBased(String en, String cn) { -+ return isCN ? cn : en; -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java b/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6687584b7bd914ae53f97df592d7a309a7e1d51 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java -@@ -0,0 +1,8 @@ -+package org.dreeam.leaf.config.annotations; -+ -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+ -+@Retention(RetentionPolicy.RUNTIME) -+public @interface DoNotLoad { -+} -diff --git a/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java b/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java -new file mode 100644 -index 0000000000000000000000000000000000000000..26a6967ca8917bbabb31804a44326a1091b9cb96 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java -@@ -0,0 +1,12 @@ -+package org.dreeam.leaf.config.annotations; -+ -+import java.lang.annotation.*; -+ -+/** -+ * Indicates that a feature is experimental and may be removed or changed in the future. -+ */ -+@Documented -+@Retention(RetentionPolicy.RUNTIME) -+@Target(value = {ElementType.FIELD}) -+public @interface Experimental { -+} -diff --git a/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java b/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c89bf6a7ec4bdd94a9ee69eb3907e8e899e6d69b ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java -@@ -0,0 +1,8 @@ -+package org.dreeam.leaf.config.annotations; -+ -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+ -+@Retention(RetentionPolicy.RUNTIME) -+public @interface HotReloadUnsupported { -+} diff --git a/patches/server/0005-Pufferfish-Utils.patch b/patches/server/0005-Pufferfish-Utils.patch deleted file mode 100644 index 83dc77e5..00000000 --- a/patches/server/0005-Pufferfish-Utils.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kevin Raneri -Date: Tue, 9 Nov 2021 23:36:56 -0500 -Subject: [PATCH] Pufferfish: Utils - -Original license: GPL v3 -Original project: https://github.com/pufferfish-gg/Pufferfish - -diff --git a/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8f01ce7f4e260f6e871dcc9cd628094b3184fb9 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java -@@ -0,0 +1,76 @@ -+package gg.pufferfish.pufferfish.util; -+ -+import com.google.common.collect.Queues; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import org.apache.logging.log4j.Marker; -+ -+import java.util.Queue; -+import java.util.concurrent.locks.Condition; -+import java.util.concurrent.locks.Lock; -+import java.util.concurrent.locks.ReentrantLock; -+import java.util.logging.Level; -+ -+public class AsyncExecutor implements Runnable { -+ -+ private final Logger LOGGER = LogManager.getLogger("Leaf"); -+ private final Queue jobs = Queues.newArrayDeque(); -+ private final Lock mutex = new ReentrantLock(); -+ private final Condition cond = mutex.newCondition(); -+ private final Thread thread; -+ private volatile boolean killswitch = false; -+ -+ public AsyncExecutor(String threadName) { -+ this.thread = new Thread(this, threadName); -+ } -+ -+ public void start() { -+ thread.start(); -+ } -+ -+ public void kill() { -+ killswitch = true; -+ cond.signalAll(); -+ } -+ -+ public void submit(Runnable runnable) { -+ mutex.lock(); -+ try { -+ jobs.offer(runnable); -+ cond.signalAll(); -+ } finally { -+ mutex.unlock(); -+ } -+ } -+ -+ @Override -+ public void run() { -+ while (!killswitch) { -+ try { -+ Runnable runnable = takeRunnable(); -+ if (runnable != null) { -+ runnable.run(); -+ } -+ } catch (InterruptedException e) { -+ Thread.currentThread().interrupt(); -+ } catch (Exception e) { -+ LOGGER.error("Failed to execute async job for thread {}", thread.getName(), e); -+ } -+ } -+ } -+ -+ private Runnable takeRunnable() throws InterruptedException { -+ mutex.lock(); -+ try { -+ while (jobs.isEmpty() && !killswitch) { -+ cond.await(); -+ } -+ -+ if (jobs.isEmpty()) return null; // We've set killswitch -+ -+ return jobs.remove(); -+ } finally { -+ mutex.unlock(); -+ } -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b4a51ae41e99d41a1095333c2036efcc9a38973 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java -@@ -0,0 +1,20 @@ -+package gg.pufferfish.pufferfish.util; -+ -+import java.util.Iterator; -+ -+import org.jetbrains.annotations.NotNull; -+ -+public class IterableWrapper implements Iterable { -+ -+ private final Iterator iterator; -+ -+ public IterableWrapper(Iterator iterator) { -+ this.iterator = iterator; -+ } -+ -+ @NotNull -+ @Override -+ public Iterator iterator() { -+ return iterator; -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5578acce073cea8a60619e634b3862624c8a1ae8 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java -@@ -0,0 +1,42 @@ -+package gg.pufferfish.pufferfish.util; -+ -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+ -+import java.util.Map; -+ -+import org.jetbrains.annotations.Nullable; -+ -+public class Long2ObjectOpenHashMapWrapper extends Long2ObjectOpenHashMap { -+ -+ private final Map backingMap; -+ -+ public Long2ObjectOpenHashMapWrapper(Map map) { -+ backingMap = map; -+ } -+ -+ @Override -+ public V put(Long key, V value) { -+ return backingMap.put(key, value); -+ } -+ -+ @Override -+ public V get(Object key) { -+ return backingMap.get(key); -+ } -+ -+ @Override -+ public V remove(Object key) { -+ return backingMap.remove(key); -+ } -+ -+ @Nullable -+ @Override -+ public V putIfAbsent(Long key, V value) { -+ return backingMap.putIfAbsent(key, value); -+ } -+ -+ @Override -+ public int size() { -+ return backingMap.size(); -+ } -+} diff --git a/patches/server/0006-Pufferfish-Sentry.patch b/patches/server/0006-Pufferfish-Sentry.patch deleted file mode 100644 index 0810e721..00000000 --- a/patches/server/0006-Pufferfish-Sentry.patch +++ /dev/null @@ -1,266 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kevin Raneri -Date: Tue, 9 Nov 2021 14:08:14 -0500 -Subject: [PATCH] Pufferfish: Sentry - -Original license: GPL v3 -Original project: https://github.com/pufferfish-gg/Pufferfish - -diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d1d6751cb6e9d6f282d343b58a80ca8640e259a ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java -@@ -0,0 +1,133 @@ -+package gg.pufferfish.pufferfish.sentry; -+ -+import com.google.common.reflect.TypeToken; -+import com.google.gson.Gson; -+import io.sentry.Breadcrumb; -+import io.sentry.Sentry; -+import io.sentry.SentryEvent; -+import io.sentry.SentryLevel; -+import io.sentry.protocol.Message; -+import io.sentry.protocol.User; -+ -+import java.util.Map; -+ -+import org.apache.logging.log4j.Level; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Marker; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.Logger; -+import org.apache.logging.log4j.core.appender.AbstractAppender; -+import org.apache.logging.log4j.core.filter.AbstractFilter; -+import org.dreeam.leaf.config.modules.misc.SentryDSN; -+ -+public class PufferfishSentryAppender extends AbstractAppender { -+ -+ private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class.getSimpleName()); -+ private static final Gson GSON = new Gson(); -+ private final Level logLevel; -+ -+ public PufferfishSentryAppender(Level logLevel) { -+ super("PufferfishSentryAdapter", new SentryFilter(), null); -+ this.logLevel = logLevel; -+ } -+ -+ @Override -+ public void append(LogEvent logEvent) { -+ if (logEvent.getLevel().isMoreSpecificThan(logLevel) && (logEvent.getThrown() != null || !SentryDSN.onlyLogThrown)) { -+ try { -+ logException(logEvent); -+ } catch (Exception e) { -+ logger.warn("Failed to log event with sentry", e); -+ } -+ } else { -+ try { -+ logBreadcrumb(logEvent); -+ } catch (Exception e) { -+ logger.warn("Failed to log event with sentry", e); -+ } -+ } -+ } -+ -+ private void logException(LogEvent e) { -+ SentryEvent event = new SentryEvent(e.getThrown()); -+ -+ Message sentryMessage = new Message(); -+ sentryMessage.setMessage(e.getMessage().getFormattedMessage()); -+ -+ event.setThrowable(e.getThrown()); -+ event.setLevel(getLevel(e.getLevel())); -+ event.setLogger(e.getLoggerName()); -+ event.setTransaction(e.getLoggerName()); -+ event.setExtra("thread_name", e.getThreadName()); -+ -+ boolean hasContext = e.getContextData() != null; -+ -+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) { -+ User user = new User(); -+ user.setId(e.getContextData().getValue("pufferfishsentry_playerid")); -+ user.setUsername(e.getContextData().getValue("pufferfishsentry_playername")); -+ event.setUser(user); -+ } -+ -+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) { -+ event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname")); -+ event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion")); -+ event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname")); -+ } -+ -+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) { -+ Map eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken>() { -+ }.getType()); -+ if (eventFields != null) { -+ event.setExtra("event", eventFields); -+ } -+ } -+ -+ Sentry.captureEvent(event); -+ } -+ -+ private void logBreadcrumb(LogEvent e) { -+ Breadcrumb breadcrumb = new Breadcrumb(); -+ -+ breadcrumb.setLevel(getLevel(e.getLevel())); -+ breadcrumb.setCategory(e.getLoggerName()); -+ breadcrumb.setType(e.getLoggerName()); -+ breadcrumb.setMessage(e.getMessage().getFormattedMessage()); -+ -+ Sentry.addBreadcrumb(breadcrumb); -+ } -+ -+ private SentryLevel getLevel(Level level) { -+ return switch (level.getStandardLevel()) { -+ case TRACE, DEBUG -> SentryLevel.DEBUG; -+ case WARN -> SentryLevel.WARNING; -+ case ERROR -> SentryLevel.ERROR; -+ case FATAL -> SentryLevel.FATAL; -+ default -> SentryLevel.INFO; -+ }; -+ } -+ -+ private static class SentryFilter extends AbstractFilter { -+ -+ @Override -+ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg, -+ Object... params) { -+ return this.filter(logger.getName()); -+ } -+ -+ @Override -+ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) { -+ return this.filter(logger.getName()); -+ } -+ -+ @Override -+ public Result filter(LogEvent event) { -+ return this.filter(event == null ? null : event.getLoggerName()); -+ } -+ -+ private Result filter(String loggerName) { -+ return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY -+ : Result.NEUTRAL; -+ } -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1fc08518f9cc2b6840b5074f181aefc693c3074b ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java -@@ -0,0 +1,44 @@ -+package gg.pufferfish.pufferfish.sentry; -+ -+import io.sentry.Sentry; -+import org.apache.logging.log4j.Level; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+public class SentryManager { -+ -+ private static final Logger logger = LogManager.getLogger(SentryManager.class); -+ -+ private SentryManager() { -+ -+ } -+ -+ private static boolean initialized = false; -+ -+ public static synchronized void init(Level logLevel) { -+ if (initialized) { -+ return; -+ } -+ if (logLevel == null) { -+ logger.error("Invalid log level, defaulting to WARN."); -+ logLevel = Level.WARN; -+ } -+ try { -+ initialized = true; -+ -+ Sentry.init(options -> { -+ options.setDsn(org.dreeam.leaf.config.modules.misc.SentryDSN.sentryDsn); -+ options.setMaxBreadcrumbs(100); -+ }); -+ -+ PufferfishSentryAppender appender = new PufferfishSentryAppender(logLevel); -+ appender.start(); -+ ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender); -+ logger.info("Sentry logging started!"); -+ } catch (Exception e) { -+ logger.warn("Failed to initialize sentry!", e); -+ initialized = false; -+ } -+ } -+ -+} -diff --git a/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/src/main/java/org/dreeam/leaf/config/LeafConfig.java -index fdbdf3f6a5071ce629d2881e861075dbeef11b42..41b24849b6601cdf89ee6cb4125a7127e716c5ee 100644 ---- a/src/main/java/org/dreeam/leaf/config/LeafConfig.java -+++ b/src/main/java/org/dreeam/leaf/config/LeafConfig.java -@@ -1,6 +1,7 @@ - package org.dreeam.leaf.config; - - import io.papermc.paper.configuration.GlobalConfiguration; -+import org.dreeam.leaf.config.modules.misc.SentryDSN; - import org.jetbrains.annotations.Contract; - import org.jetbrains.annotations.NotNull; - -@@ -211,6 +212,7 @@ public class LeafConfig { - - private static String[] buildSparkHiddenPaths() { - return new String[]{ -+ SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key - }; - } - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java b/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0234fc0d3054d8348da49a8006ec99d6d09114fe ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java -@@ -0,0 +1,43 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.apache.logging.log4j.Level; -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class SentryDSN extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".sentry"; -+ } -+ -+ public static String sentryDsnConfigPath; -+ public static String sentryDsn = ""; -+ public static String logLevel = "WARN"; -+ public static boolean onlyLogThrown = true; -+ -+ @Override -+ public void onLoaded() { -+ String sentryEnvironment = System.getenv("SENTRY_DSN"); -+ String sentryConfig = config.getString(sentryDsnConfigPath = getBasePath() + ".dsn", sentryDsn, config.pickStringRegionBased(""" -+ Sentry DSN for improved error logging, leave blank to disable, -+ Obtain from https://sentry.io/""", -+ """ -+ Sentry DSN (出现严重错误时将发送至配置的Sentry DSN地址) (留空关闭)""")); -+ -+ sentryDsn = sentryEnvironment == null -+ ? sentryConfig -+ : sentryEnvironment; -+ logLevel = config.getString(getBasePath() + ".log-level", logLevel, config.pickStringRegionBased(""" -+ Logs with a level higher than or equal to this level will be recorded.""", -+ """ -+ 大于等于该等级的日志会被记录.""")); -+ onlyLogThrown = config.getBoolean(getBasePath() + ".only-log-thrown", onlyLogThrown, config.pickStringRegionBased(""" -+ Only log with a Throwable will be recorded after enabling this.""", -+ """ -+ 是否仅记录带有 Throwable 的日志.""")); -+ -+ if (sentryDsn != null && !sentryDsn.isBlank()) { -+ gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel)); -+ } -+ } -+} diff --git a/patches/server/0007-Pufferfish-Optimize-mob-spawning.patch b/patches/server/0007-Pufferfish-Optimize-mob-spawning.patch deleted file mode 100644 index 890f9b73..00000000 --- a/patches/server/0007-Pufferfish-Optimize-mob-spawning.patch +++ /dev/null @@ -1,256 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kevin Raneri -Date: Wed, 10 Nov 2021 00:37:03 -0500 -Subject: [PATCH] Pufferfish: Optimize mob spawning - -Original license: GPL v3 -Original project: https://github.com/pufferfish-gg/Pufferfish - -Co-authored-by: booky10 - -This patch aims to reduce the main-thread impact of mob spawning by -offloading as much work as possible to other threads. It is possible for -inconsistencies to come up, but when they happen they never interfere -with the server's operation (they don't produce errors), and side -effects are limited to more or less mobs being spawned in any particular -tick. - -It is possible to disable this optimization if it is not required or if -it interferes with any plugins. On servers with thousands of entities, -this can result in performance gains of up to 15%, which is significant -and, in my opinion, worth the low risk of minor mob-spawning-related -inconsistencies. - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -index c21e00812f1aaa1279834a0562d360d6b89e146c..877d2095a066854939f260ca4b0b8c7b5abb620f 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -@@ -18,7 +18,7 @@ public final class IteratorSafeOrderedReferenceSet { - - private final double maxFragFactor; - -- private int iteratorCount; -+ private final java.util.concurrent.atomic.AtomicInteger iteratorCount = new java.util.concurrent.atomic.AtomicInteger(); // Pufferfish - async mob spawning - - public IteratorSafeOrderedReferenceSet() { - this(16, 0.75f, 16, 0.2); -@@ -79,7 +79,7 @@ public final class IteratorSafeOrderedReferenceSet { - } - - public int createRawIterator() { -- ++this.iteratorCount; -+ this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning - if (this.indexMap.isEmpty()) { - return -1; - } else { -@@ -100,7 +100,7 @@ public final class IteratorSafeOrderedReferenceSet { - } - - public void finishRawIterator() { -- if (--this.iteratorCount == 0) { -+ if (this.iteratorCount.decrementAndGet() == 0) { // Pufferfish - async mob spawning - if (this.getFragFactor() >= this.maxFragFactor) { - this.defrag(); - } -@@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet { - throw new IllegalStateException(); - } - this.listElements[index] = null; -- if (this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { -+ if (this.iteratorCount.get() == 0 && this.getFragFactor() >= this.maxFragFactor) { // Pufferfish - async mob spawning - this.defrag(); - } - //this.check(); -@@ -219,7 +219,7 @@ public final class IteratorSafeOrderedReferenceSet { - } - - public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { -- ++this.iteratorCount; -+ this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning - return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); - } - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 40c2dd482a8e10f1d5262d365841c968dda847cc..e084dd8f6e299e2ec71d7d5741c92b0e171f34f6 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -319,6 +319,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping - -+ public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning -+ - public static S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); - Thread thread = new ca.spottedleaf.moonrise.common.util.TickThread(() -> { // Paper - rewrite chunk system -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index b4e2512366226d1132d87124b465071e000d2521..535dc6414c6b14ebd652695615e5a62d82f34171 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -368,6 +368,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.info("JMX monitoring enabled"); - } - -+ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish - return true; - } - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index d6cfff8574567d0e3a77871c91be988ecfe31e25..4a2d941dab7df6158d7df507c18dfb63ec6e4afa 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -173,6 +173,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - } - // Paper end - chunk tick iteration optimisations - -+ public boolean firstRunSpawnCounts = true; // Pufferfish -+ public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs - - public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { - this.level = world; -@@ -486,6 +488,43 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - - this.broadcastChangedChunks(); // Gale - Purpur - remove vanilla profiler - } -+ -+ // Pufferfish start - optimize mob spawning -+ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { -+ for (ServerPlayer player : this.level.players) { -+ // Paper start - per player mob spawning backoff -+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { -+ player.mobCounts[ii] = 0; -+ -+ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? -+ if (newBackoff < 0) { -+ newBackoff = 0; -+ } -+ player.mobBackoffCounts[ii] = newBackoff; -+ } -+ // Paper end - per player mob spawning backoff -+ } -+ if (firstRunSpawnCounts) { -+ firstRunSpawnCounts = false; -+ _pufferfish_spawnCountsReady.set(true); -+ } -+ if (_pufferfish_spawnCountsReady.getAndSet(false)) { -+ net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> { -+ int mapped = distanceManager.getNaturalSpawnChunkCount(); -+ ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator objectiterator = -+ level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); -+ try { -+ gg.pufferfish.pufferfish.util.IterableWrapper wrappedIterator = -+ new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator); -+ lastSpawnState = NaturalSpawner.createState(mapped, wrappedIterator, this::getFullChunk, null, true); -+ } finally { -+ objectiterator.finishedIterating(); -+ } -+ _pufferfish_spawnCountsReady.set(true); -+ }); -+ } -+ } -+ // Pufferfish end - } - - private void broadcastChangedChunks() { // Gale - Purpur - remove vanilla profiler -@@ -537,6 +576,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - // Paper start - Optional per player mob spawns - final int naturalSpawnChunkCount = j; - if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled -+ if (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { // Pufferfish - moved down when async processing - // re-set mob counts - for (ServerPlayer player : this.level.players) { - // Paper start - per player mob spawning backoff -@@ -551,13 +591,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - } - // Paper end - per player mob spawning backoff - } -- spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); -+ lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); // Pufferfish - async mob spawning -+ } // Pufferfish - (endif) moved down when async processing - } else { -- spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); -+ // Pufferfish start - async mob spawning -+ lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); -+ _pufferfish_spawnCountsReady.set(true); -+ // Pufferfish end - } - // Paper end - Optional per player mob spawns - -- this.lastSpawnState = spawnercreature_d; -+ //this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously - // Gale start - MultiPaper - skip unnecessary mob spawning computations - } else { - spawnercreature_d = null; -@@ -577,7 +621,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - // Paper end - PlayerNaturallySpawnCreaturesEvent - boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - -- list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit -+ list1 = NaturalSpawner.getFilteredSpawningCategories(lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit // Pufferfish - } else { - list1 = List.of(); - } -@@ -589,8 +633,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - ChunkPos chunkcoordintpair = chunk.getPos(); - - chunk.incrementInhabitedTime(timeDelta); -- if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot -- NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1); -+ if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot // Pufferfish -+ NaturalSpawner.spawnForChunk(this.level, chunk, lastSpawnState, list1); // Pufferfish - } - - if (true) { // Paper - rewrite chunk system -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index d8b4196adf955f8d414688dc451caac2d9c609d9..80a43def4912a3228cd95117d5c2aac68798b4ec 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -9,7 +9,7 @@ import javax.annotation.Nullable; - import net.minecraft.world.entity.Entity; - - public class EntityTickList { -- private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system -+ public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public - - private void ensureActiveIsNotIterated() { - // Paper - rewrite chunk system -diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9a9cbb8ab1281fa1e8e98ed2d6fec4f68cfd002a ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java -@@ -0,0 +1,34 @@ -+package org.dreeam.leaf.config.modules.async; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class AsyncMobSpawning extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-mob-spawning"; -+ } -+ -+ public static boolean enabled = true; -+ public static boolean asyncMobSpawningInitialized; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Whether or not asynchronous mob spawning should be enabled. -+ On servers with many entities, this can improve performance by up to 15%. You must have -+ paper's per-player-mob-spawns setting set to true for this to work. -+ One quick note - this does not actually spawn mobs async (that would be very unsafe). -+ This just offloads some expensive calculations that are required for mob spawning.""", -+ """ -+ 是否异步化生物生成. -+ 在实体较多的服务器上, 异步生成可最高带来15%的性能提升. -+ 须在Paper配置文件中打开 per-player-mob-spawns 才能生效."""); -+ -+ // This prevents us from changing the value during a reload. -+ if (!asyncMobSpawningInitialized) { -+ asyncMobSpawningInitialized = true; -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled); -+ } -+ } -+} diff --git a/patches/server/0008-Pufferfish-Dynamic-Activation-of-Brain.patch b/patches/server/0008-Pufferfish-Dynamic-Activation-of-Brain.patch deleted file mode 100644 index 5710f4d4..00000000 --- a/patches/server/0008-Pufferfish-Dynamic-Activation-of-Brain.patch +++ /dev/null @@ -1,423 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Paul Sauve -Date: Fri, 15 Jan 2021 19:05:01 -0600 -Subject: [PATCH] Pufferfish: Dynamic Activation of Brain - -Dreeam TODO: waiting Paper dealing with the newGoalRate - -Original license: GPL v3 -Original project: https://github.com/pufferfish-gg/Pufferfish - -This replaces the current method of ticking an inactive entity's -pathfinder 1/4 times with a new method that's dynamic based off how far -away it is from a player. If an entity is within 32 blocks, it gets -ticked every tick. If it's within 45 blocks, it gets ticked every other -tick. If it's within 55 blocks, it gets ticked once every three ticks. -(these numbers have since been changed, but the idea is the same.) - -Airplane -Copyright (C) 2020 Technove LLC - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index c25da6e647401767def8cdbe505964865e8d7820..ea586b6c382ac49ffce0675442cc5dfb5c638628 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -774,6 +774,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - org.spigotmc.ActivationRange.activateEntities(this); // Spigot - this.entityTickList.forEach((entity) -> { -+ entity.activatedPriorityReset = false; // Pufferfish - DAB - if (!entity.isRemoved()) { - if (!tickratemanager.isEntityFrozen(entity)) { - entity.checkDespawn(); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index d9bd4116bd252e6df3b9c717c469cce0fb2e6fde..e9438f3b2acabcb9d3ecd6484704db1a94b21a0e 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -388,6 +388,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public boolean freezeLocked = false; // Paper - Freeze Tick Lock API - public boolean fixedPose = false; // Paper - Expand Pose API - private final int despawnTime; // Paper - entity despawn time limit -+ public boolean activatedPriorityReset = false; // Pufferfish - DAB -+ public int activatedPriority = org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.maximumActivationPrio; // Pufferfish - DAB (golf score) - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index c8c2394558952d7ca57d29874485251b8f2b3400..24eaa9a2e6f0cf198a307058e655d5eb16a2c8c5 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -384,6 +384,7 @@ public class EntityType implements FeatureElement, EntityTypeT - private final boolean canSpawnFarFromPlayer; - private final int clientTrackingRange; - private final int updateInterval; -+ public boolean dabEnabled = false; // Pufferfish - private final String descriptionId; - @Nullable - private Component description; -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 3b10055c589575078bed8c79b3a0967d3f237957..97e63541e76d0dfeb18d69c08114e9652ab30c96 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -237,10 +237,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - @Override - public void inactiveTick() { - super.inactiveTick(); -- if (this.goalSelector.inactiveTick()) { -+ if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity - this.goalSelector.tick(); - } -- if (this.targetSelector.inactiveTick()) { -+ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority - this.targetSelector.tick(); - } - } -@@ -914,10 +914,14 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - int i = this.tickCount + this.getId(); - - if (i % 2 != 0 && this.tickCount > 1) { -+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.targetSelector.tickRunningGoals(false); -+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.goalSelector.tickRunningGoals(false); - } else { -+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.targetSelector.tick(); -+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.goalSelector.tick(); - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java -index 758f62416ca9c02351348ac0d41deeb4624abc0e..69130969c9a434ec2361e573c9a1ec9f462dfda2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java -@@ -36,7 +36,11 @@ public class VillagerPanicTrigger extends Behavior { - - @Override - protected void tick(ServerLevel world, Villager entity, long time) { -- if (time % 100L == 0L) { -+ // Pufferfish start -+ if (entity.nextGolemPanic < 0) entity.nextGolemPanic = time + 100; -+ if (--entity.nextGolemPanic < time) { -+ entity.nextGolemPanic = -1; -+ // Pufferfish end - entity.spawnGolemIfNeeded(world, time, 3); - } - } -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 7ac88cb8704f84f1d932dff0fee927dfab8cad6a..38845c57d724dd08e224f2792caa874ad11268e1 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 -@@ -36,9 +36,13 @@ public class GoalSelector { - } - - // Paper start - EAR 2 -- public boolean inactiveTick() { -+ public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start -+ if (inactive && !org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.enabled) tickRate = 4; // reset to Paper's -+ tickRate = Math.min(tickRate, 3); // Dreeam TODO - Waiting Paper - this.curRate++; -- return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct -+ //return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct -+ return this.curRate % tickRate == 0; -+ // Pufferfish end - } - public boolean hasTasks() { - for (WrappedGoal task : this.availableGoals) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -index 3a89c8bb4fbbe5a3a219e2fbc823c3abe529d1f2..5470b000760728b6703fccab5448a3f62d4335c8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -@@ -217,8 +217,10 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS - return 0.4F; - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - AllayAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index 22dbc237498d765bc9a13f6c5924769122406c8a..dab55d73069018b17769b82a79acb5b8fe62772d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -@@ -290,8 +290,10 @@ public class Axolotl extends Animal implements VariantHolder, B - return true; - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - AxolotlAi.updateActivity(this); - if (!this.isNoAi()) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index 1a71f1d094271b5ce798210562000f6d0160eaa6..e6e12142c24e7fff99423678bc47c2e1d38e8afd 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -@@ -182,8 +182,10 @@ public class Frog extends Animal implements VariantHolder> { - .ifPresent(this::setVariant); - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - FrogAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index 76ecf98d91a368cd223f09bb0c56a50320999735..db617182d75f3418fef4b34cd8eddbfc90fc5a9e 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -81,8 +81,10 @@ public class Tadpole extends AbstractFish { - return SoundEvents.TADPOLE_FLOP; - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - TadpoleAi.updateActivity(this); - super.customServerAiStep(world); -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 f480ee1d0cde254d9621b85b4064771bdab2d3a3..509d1d1497c3f015686dbc485b82649a98356a02 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 -@@ -190,8 +190,10 @@ public class Goat extends Animal { - return (Brain) super.getBrain(); // CraftBukkit - decompile error - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - GoatAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -index 05a2d7a104161bde1dd913e14afb045264a54e87..9802aa9a1e4822684ba4406d2cafa4af69fe2ec5 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -@@ -136,8 +136,10 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - return (Brain) super.getBrain(); // CraftBukkit - decompile error - } - -+ private int behaviorTick; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - HoglinAi.updateActivity(this); - if (this.isConverting()) { -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 ad987660373cf1fd0edb778fa4203e3948b12fd4..aeedddd4b2ade905455c04ce475d35042c54e741 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 -@@ -302,8 +302,10 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - return !this.cannotHunt; - } - -+ private int behaviorTick; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - PiglinAi.updateActivity(this); - super.customServerAiStep(world); -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 9d9d58ab055b5bccedd6ebc9f6853ca8206cde65..64f3204b4b6ac0c57d0eb833a959f666f5259c6b 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 -@@ -273,8 +273,10 @@ public class Warden extends Monster implements VibrationSystem { - - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - super.customServerAiStep(world); - if ((this.tickCount + this.getId()) % 120 == 0) { -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 08c780e9e1e167b84f70dce691bb564c8420b286..bb5a924c203be427e3faf84917b86622fdec5f25 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -140,6 +140,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - return holder.is(PoiTypes.MEETING); - }); - -+ public long nextGolemPanic = -1; // Pufferfish -+ - public Villager(EntityType entityType, Level world) { - this(entityType, world, VillagerType.PLAINS); - } -@@ -243,6 +245,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - // Spigot End - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - // Paper start - EAR 2 -@@ -250,7 +253,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - protected void customServerAiStep(ServerLevel world, final boolean inactive) { - // Paper end - EAR 2 -- if (!inactive) this.getBrain().tick(world, this); -+ // Pufferfish start -+ if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) { -+ this.getBrain().tick(world, this); // Paper -+ } -+ // Pufferfish end - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java b/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7e1e06ff65d1e186e6ec41917945d3d90fe72008 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java -@@ -0,0 +1,81 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.EntityType; -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.LeafConfig; -+ -+import java.util.ArrayList; -+import java.util.List; -+ -+public class DynamicActivationofBrain extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".dab"; -+ } -+ -+ public static boolean enabled = true; -+ public static int startDistance = 12; -+ public static int startDistanceSquared; -+ public static int maximumActivationPrio = 20; -+ public static int activationDistanceMod = 8; -+ public static boolean dontEnableIfInWater = false; -+ public static List blackedEntities = new ArrayList<>(); -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Optimizes entity brains when -+ they're far away from the player""", -+ """ -+ 根据距离动态优化生物 AI"""); -+ -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled); -+ dontEnableIfInWater = config.getBoolean(getBasePath() + ".dont-enable-if-in-water", dontEnableIfInWater, config.pickStringRegionBased(""" -+ After enabling this, non-aquatic entities in the water will not be affected by DAB. -+ This could fix entities suffocate in the water.""", -+ """ -+ 启用此项后, 在水中的非水生生物将不会被 DAB 影响. -+ 可以避免距离玩家较远的生物在水里淹死.""")); -+ startDistance = config.getInt(getBasePath() + ".start-distance", startDistance, config.pickStringRegionBased(""" -+ This value determines how far away an entity has to be -+ from the player to start being effected by DEAR.""", -+ """ -+ 生物距离玩家多少格 DAB 开始生效""")); -+ maximumActivationPrio = config.getInt(getBasePath() + ".max-tick-freq", maximumActivationPrio, config.pickStringRegionBased(""" -+ This value defines how often in ticks, the furthest entity -+ will get their pathfinders and behaviors ticked. 20 = 1s""", -+ """ -+ 最远处的实体每隔多少刻tick一次""")); -+ activationDistanceMod = config.getInt(getBasePath() + ".activation-dist-mod", activationDistanceMod, """ -+ This value defines how much distance modifies an entity's -+ tick frequency. freq = (distanceToPlayer^2) / (2^value)", -+ If you want further away entities to tick less often, use 7. -+ If you want further away entities to tick more often, try 9."""); -+ blackedEntities = config.getList(getBasePath() + ".blacklisted-entities", blackedEntities, -+ config.pickStringRegionBased("A list of entities to ignore for activation", -+ "不会被 DAB 影响的实体列表")); -+ -+ startDistanceSquared = startDistance * startDistance; -+ -+ for (EntityType entityType : BuiltInRegistries.ENTITY_TYPE) { -+ entityType.dabEnabled = true; // reset all, before setting the ones to true -+ } -+ -+ final String DEFAULT_PREFIX = ResourceLocation.DEFAULT_NAMESPACE + ResourceLocation.NAMESPACE_SEPARATOR; -+ -+ for (String name : blackedEntities) { -+ // Be compatible with both `minecraft:example` and `example` syntax -+ // If unknown, show user config value in the logger instead of parsed result -+ String typeId = name.toLowerCase().startsWith(DEFAULT_PREFIX) ? name : DEFAULT_PREFIX + name; -+ -+ EntityType.byString(typeId).ifPresentOrElse(entityType -> -+ entityType.dabEnabled = false, -+ () -> LeafConfig.LOGGER.warn("Skip unknown entity {}, in {}", name, getBasePath() + ".blacklisted-entities") -+ -+ ); -+ } -+ } -+} -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 5d1cc9b9150f84bb84cef5dd741e72787c02f089..62bb1fa77045ea83afe8a181c3b3a4d7284103b7 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -233,6 +233,23 @@ public class ActivationRange - } - // Paper end - Configurable marker ticking - ActivationRange.activateEntity(entity); -+ -+ // Pufferfish start -+ if (org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.enabled && entity.getType().dabEnabled && -+ (!org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.dontEnableIfInWater || entity.getType().is(net.minecraft.tags.EntityTypeTags.CAN_BREATHE_UNDER_WATER) || !entity.isInWaterOrBubble())) { // Leaf - Option for dontEnableIfInWater -+ if (!entity.activatedPriorityReset) { -+ entity.activatedPriorityReset = true; -+ entity.activatedPriority = org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.maximumActivationPrio; -+ } -+ int squaredDistance = (int) player.distanceToSqr(entity); -+ entity.activatedPriority = squaredDistance > org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.startDistanceSquared ? -+ Math.max(1, Math.min(squaredDistance >> org.dreeam.leaf.config.modules.opt.DynamicActivationofBrain.activationDistanceMod, entity.activatedPriority)) : -+ 1; -+ } else { -+ entity.activatedPriority = 1; -+ } -+ // Pufferfish end -+ - } - // Paper end - } -@@ -248,12 +265,12 @@ public class ActivationRange - if ( MinecraftServer.currentTick > entity.activatedTick ) - { - if ( entity.defaultActivationState ) -- { -+ { // Pufferfish - diff on change - entity.activatedTick = MinecraftServer.currentTick; - return; - } - if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) ) -- { -+ { // Pufferfish - diff on change - entity.activatedTick = MinecraftServer.currentTick; - } - } diff --git a/patches/server/0009-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch b/patches/server/0009-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch deleted file mode 100644 index cd88afda..00000000 --- a/patches/server/0009-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kevin Raneri -Date: Sat, 11 Dec 2021 22:20:45 -0500 -Subject: [PATCH] Pufferfish: Throttle goal selector during inactive ticking - -Original license: GPL v3 -Original project: https://github.com/pufferfish-gg/Pufferfish - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index df932757d6a2f6812a1be16a3a71c4c4c75768b5..aa6d06c5b873ae92d7de6c091a8b9d27221ef7f8 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -233,11 +233,13 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - return this.lookControl; - } - -+ int _pufferfish_inactiveTickDisableCounter = 0; // Pufferfish - throttle inactive goal selector ticking - // Paper start - @Override - public void inactiveTick() { - super.inactiveTick(); -- if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity -+ boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking -+ if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking - this.goalSelector.tick(); - } - if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java b/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java -new file mode 100644 -index 0000000000000000000000000000000000000000..785215c05111424bbcd2d9708ed05b35530bb9c5 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleInactiveGoalSelectorTick.java -@@ -0,0 +1,23 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ThrottleInactiveGoalSelectorTick extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".inactive-goal-selector-throttle", enabled, config.pickStringRegionBased(""" -+ Throttles the AI goal selector in entity inactive ticks. -+ This can improve performance by a few percent, but has minor gameplay implications.""", -+ """ -+ 是否在实体不活跃 tick 时阻塞 AI 目标选择器. -+ 有助于提升性能, 但对游戏有轻微影响.""")); -+ } -+} diff --git a/patches/server/0011-Fix-Pufferfish-and-Purpur-patches.patch b/patches/server/0011-Fix-Pufferfish-and-Purpur-patches.patch deleted file mode 100644 index 48c8bef8..00000000 --- a/patches/server/0011-Fix-Pufferfish-and-Purpur-patches.patch +++ /dev/null @@ -1,325 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Mon, 29 Apr 2024 14:18:58 -0400 -Subject: [PATCH] Fix Pufferfish and Purpur patches - - -diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -index 23609a71a993fc91271578ee0a541a9c6ec7354f..298574c83ed88c307b07ca163a97b2f43784401c 100644 ---- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -+++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -@@ -33,6 +33,7 @@ public record ServerBuildInfoImpl( - private static final String BRAND_PAPER_NAME = "Paper"; - private static final String BRAND_GALE_NAME = "Gale"; // Gale - branding changes - private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur -+ private static final String BRAND_PUFFERFISH_NAME = "Pufferfish"; // Leaf - private static final String BRAND_LEAF_NAME = "Leaf"; // Leaf - - private static final String BUILD_DEV = "DEV"; -@@ -66,7 +67,9 @@ public record ServerBuildInfoImpl( - public boolean isBrandCompatible(final @NotNull Key brandId) { - return brandId.equals(this.brandId) - || brandId.equals(BRAND_PAPER_ID) -- || brandId.equals(BRAND_GALE_ID); // Gale - branding changes // Leaf -+ || brandId.equals(BRAND_GALE_ID) // Gale - branding changes // Leaf -+ || brandId.equals(BRAND_PUFFERFISH_ID) // Leaf -+ || brandId.equals(BRAND_PURPUR_ID); // Leaf - } - - @Override -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index a624cd8a0fa9ad05f8f69c50f3fdf961713b1791..b6d930287dd02c08575ffbf3353076c9bb74ecfc 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -309,7 +309,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping - -- public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning -+ public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("Leaf Async Mob Spawn Thread"); // Pufferfish - optimize mob spawning // Leaf - Unify thread name - - public static S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -@@ -1327,9 +1327,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop byteAllowed) { -- ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); -+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); - org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect - return; -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 68f94543395c143234957cd5e33600a0c4b1c87d..ee64667e5c33afd68d4458bffd858c66db4c1421 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -579,13 +579,28 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // Purpur end - - // Purpur start - copied from Mob - API for any mob to burn daylight -+ // Gale start - JettPack - optimize sun burn tick - cache eye blockpos - copied from Mob#isSunBurnTick -+ private BlockPos cached_eye_blockpos; -+ private net.minecraft.world.phys.Vec3 cached_position; -+ // Gale end - JettPack - optimize sun burn tick - cache eye blockpos - copied from Mob#isSunBurnTick -+ - public boolean isSunBurnTick() { - if (this.level().isDay() && !this.level().isClientSide) { -- float f = this.getLightLevelDependentMagicValue(); -- BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); -+ // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - copied from Mob#isSunBurnTick -+ if (this.cached_position != this.position) { -+ this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); -+ this.cached_position = this.position; -+ } -+ -+ float f = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness -+ -+ // Check brightness first -+ if (f <= 0.5F) return false; -+ if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false; -+ // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - copied from Mob#isSunBurnTick - boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; - -- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { -+ if (!flag && this.level().canSeeSky(this.cached_eye_blockpos)) { // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - copied from Mob#isSunBurnTick - return true; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 260e9b0398ddaacacfe5de352ac686ef273a6167..6f3986ba57ce794a1f78b8960a7c8de8a3508c19 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1064,18 +1064,20 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (entity != null) { - EntityType entitytypes = entity.getType(); - -+ // Gale start - Petal - reduce skull ItemStack lookups for reduced visibility - // Purpur start -- if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) { -+ if (entitytypes == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL)) { - d0 *= entity.level().purpurConfig.skeletonHeadVisibilityPercent; -- } else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) { -+ } else if (entitytypes == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD)) { - d0 *= entity.level().purpurConfig.zombieHeadVisibilityPercent; - } -- else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) { -+ else if (entitytypes == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { - d0 *= entity.level().purpurConfig.creeperHeadVisibilityPercent; -- } else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && itemstack.is(Items.PIGLIN_HEAD)) { -+ } else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD)) { - d0 *= entity.level().purpurConfig.piglinHeadVisibilityPercent; - } - // Purpur end -+ // Gale end - Petal - reduce skull ItemStack lookups for reduced visibility - - // Purpur start - if (entity instanceof LivingEntity entityliving) { -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index a2ab53e70328b4ac0d019ebbd3d3cffeeb29d76b..6c603c83d9caeed480f5d67854e40c3fa8ff20dd 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1746,11 +1746,6 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - - protected void playAttackSound() {} - -- // Gale start - JettPack - optimize sun burn tick - cache eye blockpos -- private BlockPos cached_eye_blockpos; -- private net.minecraft.world.phys.Vec3 cached_position; -- // Gale end - JettPack - optimize sun burn tick - cache eye blockpos -- - public boolean isSunBurnTick() { - // Purpur - implemented in Entity - API for any mob to burn daylight - return super.isSunBurnTick(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java -index d548d1b2686667d809f363cd0ae4444bc3918bf2..c3519eb6b28d180c9a5bf673037f1c4324ba5685 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java -@@ -22,19 +22,15 @@ public class SecondaryPoiSensor extends Sensor { - - @Override - protected void doTick(ServerLevel world, Villager entity) { -+ // Purpur start - make sure clerics don't wander to soul sand when the option is off - // Gale start - Lithium - skip secondary POI sensor if absent - var secondaryPoi = entity.getVillagerData().getProfession().secondaryPoi(); -- if (secondaryPoi.isEmpty()) { -- entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); -- return; -- } -- // Gale end - Lithium - skip secondary POI sensor if absent -- // Purpur start - make sure clerics don't wander to soul sand when the option is off - Brain brain = entity.getBrain(); -- if (!world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { -+ if (secondaryPoi.isEmpty() || (!world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC)) { - brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); - return; - } -+ // Gale end - Lithium - skip secondary POI sensor if absent - // Purpur end - ResourceKey resourceKey = world.dimension(); - BlockPos blockPos = entity.blockPosition(); -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 037586c0fdb42a02660aba89dd741a647c67e52b..a3c284976b37e865c51ee91166c4046a3c4f3a16 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -685,7 +685,7 @@ public class ArmorStand extends LivingEntity { - - @Override - public void tick() { -- maxUpStep = level().purpurConfig.armorstandStepHeight; -+ maxUpStep = level().purpurConfig.armorstandStepHeight; // Purpur - // Paper start - Allow ArmorStands not to tick - if (!this.canTick) { - if (this.noTickPoseDirty) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -index 07db4557ab0d7a4a0f5432257bd18195d2de7255..c334f0bdd4d5e1e5468216b13e6f5350d2348349 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -@@ -73,7 +73,6 @@ public class Blaze extends Monster { - setDeltaMovement(mot.scale(0.9D)); - } - } -- // Purpur end - - @Override - public void initAttributes() { -@@ -85,6 +84,7 @@ public class Blaze extends Monster { - protected boolean isAlwaysExperienceDropper() { - return this.level().purpurConfig.blazeAlwaysDropExp; - } -+ // Purpur end - - @Override - protected void registerGoals() { -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 fd373d98f836c057c30c4fbd5d7618cc4e757b78..bd4a96e2b4b539bafe13605e80706311c15263a7 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -354,6 +354,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) { // Purpur - this.getBrain().tick(world, this); // Paper - } -+ else if (this.isLobotomized && shouldRestock()) restock(); // Leaf - Purpur - Fix - // Pufferfish end - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index a7fe724fd2aec7a72781e7b3ab74ff317cec8fbf..fd22b5374fe5de11474f495ea941b8dd10e2d41f 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -83,7 +83,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - int maxChunkLoadsPerProjectile = maxProjectileChunkLoadsConfig.perProjectile.max; - if (maxChunkLoadsPerProjectile >= 0 && this.chunksLoadedByProjectile >= maxChunkLoadsPerProjectile) { - if (maxProjectileChunkLoadsConfig.perProjectile.removeFromWorldAfterReachLimit) { -- this.discard(); -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Purpur - } else if (maxProjectileChunkLoadsConfig.perProjectile.resetMovementAfterReachLimit) { - this.setDeltaMovement(0, this.getDeltaMovement().y, 0); - } -diff --git a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java -index fab5d5af9ec6a20810ce5e437dd617684cc5768f..d0a031014ec410142d59c8edd577bf035b7e407b 100644 ---- a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java -+++ b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java -@@ -101,10 +101,10 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { - // Gale end - branding changes - version fetcher - - return switch (distance) { -- case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW); -- case 0 -> text("You are running the latest version", NamedTextColor.GREEN); -- case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW); -- default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) -+ case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur -+ case 0 -> text("* You are running the latest version", NamedTextColor.GREEN); // Purpur -+ case DISTANCE_UNKNOWN -> text("* Unknown version", NamedTextColor.YELLOW); // Purpur -+ default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur - .append(Component.newline()) - .append(text("Download the new version at: ") - .append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher -@@ -149,6 +149,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { - return null; - } - -- return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); -+ return text("Previous: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); // Purpur - } - } diff --git a/patches/server/0012-Purpur-Configurable-server-mod-name.patch b/patches/server/0012-Purpur-Configurable-server-mod-name.patch deleted file mode 100644 index 6c7f776f..00000000 --- a/patches/server/0012-Purpur-Configurable-server-mod-name.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Mon, 17 Jul 2023 08:31:51 +0800 -Subject: [PATCH] Purpur: Configurable server mod name - -Original license: MIT -Original project: https://github.com/PurpurMC/Purpur - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index b6d930287dd02c08575ffbf3353076c9bb74ecfc..a7a8273a21434fc04b01d06708b65e80487a95d3 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1983,7 +1983,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sun, 14 Jan 2024 05:14:09 -0500 -Subject: [PATCH] Configurable server GUI name - - -diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -index 8f74c2ec5252b6265549589310d742337c91cb2c..8f951e3b6241d040b28d3c8b8e7d7f96ab4d2b8d 100644 ---- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -@@ -56,7 +56,7 @@ public class MinecraftServerGui extends JComponent { - ; - } - -- final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur -+ final JFrame jframe = new JFrame(org.dreeam.leaf.config.modules.misc.ServerBrand.serverGUIName); // Purpur // Leaf - Configurable server GUI name - final MinecraftServerGui servergui = new MinecraftServerGui(server); - - jframe.setDefaultCloseOperation(2); -@@ -64,7 +64,7 @@ public class MinecraftServerGui extends JComponent { - jframe.pack(); - jframe.setLocationRelativeTo((Component) null); - jframe.setVisible(true); -- jframe.setName("Purpur Minecraft server"); // Paper - Improve ServerGUI // Purpur -+ jframe.setName(org.dreeam.leaf.config.modules.misc.ServerBrand.serverGUIName); // Paper - Improve ServerGUI // Purpur // Leaf - Configurable server GUI name - - // Paper start - Improve ServerGUI - try { -@@ -76,7 +76,7 @@ public class MinecraftServerGui extends JComponent { - jframe.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent windowevent) { - if (!servergui.isClosing.getAndSet(true)) { -- jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur -+ jframe.setTitle(org.dreeam.leaf.config.modules.misc.ServerBrand.serverGUIName + " - shutting down!"); // Purpur // Leaf - Configurable server GUI name - server.halt(true); - servergui.runFinalizers(); - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java b/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java -index 15457b1928be0d8f1e1c0f1f1ef62dea54396a05..6e20b63fcc002dd2d7a148d0315113b12441adc9 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java -@@ -10,9 +10,11 @@ public class ServerBrand extends ConfigModules { - } - - public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); -+ public static String serverGUIName = "Leaf Console"; - - @Override - public void onLoaded() { - serverModName = config.getString(getBasePath() + ".server-mod-name", serverModName); -+ serverGUIName = config.getString(getBasePath() + ".server-gui-name", serverGUIName); - } - } diff --git a/patches/server/0015-Bump-Dependencies.patch b/patches/server/0015-Bump-Dependencies.patch deleted file mode 100644 index d4d38d2b..00000000 --- a/patches/server/0015-Bump-Dependencies.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Fri, 28 Oct 2022 17:48:45 -0400 -Subject: [PATCH] Bump Dependencies - -TODO - Dreeam: Bump & test dependencies, impl new features? - -diff --git a/build.gradle.kts b/build.gradle.kts -index 3b0cd45cb07d9563c84901729f1f7edc498653bd..635ec3ef8bd96f73527ee50ca87c8f2e6b8232b2 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -34,38 +34,42 @@ dependencies { - // Leaf end - Leaf Config - - // Paper start -- implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ -- implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 -+ // Leaf start - Bump Dependencies -+ implementation("org.jline:jline-terminal-ffm:3.28.0") // use ffm on java 22+ // Leaf Bu -+ implementation("org.jline:jline-terminal-jni:3.28.0") // fall back to jni on java 21 - implementation("net.minecrell:terminalconsoleappender:1.3.0") -- implementation("net.kyori:adventure-text-serializer-ansi:4.17.0") // Keep in sync with adventureVersion from Paper-API build file -+ implementation("net.kyori:adventure-text-serializer-ansi:4.18.0") // Keep in sync with adventureVersion from Paper-API build file - /* - Required to add the missing Log4j2Plugins.dat file from log4j-core - which has been removed by Mojang. Without it, log4j has to classload - all its classes to check if they are plugins. - Scanning takes about 1-2 seconds so adding this speeds up the server start. - */ -- implementation("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - implementation -- log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins -+ implementation("org.apache.logging.log4j:log4j-core:2.24.3") // Paper - implementation -+ log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.24.3") // Paper - Needed to generate meta for our Log4j plugins - runtimeOnly(log4jPlugins.output) - alsoShade(log4jPlugins.output) -- implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Paper - Add support for proxy protocol -+ implementation("io.netty:netty-codec-haproxy:4.1.116.Final") // Paper - Add support for proxy protocol - // Paper end -- implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion -+ implementation("org.apache.logging.log4j:log4j-iostreams:2.24.3") // Paper - remove exclusion -+ // Leaf end - Bump Dependencies - implementation("org.ow2.asm:asm-commons:9.7.1") - implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files - implementation("commons-lang:commons-lang:2.6") -- runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3") -+ runtimeOnly("org.xerial:sqlite-jdbc:3.47.2.0") // Leaf - Bump Dependencies - runtimeOnly("com.mysql:mysql-connector-j:9.1.0") -- runtimeOnly("com.lmax:disruptor:3.4.4") // Paper -+ runtimeOnly("com.lmax:disruptor:3.4.4") // Paper // Dreeam TODO - Waiting Log4j 3.x to support disruptor 4.0.0 - // Paper start - Use Velocity cipher -- implementation("com.velocitypowered:velocity-native:3.3.0-SNAPSHOT") { -+ implementation("com.velocitypowered:velocity-native:3.4.0-SNAPSHOT") { // Leaf - Bump Dependencies - isTransitive = false - } - // Paper end - Use Velocity cipher - -- runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") -- runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") -- runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") -+ // Leaf start - Bump Dependencies -+ runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.9") -+ runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.22") // Dreeam TODO - Update to 2.0.1 -+ runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.22") // Dreeam TODO - Update to 2.0.1 -+ // Leaf end - - // Purpur start - implementation("org.mozilla:rhino-runtime:1.7.15") -@@ -73,14 +77,16 @@ dependencies { - implementation("dev.omega24:upnp4j:1.0") - // Purpur end - -- testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test -- testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") -- testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") -- testImplementation("org.hamcrest:hamcrest:2.2") -- testImplementation("org.mockito:mockito-core:5.14.1") -- mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions -+ // Leaf start - Bump Dependencies -+ testImplementation("io.github.classgraph:classgraph:4.8.179") // Paper - mob goal test -+ testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") -+ testImplementation("org.junit.platform:junit-platform-suite-engine:1.11.4") -+ testImplementation("org.hamcrest:hamcrest:3.0") -+ testImplementation("org.mockito:mockito-core:5.15.2") -+ mockitoAgent("org.mockito:mockito-core:5.15.2") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions - testImplementation("org.ow2.asm:asm-tree:9.7.1") -- testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest -+ testImplementation("org.junit-pioneer:junit-pioneer:2.3.0") // Paper - CartesianTest -+ // Leaf end - Bump Dependencies - implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling - implementation("net.neoforged:AutoRenamingTool:2.0.3") // Paper - remap plugins - // Paper start - Remap reflection -@@ -93,6 +99,8 @@ dependencies { - implementation("me.lucko:spark-api:0.1-20240720.200737-2") - implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") - // Paper end - spark -+ -+ implementation("io.netty:netty-all:4.1.116.Final") // Leaf - Bump Dependencies // Dreeam TODO - Update to 4.2.0 - } - - paperweight { -@@ -295,3 +303,8 @@ sourceSets { - } - } - // Gale end - package license into jar -+// Leaf start - Bump Dependencies -+repositories { -+ mavenCentral() -+} -+// Leaf end - Bump Dependencies diff --git a/patches/server/0016-Remove-vanilla-username-check.patch b/patches/server/0016-Remove-vanilla-username-check.patch deleted file mode 100644 index 2b4f53d0..00000000 --- a/patches/server/0016-Remove-vanilla-username-check.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Wed, 12 Oct 2022 14:36:58 -0400 -Subject: [PATCH] Remove vanilla username check - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index d5acf191e3f34c84b3711c0ba77e7cb001aee507..0afd30a9a4eb7de7e58e1b91342cca51c771f300 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -186,7 +186,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - // Gale start - JettPack - reduce array allocations - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", me.titaniumtown.ArrayConstants.emptyObjectArray); - // Paper start - Validate usernames -- if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() -+ if (!org.dreeam.leaf.config.modules.misc.RemoveVanillaUsernameCheck.enabled // Leaf - Remove Vanilla username check -+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() - && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation - && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { - Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", me.titaniumtown.ArrayConstants.emptyObjectArray); -diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index 34b4166adfae8ff7d1eb73d56a72931b005330a7..18ecb876bb65d2cf5d8fc999a22712f5731d8fcc 100644 ---- a/src/main/java/net/minecraft/server/players/GameProfileCache.java -+++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java -@@ -82,7 +82,7 @@ public class GameProfileCache { - } - - private static Optional lookupGameProfile(GameProfileRepository repository, String name) { -- if (!StringUtil.isValidPlayerName(name)) { -+ if (!StringUtil.isValidPlayerName(name, false)) { // Leaf start - Remove Vanilla username check - Directly return, skip unnecessary following logic - return GameProfileCache.createUnknownProfile(name); - } else { - final AtomicReference atomicreference = new AtomicReference(); -diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java -index c89fc375aff548a2b03eaf4da3b6a075012df012..68e621ef12d9da67f04086d42360cb08b169aba4 100644 ---- a/src/main/java/net/minecraft/util/StringUtil.java -+++ b/src/main/java/net/minecraft/util/StringUtil.java -@@ -64,6 +64,13 @@ public class StringUtil { - } - - public static boolean isValidPlayerName(String name) { -+ // Leaf start - Remove Vanilla username check -+ return isValidPlayerName(name, org.dreeam.leaf.config.modules.misc.RemoveVanillaUsernameCheck.enabled); -+ } -+ -+ public static boolean isValidPlayerName(String name, boolean bypassCheck) { -+ if (bypassCheck) return name.length() <= 16; -+ // Leaf end- Remove Vanilla username check - return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty(); - } - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java b/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f094806fdc4f98924aab69b5dffb3a3ffbc2e21d ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveVanillaUsernameCheck.java -@@ -0,0 +1,23 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class RemoveVanillaUsernameCheck extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-vanilla-username-check"; -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(""" -+ Remove Vanilla username check, -+ allowing all characters as username.""", -+ """ -+ 移除原版的用户名验证, -+ 让所有字符均可作为玩家名.""")); -+ } -+} diff --git a/patches/server/0017-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch b/patches/server/0017-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch deleted file mode 100644 index 56d59450..00000000 --- a/patches/server/0017-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Wed, 12 Oct 2022 14:48:45 -0400 -Subject: [PATCH] Remove Spigot Check for Broken BungeeCord Configurations - - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index a1e39d4476e932da3d4e0c363ce22eb5be2b8e6c..e4911a420c81a371142f556584cc05a4f76334e1 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -179,7 +179,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - { - this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class); - } -- } else if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { -+ } else if (!org.dreeam.leaf.config.modules.misc.RemoveSpigotCheckBungee.enabled && ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() )) { // Leaf - Remove Spigot check for broken BungeeCord configurations - Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?"); - this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); - this.connection.disconnect(chatmessage); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java b/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f7dad48d09759db5254dc7dbcef64f801a0a1546 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java -@@ -0,0 +1,22 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class RemoveSpigotCheckBungee extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-spigot-check-bungee-config"; -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(""" -+ Enable player enter backend server through proxy -+ without backend server enabling its bungee mode.""", -+ """ -+ 使服务器无需打开 bungee 模式即可让玩家加入后端服务器.""")); -+ } -+} diff --git a/patches/server/0018-Remove-UseItemOnPacket-Too-Far-Check.patch b/patches/server/0018-Remove-UseItemOnPacket-Too-Far-Check.patch deleted file mode 100644 index d8ec9634..00000000 --- a/patches/server/0018-Remove-UseItemOnPacket-Too-Far-Check.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Fri, 18 Nov 2022 23:26:16 -0500 -Subject: [PATCH] Remove UseItemOnPacket Too Far Check - -This Check is added in 1.17.x -> 1.18.x that updated by Mojang. -By removing this check, it gives ability for hackers to use some modules of hack clients. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7434eac75587ed82e60a25145f42ec3e949da672..8018b41a8e36a8b27de369f00e250c7dd9340907 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2027,7 +2027,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - Vec3 vec3d1 = vec3d.subtract(Vec3.atCenterOf(blockposition)); - double d0 = 1.0000001D; - -- if (Math.abs(vec3d1.x()) < 1.0000001D && Math.abs(vec3d1.y()) < 1.0000001D && Math.abs(vec3d1.z()) < 1.0000001D) { -+ if (org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance <= 0 -+ || (Math.abs(vec3d1.x()) < org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance -+ && Math.abs(vec3d1.y()) < org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance -+ && Math.abs(vec3d1.z()) < org.dreeam.leaf.config.modules.gameplay.ConfigurableMaxUseItemDistance.maxUseItemDistance) -+ ) { // Leaf - Remove UseItemOnPacket Too Far Check and make it configurable - Direction enumdirection = movingobjectpositionblock.getDirection(); - - this.player.resetLastActionTime(); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java b/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b1ae10cec5638f65320327ba6bfe112fd25cae8c ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java -@@ -0,0 +1,28 @@ -+package org.dreeam.leaf.config.modules.gameplay; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ConfigurableMaxUseItemDistance extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".player"; -+ } -+ -+ public static double maxUseItemDistance = 1.0000001D; -+ -+ @Override -+ public void onLoaded() { -+ maxUseItemDistance = config.getDouble(getBasePath() + ".max-use-item-distance", maxUseItemDistance, config.pickStringRegionBased(""" -+ The max distance of UseItem for players. -+ Set to -1 to disable max-distance-check. -+ NOTE: if set to -1 to disable the check, -+ players are able to use some packet modules of hack clients, -+ and NoCom Exploit!!""", -+ """ -+ 玩家 UseItem 的最大距离. -+ 设置为 -1 来禁用最大距离检测. -+ 注意: 禁用此项后, -+ 玩家可以使用作弊客户端的部分发包模块和 NoCom 漏洞!!""")); -+ } -+} diff --git a/patches/server/0021-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch b/patches/server/0021-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch deleted file mode 100644 index c443d512..00000000 --- a/patches/server/0021-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nostalgic853 -Date: Sun, 23 Oct 2022 23:21:45 +0800 -Subject: [PATCH] KeYi: Add an option for spigot item merging mechanism - -Original license: MIT -Original project: https://github.com/KeYiMC/KeYi - -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 8162d95c111dc0e05c6993a43e40c3406060e600..186fb46e491bcf5d2a85e68ecf8e13b87381875c 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -357,7 +357,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - ItemStack itemstack1 = other.getItem(); - - if (Objects.equals(this.target, other.target) && ItemEntity.areMergable(itemstack, itemstack1)) { -- if (true || itemstack1.getCount() < itemstack.getCount()) { // Spigot -+ if (org.dreeam.leaf.config.modules.gameplay.UseSpigotItemMergingMech.enabled || itemstack1.getCount() < itemstack.getCount()) { // Spigot // Leaf - KeYi - Configurable spigot item merging mechanism - ItemEntity.merge(this, itemstack, other, itemstack1); - } else { - ItemEntity.merge(other, itemstack1, this, itemstack); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java b/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java -new file mode 100644 -index 0000000000000000000000000000000000000000..25373d9cc5319795b2b572a336d8834b608c5279 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.gameplay; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class UseSpigotItemMergingMech extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".use-spigot-item-merging-mechanism"; -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath(), enabled); -+ } -+} diff --git a/patches/server/0024-Akarin-Save-Json-list-asynchronously.patch b/patches/server/0024-Akarin-Save-Json-list-asynchronously.patch deleted file mode 100644 index 1923762b..00000000 --- a/patches/server/0024-Akarin-Save-Json-list-asynchronously.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?=E3=84=97=E3=84=A0=CB=8B=20=E3=84=91=E3=84=A7=CB=8A?= - -Date: Thu, 5 Jan 2023 09:08:17 +0800 -Subject: [PATCH] Akarin: Save Json list asynchronously - -Original license: GPL v3 -Original project: https://github.com/Akarin-project/Akarin - -diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java -index 97737da3c2f13e1bd29dc119133c7267f5d10117..6030e760c07cc90a06403a364d898a6259a40214 100644 ---- a/src/main/java/net/minecraft/server/players/StoredUserList.java -+++ b/src/main/java/net/minecraft/server/players/StoredUserList.java -@@ -103,6 +103,7 @@ public abstract class StoredUserList> { - } - - public void save() throws IOException { -+ Runnable saveTask = () -> {// Leaf - Akarin - Save json list async - this.removeExpired(); // Paper - remove expired values before saving - JsonArray jsonarray = new JsonArray(); - Stream stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error -@@ -114,6 +115,8 @@ public abstract class StoredUserList> { - - Objects.requireNonNull(jsonarray); - stream.forEach(jsonarray::add); -+ -+ try {// Leaf - Akarin - Save json list async - BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); - - try { -@@ -134,6 +137,13 @@ public abstract class StoredUserList> { - bufferedwriter.close(); - } - -+ // Leaf start - Akarin - Save json list async -+ } catch (Exception e) { -+ StoredUserList.LOGGER.warn("Failed to async save " + this.file, e); -+ } -+ }; -+ io.papermc.paper.util.MCUtil.scheduleAsyncTask(saveTask); -+ // Leaf end - Akarin - Save json list async - } - - public void load() throws IOException { diff --git a/patches/server/0025-Slice-Smooth-Teleports.patch b/patches/server/0025-Slice-Smooth-Teleports.patch deleted file mode 100644 index bcdd4af0..00000000 --- a/patches/server/0025-Slice-Smooth-Teleports.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cryptite -Date: Sat, 13 Aug 2022 08:58:14 -0500 -Subject: [PATCH] Slice: Smooth Teleports - -Original license: MIT -Original project: https://github.com/Cryptite/Slice - -Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 61a990d17adaa9f4144d8a1010e764ae5b4ee2fa..35a73243ce9a27af0c12ef7fb8b1a183f3efaaa5 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -332,6 +332,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - private boolean tpsBar = false; // Purpur - private boolean compassBar = false; // Purpur - private boolean ramBar = false; // Purpur -+ public boolean smoothWorldTeleport; // Slice - - // Paper start - rewrite chunk system - private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 3d6a728c6f9542109e7466d590bb8f015b8c4ac1..e1ceb6added5c5d473cf25162b77c7cf6bf9da49 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -865,10 +865,10 @@ public abstract class PlayerList { - ServerLevel worldserver1 = entityplayer1.serverLevel(); - LevelData worlddata = worldserver1.getLevelData(); - -- entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver1), (byte) i)); -+ if (!entityplayer.smoothWorldTeleport || !isSameLogicalHeight((ServerLevel) fromWorld, worldserver)) entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver1), (byte) i)); // Slice // Leaf - entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot - entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot -- entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit -+ if (!entityplayer.smoothWorldTeleport || !isSameLogicalHeight((ServerLevel) fromWorld, worldserver)) entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit // Slice // Leaf - entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle())); - entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); - entityplayer1.connection.send(new ClientboundSetExperiencePacket(entityplayer1.experienceProgress, entityplayer1.totalExperience, entityplayer1.experienceLevel)); -@@ -931,6 +931,8 @@ public abstract class PlayerList { - return entityplayer1; - } - -+ private boolean isSameLogicalHeight(ServerLevel level1, ServerLevel level2) { return level1.getLogicalHeight() == level2.getLogicalHeight(); } // Leaf - Check world height before smooth teleport -+ - public void sendActivePlayerEffects(ServerPlayer player) { - this.sendActiveEffects(player, player.connection); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index d0bdce91b073344f218c1624cd66fdda5d3e5b0e..414e30430eb7bcb935ef2cc038fcb7c27747bdd4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1366,6 +1366,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper end - Teleportation API - } - -+ // Slice start -+ @Override -+ public void teleportWithoutRespawn(Location location) { -+ ServerPlayer serverPlayer = getHandle(); -+ serverPlayer.smoothWorldTeleport = true; -+ teleport(location); -+ serverPlayer.smoothWorldTeleport = false; -+ } -+ -+ @Override -+ public boolean teleportWithoutRespawnOptionally(Location location) { -+ ServerPlayer serverPlayer = getHandle(); -+ serverPlayer.smoothWorldTeleport = true; -+ boolean teleportResult = teleport(location); -+ serverPlayer.smoothWorldTeleport = false; -+ return teleportResult; -+ } -+ // Slice end -+ - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { - // Paper start - Teleport API diff --git a/patches/server/0027-Leaves-Server-Utils.patch b/patches/server/0027-Leaves-Server-Utils.patch deleted file mode 100644 index 6df13379..00000000 --- a/patches/server/0027-Leaves-Server-Utils.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Tue, 13 Sep 2022 16:59:31 +0800 -Subject: [PATCH] Leaves: Server Utils - -TODO: Check whether Leaves's Return-nether-portal-fix.patch improves performance -and change store way to sql maybe? - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -Commit: 41476d86922416c45f703df2871890831fc42bb5 - -diff --git a/src/main/java/org/leavesmc/leaves/LeavesLogger.java b/src/main/java/org/leavesmc/leaves/LeavesLogger.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bc45935c96553c9bd9d9b6ab3a64e28f52862198 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/LeavesLogger.java -@@ -0,0 +1,24 @@ -+package org.leavesmc.leaves; -+ -+import org.bukkit.Bukkit; -+ -+import java.util.logging.Level; -+import java.util.logging.Logger; -+ -+public class LeavesLogger extends Logger { -+ public static final LeavesLogger LOGGER = new LeavesLogger(); -+ -+ private LeavesLogger() { -+ super("Leaves", null); -+ setParent(Bukkit.getLogger()); -+ setLevel(Level.ALL); -+ } -+ -+ public void severe(String msg, Exception exception) { -+ this.log(Level.SEVERE, msg, exception); -+ } -+ -+ public void warning(String msg, Exception exception) { -+ this.log(Level.WARNING, msg, exception); -+ } -+} diff --git a/patches/server/0028-Leaves-Protocol-Core.patch b/patches/server/0028-Leaves-Protocol-Core.patch deleted file mode 100644 index 2120f22c..00000000 --- a/patches/server/0028-Leaves-Protocol-Core.patch +++ /dev/null @@ -1,763 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Tue, 26 Sep 2023 19:00:41 +0800 -Subject: [PATCH] Leaves: Protocol Core - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -diff --git a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java -index a4f9de18d40d5d85a809af3777b00eb75b1af027..b2048639f652cb16358901052af0c6c68ebf7716 100644 ---- a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java -+++ b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java -@@ -40,13 +40,23 @@ public interface CustomPacketPayload { - - @Override - public void encode(B friendlyByteBuf, CustomPacketPayload customPacketPayload) { -+ // Leaves start - protocol core -+ if (customPacketPayload instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload leavesCustomPayload) { -+ friendlyByteBuf.writeResourceLocation(leavesCustomPayload.id()); -+ leavesCustomPayload.write(friendlyByteBuf); -+ return; -+ } -+ // Leaves end - protocol core - this.writeCap(friendlyByteBuf, customPacketPayload.type(), customPacketPayload); - } - - @Override - public CustomPacketPayload decode(B friendlyByteBuf) { - ResourceLocation resourceLocation = friendlyByteBuf.readResourceLocation(); -- return (CustomPacketPayload)this.findCodec(resourceLocation).decode(friendlyByteBuf); -+ // Leaves start - protocol core -+ var leavesCustomPayload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, friendlyByteBuf); -+ return java.util.Objects.requireNonNullElseGet(leavesCustomPayload, () -> (CustomPacketPayload) this.findCodec(resourceLocation).decode(friendlyByteBuf)); -+ // Leaves end - protocol core - } - }; - } -diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java -index 1967c43ee3a12e63365cc40ee6565307e2fd73cf..6e376d0db5321d8e9b6e0b54617ffd171bf4ee73 100644 ---- a/src/main/java/net/minecraft/resources/ResourceLocation.java -+++ b/src/main/java/net/minecraft/resources/ResourceLocation.java -@@ -36,7 +36,7 @@ public final class ResourceLocation implements Comparable { - private final String namespace; - private final String path; - -- private ResourceLocation(String namespace, String path) { -+ public ResourceLocation(String namespace, String path) { // Leaves - private -> public - assert isValidNamespace(namespace); - - assert isValidPath(path); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2550e0e4e40eccb6e01cd0b8287358c105abaebf..f52eb8f076a007167647e49db2734cd43f88b0b6 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1876,6 +1876,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop leavesPayload) { -+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(player, leavesPayload); -+ } -+ // Leaves end - protocol - // Paper start - Brand support - if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { - this.player.clientBrandName = brandPayload.brand(); -@@ -189,6 +194,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - String channels = payload.toString(com.google.common.base.Charsets.UTF_8); - for (String channel : channels.split("\0")) { - this.getCraftPlayer().addChannel(channel); -+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, player); // Leaves - protocol - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index e1ceb6added5c5d473cf25162b77c7cf6bf9da49..f846281cf11357249aea45e1add3e05150bb5615 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -364,6 +364,8 @@ public abstract class PlayerList { - - player.didPlayerJoinEvent = true; // Gale - EMC - do not process chat/commands before player has joined - -+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol -+ - final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); - - if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure -@@ -557,6 +559,7 @@ public abstract class PlayerList { - return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); - } - public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { -+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(entityplayer); // Leaves - protocol - // Paper end - Fix kick event leave message not being sent - org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur - ServerLevel worldserver = entityplayer.serverLevel(); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b4986e322cb05ad6f98bbf5eee2b571c67e4d9d3..073ad95dc56f3b8d35913058a8eddcd610997124 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -506,6 +506,7 @@ public final class CraftServer implements Server { - this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes - datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper - this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark -+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol - } - - public boolean getCommandBlockOverride(String command) { -@@ -1137,6 +1138,7 @@ public final class CraftServer implements Server { - org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); - this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); -+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // Leaves - protocol - - int pollCount = 0; - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c63bdbd72ecd45b8d5862a996636c0ce1e87166e ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -0,0 +1,15 @@ -+package org.dreeam.leaf.config.modules.network; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ProtocolSupport extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.NETWORK.getBaseKeyName() + ".protocol-support"; -+ } -+ -+ @Override -+ public void onLoaded() { -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b09a7bfe4cbff89acfaf4aec498a5b4e5f911cf6 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java -@@ -0,0 +1,29 @@ -+package org.leavesmc.leaves.protocol.core; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+ -+import java.lang.annotation.ElementType; -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+import java.lang.annotation.Target; -+ -+public interface LeavesCustomPayload> extends CustomPacketPayload { -+ -+ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) -+ @Retention(RetentionPolicy.RUNTIME) -+ @interface New { -+ } -+ -+ void write(FriendlyByteBuf buf); -+ -+ ResourceLocation id(); -+ -+ @Override -+ @NotNull -+ default Type type() { -+ return new CustomPacketPayload.Type<>(id()); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..986d2a6641ff8017dddf3e5f2655adfc2866c119 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java -@@ -0,0 +1,12 @@ -+package org.leavesmc.leaves.protocol.core; -+ -+import java.lang.annotation.ElementType; -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+import java.lang.annotation.Target; -+ -+@Target(ElementType.TYPE) -+@Retention(RetentionPolicy.RUNTIME) -+public @interface LeavesProtocol { -+ String[] namespace(); -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..87b1502c1b980d33cae205ae35d336f7450e5e94 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java -@@ -0,0 +1,435 @@ -+package org.leavesmc.leaves.protocol.core; -+ -+import com.google.common.collect.ImmutableSet; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.chat.Component; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import org.apache.commons.lang.ArrayUtils; -+import org.bukkit.event.player.PlayerKickEvent; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.LeavesLogger; -+ -+import java.io.File; -+import java.io.IOException; -+import java.lang.reflect.Constructor; -+import java.lang.reflect.Executable; -+import java.lang.reflect.InvocationTargetException; -+import java.lang.reflect.Method; -+import java.lang.reflect.Modifier; -+import java.net.JarURLConnection; -+import java.net.URL; -+import java.net.URLDecoder; -+import java.nio.charset.StandardCharsets; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collections; -+import java.util.Enumeration; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.LinkedHashSet; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import java.util.jar.JarEntry; -+import java.util.jar.JarFile; -+ -+public class LeavesProtocolManager { -+ -+ private static final Class[] PAYLOAD_PARAMETER_TYPES = {ResourceLocation.class, FriendlyByteBuf.class}; -+ -+ private static final LeavesLogger LOGGER = LeavesLogger.LOGGER; -+ -+ private static final Map> KNOWN_TYPES = new HashMap<>(); -+ private static final Map> KNOW_RECEIVERS = new HashMap<>(); -+ private static Set ALL_KNOWN_ID = new HashSet<>(); -+ -+ private static final List TICKERS = new ArrayList<>(); -+ private static final List PLAYER_JOIN = new ArrayList<>(); -+ private static final List PLAYER_LEAVE = new ArrayList<>(); -+ private static final List RELOAD_SERVER = new ArrayList<>(); -+ private static final Map> MINECRAFT_REGISTER = new HashMap<>(); -+ -+ public static void init() { -+ for (Class clazz : getClasses("org.leavesmc.leaves.protocol")) { -+ final LeavesProtocol protocol = clazz.getAnnotation(LeavesProtocol.class); -+ if (protocol != null) { -+ Set methods; -+ try { -+ Method[] publicMethods = clazz.getMethods(); -+ Method[] privateMethods = clazz.getDeclaredMethods(); -+ methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0f); -+ Collections.addAll(methods, publicMethods); -+ Collections.addAll(methods, privateMethods); -+ } catch (NoClassDefFoundError error) { -+ LOGGER.severe("Failed to load class " + clazz.getName() + " due to missing dependencies, " + error.getCause() + ": " + error.getMessage()); -+ return; -+ } -+ -+ Map map = KNOWN_TYPES.getOrDefault(protocol, new HashMap<>()); -+ for (final Method method : methods) { -+ if (method.isBridge() || method.isSynthetic() || !Modifier.isStatic(method.getModifiers())) { -+ continue; -+ } -+ -+ method.setAccessible(true); -+ -+ final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class); -+ if (init != null) { -+ try { -+ method.invoke(null); -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.severe("Failed to invoke init method " + method.getName() + " in " + clazz.getName() + ", " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ continue; -+ } -+ -+ final ProtocolHandler.PayloadReceiver receiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class); -+ if (receiver != null) { -+ try { -+ boolean found = false; -+ for (Method payloadMethod : receiver.payload().getDeclaredMethods()) { -+ if (payloadMethod.isAnnotationPresent(LeavesCustomPayload.New.class)) { -+ if (Arrays.equals(payloadMethod.getParameterTypes(), PAYLOAD_PARAMETER_TYPES) && payloadMethod.getReturnType() == receiver.payload() && Modifier.isStatic(payloadMethod.getModifiers())) { -+ payloadMethod.setAccessible(true); -+ map.put(receiver, payloadMethod); -+ found = true; -+ break; -+ } -+ } -+ } -+ -+ if (!found) { -+ Constructor> constructor = receiver.payload().getConstructor(PAYLOAD_PARAMETER_TYPES); -+ if (constructor.isAnnotationPresent(LeavesCustomPayload.New.class)) { -+ constructor.setAccessible(true); -+ map.put(receiver, constructor); -+ } else { -+ throw new NoSuchMethodException(); -+ } -+ } -+ } catch (NoSuchMethodException exception) { -+ LOGGER.severe("Failed to find constructor for " + receiver.payload().getName() + ", " + exception.getCause() + ": " + exception.getMessage()); -+ continue; -+ } -+ -+ if (!KNOW_RECEIVERS.containsKey(protocol)) { -+ KNOW_RECEIVERS.put(protocol, new HashMap<>()); -+ } -+ -+ KNOW_RECEIVERS.get(protocol).put(receiver, method); -+ continue; -+ } -+ -+ final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class); -+ if (ticker != null) { -+ TICKERS.add(method); -+ continue; -+ } -+ -+ final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class); -+ if (playerJoin != null) { -+ PLAYER_JOIN.add(method); -+ continue; -+ } -+ -+ final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class); -+ if (playerLeave != null) { -+ PLAYER_LEAVE.add(method); -+ continue; -+ } -+ -+ final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class); -+ if (reloadServer != null) { -+ RELOAD_SERVER.add(method); -+ continue; -+ } -+ -+ final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class); -+ if (minecraftRegister != null) { -+ if (!MINECRAFT_REGISTER.containsKey(protocol)) { -+ MINECRAFT_REGISTER.put(protocol, new HashMap<>()); -+ } -+ -+ MINECRAFT_REGISTER.get(protocol).put(minecraftRegister, method); -+ } -+ } -+ KNOWN_TYPES.put(protocol, map); -+ } -+ } -+ -+ for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) { -+ Map map = KNOWN_TYPES.get(protocol); -+ for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { -+ if (receiver.sendFabricRegister() && !receiver.ignoreId()) { -+ for (String payloadId : receiver.payloadId()) { -+ for (String namespace : protocol.namespace()) { -+ ALL_KNOWN_ID.add(new ResourceLocation(namespace, payloadId)); -+ } -+ } -+ } -+ } -+ } -+ ALL_KNOWN_ID = ImmutableSet.copyOf(ALL_KNOWN_ID); -+ } -+ -+ public static LeavesCustomPayload decode(ResourceLocation id, FriendlyByteBuf buf) { -+ for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) { -+ if (!ArrayUtils.contains(protocol.namespace(), id.getNamespace())) { -+ continue; -+ } -+ -+ Map map = KNOWN_TYPES.get(protocol); -+ for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { -+ if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), id.getPath())) { -+ try { -+ if (map.get(receiver) instanceof Constructor constructor) { -+ return (LeavesCustomPayload) constructor.newInstance(id, buf); -+ } else if (map.get(receiver) instanceof Method method) { -+ return (LeavesCustomPayload) method.invoke(null, id, buf); -+ } -+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to create payload for " + id + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage()); -+ buf.readBytes(buf.readableBytes()); -+ return new ErrorPayload(id, protocol.namespace(), receiver.payloadId()); -+ } -+ } -+ } -+ } -+ return null; -+ } -+ -+ public static void handlePayload(ServerPlayer player, LeavesCustomPayload payload) { -+ if (payload instanceof ErrorPayload errorPayload) { -+ player.connection.disconnect(Component.literal("Payload " + Arrays.toString(errorPayload.packetID) + " from " + Arrays.toString(errorPayload.protocolID) + " error"), PlayerKickEvent.Cause.INVALID_PAYLOAD); -+ return; -+ } -+ -+ for (LeavesProtocol protocol : KNOW_RECEIVERS.keySet()) { -+ if (!ArrayUtils.contains(protocol.namespace(), payload.type().id().getNamespace())) { -+ continue; -+ } -+ -+ Map map = KNOW_RECEIVERS.get(protocol); -+ for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { -+ if (payload.getClass() == receiver.payload()) { -+ if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), payload.type().id().getPath())) { -+ try { -+ map.get(receiver).invoke(null, player, payload); -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to handle payload " + payload.type().id() + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ public static void handleTick() { -+ if (!TICKERS.isEmpty()) { -+ try { -+ for (Method method : TICKERS) { -+ method.invoke(null); -+ } -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to tick, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ -+ public static void handlePlayerJoin(ServerPlayer player) { -+ if (!PLAYER_JOIN.isEmpty()) { -+ try { -+ for (Method method : PLAYER_JOIN) { -+ method.invoke(null, player); -+ } -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to handle player join, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ -+ ProtocolUtils.sendPayloadPacket(player, new FabricRegisterPayload(ALL_KNOWN_ID)); -+ } -+ -+ public static void handlePlayerLeave(ServerPlayer player) { -+ if (!PLAYER_LEAVE.isEmpty()) { -+ try { -+ for (Method method : PLAYER_LEAVE) { -+ method.invoke(null, player); -+ } -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to handle player leave, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ -+ public static void handleServerReload() { -+ if (!RELOAD_SERVER.isEmpty()) { -+ try { -+ for (Method method : RELOAD_SERVER) { -+ method.invoke(null); -+ } -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to handle server reload, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ -+ public static void handleMinecraftRegister(String channelId, ServerPlayer player) { -+ for (LeavesProtocol protocol : MINECRAFT_REGISTER.keySet()) { -+ String[] channel = channelId.split(":"); -+ if (!ArrayUtils.contains(protocol.namespace(), channel[0])) { -+ continue; -+ } -+ -+ Map map = MINECRAFT_REGISTER.get(protocol); -+ for (ProtocolHandler.MinecraftRegister register : map.keySet()) { -+ if (register.ignoreId() || ArrayUtils.contains(register.channelId(), channel[1])) { -+ try { -+ map.get(register).invoke(null, player, channel[1]); -+ } catch (InvocationTargetException | IllegalAccessException exception) { -+ LOGGER.warning("Failed to handle minecraft register, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ } -+ } -+ -+ public static Set> getClasses(String pack) { -+ Set> classes = new LinkedHashSet<>(); -+ String packageDirName = pack.replace('.', '/'); -+ Enumeration dirs; -+ try { -+ dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); -+ while (dirs.hasMoreElements()) { -+ URL url = dirs.nextElement(); -+ String protocol = url.getProtocol(); -+ if ("file".equals(protocol)) { -+ String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); -+ findClassesInPackageByFile(pack, filePath, classes); -+ } else if ("jar".equals(protocol)) { -+ JarFile jar; -+ try { -+ jar = ((JarURLConnection) url.openConnection()).getJarFile(); -+ Enumeration entries = jar.entries(); -+ findClassesInPackageByJar(pack, entries, packageDirName, classes); -+ } catch (IOException exception) { -+ LOGGER.warning("Failed to load jar file, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ } catch (IOException exception) { -+ LOGGER.warning("Failed to load classes, " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ return classes; -+ } -+ -+ private static void findClassesInPackageByFile(String packageName, String packagePath, Set> classes) { -+ File dir = new File(packagePath); -+ if (!dir.exists() || !dir.isDirectory()) { -+ return; -+ } -+ File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class")); -+ if (dirfiles != null) { -+ for (File file : dirfiles) { -+ if (file.isDirectory()) { -+ findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); -+ } else { -+ String className = file.getName().substring(0, file.getName().length() - 6); -+ try { -+ classes.add(Class.forName(packageName + '.' + className)); -+ } catch (ClassNotFoundException exception) { -+ LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ } -+ } -+ -+ private static void findClassesInPackageByJar(String packageName, Enumeration entries, String packageDirName, Set> classes) { -+ while (entries.hasMoreElements()) { -+ JarEntry entry = entries.nextElement(); -+ String name = entry.getName(); -+ if (name.charAt(0) == '/') { -+ name = name.substring(1); -+ } -+ if (name.startsWith(packageDirName)) { -+ int idx = name.lastIndexOf('/'); -+ if (idx != -1) { -+ packageName = name.substring(0, idx).replace('/', '.'); -+ } -+ if (name.endsWith(".class") && !entry.isDirectory()) { -+ String className = name.substring(packageName.length() + 1, name.length() - 6); -+ try { -+ classes.add(Class.forName(packageName + '.' + className)); -+ } catch (ClassNotFoundException exception) { -+ LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage()); -+ } -+ } -+ } -+ } -+ } -+ -+ public record ErrorPayload(ResourceLocation id, String[] protocolID, String[] packetID) implements LeavesCustomPayload { -+ @Override -+ public void write(@NotNull FriendlyByteBuf buf) { -+ } -+ } -+ -+ public record EmptyPayload(ResourceLocation id) implements LeavesCustomPayload { -+ -+ @New -+ public EmptyPayload(ResourceLocation location, FriendlyByteBuf buf) { -+ this(location); -+ } -+ -+ @Override -+ public void write(@NotNull FriendlyByteBuf buf) { -+ } -+ } -+ -+ public record LeavesPayload(FriendlyByteBuf data, ResourceLocation id) implements LeavesCustomPayload { -+ -+ @New -+ public LeavesPayload(ResourceLocation location, FriendlyByteBuf buf) { -+ this(new FriendlyByteBuf(buf.readBytes(buf.readableBytes())), location); -+ } -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ buf.writeBytes(data); -+ } -+ } -+ -+ public record FabricRegisterPayload( -+ Set channels) implements LeavesCustomPayload { -+ -+ public static final ResourceLocation CHANNEL = ResourceLocation.withDefaultNamespace("register"); -+ -+ @New -+ public FabricRegisterPayload(ResourceLocation location, FriendlyByteBuf buf) { -+ this(buf.readCollection(HashSet::new, FriendlyByteBuf::readResourceLocation)); -+ } -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ boolean first = true; -+ -+ ResourceLocation channel; -+ for (Iterator var3 = this.channels.iterator(); var3.hasNext(); buf.writeBytes(channel.toString().getBytes(StandardCharsets.US_ASCII))) { -+ channel = var3.next(); -+ if (first) { -+ first = false; -+ } else { -+ buf.writeByte(0); -+ } -+ } -+ } -+ -+ @Override -+ public ResourceLocation id() { -+ return CHANNEL; -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..22f47824e0d0ae9050d3a7fd6f59e1d0a7cde561 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java -@@ -0,0 +1,56 @@ -+package org.leavesmc.leaves.protocol.core; -+ -+import java.lang.annotation.ElementType; -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+import java.lang.annotation.Target; -+ -+public class ProtocolHandler { -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface Init { -+ } -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface PayloadReceiver { -+ Class> payload(); -+ -+ String[] payloadId() default ""; -+ -+ boolean ignoreId() default false; -+ -+ boolean sendFabricRegister() default true; -+ } -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface Ticker { -+ int delay() default 0; -+ } -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface PlayerJoin { -+ } -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface PlayerLeave { -+ } -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface ReloadServer { -+ } -+ -+ @Target(ElementType.METHOD) -+ @Retention(RetentionPolicy.RUNTIME) -+ public @interface MinecraftRegister { -+ -+ String[] channelId() default ""; -+ -+ boolean ignoreId() default false; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..72fb1e6517e266146cb1828875e7b7e6ae9b140d ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java -@@ -0,0 +1,52 @@ -+package org.leavesmc.leaves.protocol.core; -+ -+import io.netty.buffer.ByteBuf; -+import io.papermc.paper.ServerBuildInfo; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.function.Consumer; -+import java.util.function.Function; -+ -+public class ProtocolUtils { -+ -+ private static final Function bufDecorator = RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess()); -+ -+ public static String buildProtocolVersion(String protocol) { -+ return protocol + "-leaves-" + ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE); -+ } -+ -+ public static void sendEmptyPayloadPacket(ServerPlayer player, ResourceLocation id) { -+ player.connection.send(new ClientboundCustomPayloadPacket(new LeavesProtocolManager.EmptyPayload(id))); -+ } -+ -+ @SuppressWarnings("all") -+ public static void sendPayloadPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer consumer) { -+ player.connection.send(new ClientboundCustomPayloadPacket(new LeavesCustomPayload() { -+ @Override -+ public void write(@NotNull FriendlyByteBuf buf) { -+ consumer.accept(buf); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return id; -+ } -+ })); -+ } -+ -+ public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) { -+ player.connection.send(new ClientboundCustomPayloadPacket(payload)); -+ } -+ -+ public static RegistryFriendlyByteBuf decorate(ByteBuf buf) { -+ return bufDecorator.apply(buf); -+ } -+} diff --git a/patches/server/0029-Leaves-Jade-Protocol.patch b/patches/server/0029-Leaves-Jade-Protocol.patch deleted file mode 100644 index 771dd08f..00000000 --- a/patches/server/0029-Leaves-Jade-Protocol.patch +++ /dev/null @@ -1,3556 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Sat, 3 Dec 2022 08:57:15 +0800 -Subject: [PATCH] Leaves: Jade Protocol - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -This patch is Powered by Jade (https://github.com/Snownee/Jade) - -diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -index 35e89ecad020a2a5320a757b154fbf409bb2b19b..8e8345d9062b1e666cd1b987c44175741b094d07 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -@@ -61,7 +61,7 @@ public class Armadillo extends Animal { - public final AnimationState rollOutAnimationState = new AnimationState(); - public final AnimationState rollUpAnimationState = new AnimationState(); - public final AnimationState peekAnimationState = new AnimationState(); -- private int scuteTime; -+ public int scuteTime; // Leaves - private -> public - private boolean peekReceivedClient = false; - - public Armadillo(EntityType type, Level world) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index 2b3e722a5c802fbbebb339df4398905e144fd174..72a858ac4ebcfa7520a74bd52501521af64ffcab 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -277,7 +277,7 @@ public class Tadpole extends AbstractFish { - - } - -- private int getTicksLeftUntilAdult() { -+ public int getTicksLeftUntilAdult() { // Leaves - private -> public - return Math.max(0, Tadpole.ticksToBeFrog - this.age); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -index a49f83784f85f5420091692aae588ef067aa5fcd..137f777d324d0a0addc2e43913e196bc9f09e004 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -@@ -73,7 +73,7 @@ public class TrialSpawnerData { - }); - public final Set detectedPlayers; - public final Set currentMobs; -- protected long cooldownEndsAt; -+ public long cooldownEndsAt; // Leaves - protected -> public - protected long nextMobSpawnsAt; - protected int totalMobsSpawned; - public Optional nextSpawnData; -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java b/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java -index 38078c44b35e917d1d243a5f8599aa858d8611de..13dbadfb50278b79b33d9dce10413c93c9e4ff31 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java -@@ -36,7 +36,7 @@ public class LootPool { - ) - .apply(instance, LootPool::new) - ); -- private final List entries; -+ public final List entries; // Leaves - private -> public - private final List conditions; - private final Predicate compositeCondition; - private final List functions; -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 c2bded5094097f5615a2ddb0718942486ede93b5..78fd2b33174a0c8b1dec99009bc2c8d41f6cbfe0 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 -@@ -58,7 +58,7 @@ public class LootTable { - public static final Codec> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, LootTable.DIRECT_CODEC); - private final ContextKeySet paramSet; - private final Optional randomSequence; -- private final List pools; -+ public final List pools; // Leaves - private -> public - private final List functions; - private final BiFunction compositeFunction; - public CraftLootTable craftLootTable; // CraftBukkit -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java -index 128792f76f02d74c1ccf84beb8e7973453424639..775fbddf3e3b133e33f54aaa8e8a07d131095e34 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java -@@ -9,7 +9,7 @@ import net.minecraft.world.level.storage.loot.ValidationContext; - import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; - - public abstract class CompositeEntryBase extends LootPoolEntryContainer { -- protected final List children; -+ public final List children; // Leaves - private -> public - private final ComposableEntryContainer composedChildren; - - protected CompositeEntryBase(List terms, List conditions) { -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java -index 1d2f2bb352abf6772cd20293575fc79e8e64ce3b..b157dfaf1efffebd3f2ae8cb8fcf0972fe742258 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java -@@ -13,7 +13,7 @@ import net.minecraft.world.level.storage.loot.predicates.ConditionUserBuilder; - import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; - - public abstract class LootPoolEntryContainer implements ComposableEntryContainer { -- protected final List conditions; -+ public final List conditions; // Leaves - private -> public - private final Predicate compositeCondition; - - protected LootPoolEntryContainer(List conditions) { -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java -index 61d47a1e86a26bdee49d0ae931aeb92417ab02c2..1116f194b457ef736bf9e836c190817610008ef6 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java -@@ -25,7 +25,7 @@ public class NestedLootTable extends LootPoolSingletonContainer { - .and(singletonFields(instance)) - .apply(instance, NestedLootTable::new) - ); -- private final Either, LootTable> contents; -+ public final Either, LootTable> contents; // Leaves - private -> public - - private NestedLootTable( - Either, LootTable> value, int weight, int quality, List conditions, List functions -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java b/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java -index 30d0133a42ce990352f5c492fcf9beb105364848..1ab2eab686b3a89d406f127a6036c0e2932db4a6 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java -@@ -11,7 +11,7 @@ import net.minecraft.world.level.storage.loot.LootContext; - import net.minecraft.world.level.storage.loot.ValidationContext; - - public abstract class CompositeLootItemCondition implements LootItemCondition { -- protected final List terms; -+ public final List terms; // Leaves - private -> public - private final Predicate composedPredicate; - - protected CompositeLootItemCondition(List terms, Predicate predicate) { -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index c63bdbd72ecd45b8d5862a996636c0ce1e87166e..4e91895387b214cadc658b150ed6b88efb58d6cd 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -9,7 +9,10 @@ public class ProtocolSupport extends ConfigModules { - return EnumConfigCategory.NETWORK.getBaseKeyName() + ".protocol-support"; - } - -+ public static boolean jadeProtocol = false; -+ - @Override - public void onLoaded() { -+ jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); - } - } -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3f21db1707c961242090f2edd54b72f233c59d79 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java -@@ -0,0 +1,279 @@ -+package org.leavesmc.leaves.protocol.jade; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.AgeableMob; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Chicken; -+import net.minecraft.world.entity.animal.allay.Allay; -+import net.minecraft.world.entity.animal.armadillo.Armadillo; -+import net.minecraft.world.entity.animal.frog.Tadpole; -+import net.minecraft.world.entity.monster.ZombieVillager; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.CampfireBlock; -+import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; -+import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; -+import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; -+import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; -+import net.minecraft.world.level.block.entity.CommandBlockEntity; -+import net.minecraft.world.level.block.entity.ComparatorBlockEntity; -+import net.minecraft.world.level.block.entity.HopperBlockEntity; -+import net.minecraft.world.level.block.entity.JukeboxBlockEntity; -+import net.minecraft.world.level.block.entity.LecternBlockEntity; -+import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.protocol.core.LeavesProtocol; -+import org.leavesmc.leaves.protocol.core.ProtocolHandler; -+import org.leavesmc.leaves.protocol.core.ProtocolUtils; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.payload.ReceiveDataPayload; -+import org.leavesmc.leaves.protocol.jade.payload.RequestBlockPayload; -+import org.leavesmc.leaves.protocol.jade.payload.RequestEntityPayload; -+import org.leavesmc.leaves.protocol.jade.payload.ServerPingPayload; -+import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; -+import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; -+import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; -+import org.leavesmc.leaves.protocol.jade.provider.ItemStorageExtensionProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.BeehiveProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.BrewingStandProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.CampfireProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.ChiseledBookshelfProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.CommandBlockProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.FurnaceProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.HopperLockProvider; -+import org.leavesmc.leaves.protocol.jade.provider.ItemStorageProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.JukeboxProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.LecternProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.MobSpawnerCooldownProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.ObjectNameProvider; -+import org.leavesmc.leaves.protocol.jade.provider.block.RedstoneProvider; -+import org.leavesmc.leaves.protocol.jade.provider.entity.AnimalOwnerProvider; -+import org.leavesmc.leaves.protocol.jade.provider.entity.MobBreedingProvider; -+import org.leavesmc.leaves.protocol.jade.provider.entity.MobGrowthProvider; -+import org.leavesmc.leaves.protocol.jade.provider.entity.NextEntityDropProvider; -+import org.leavesmc.leaves.protocol.jade.provider.entity.StatusEffectsProvider; -+import org.leavesmc.leaves.protocol.jade.provider.entity.ZombieVillagerProvider; -+import org.leavesmc.leaves.protocol.jade.util.HierarchyLookup; -+import org.leavesmc.leaves.protocol.jade.util.LootTableMineableCollector; -+import org.leavesmc.leaves.protocol.jade.util.PairHierarchyLookup; -+import org.leavesmc.leaves.protocol.jade.util.PriorityStore; -+import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup; -+ -+import java.util.Collections; -+import java.util.List; -+ -+@LeavesProtocol(namespace = "jade") -+public class JadeProtocol { -+ -+ public static PriorityStore priorities; -+ private static List shearableBlocks = null; -+ -+ public static final String PROTOCOL_ID = "jade"; -+ -+ public static final HierarchyLookup> entityDataProviders = new HierarchyLookup<>(Entity.class); -+ public static final PairHierarchyLookup> blockDataProviders = new PairHierarchyLookup<>(new HierarchyLookup<>(Block.class), new HierarchyLookup<>(BlockEntity.class)); -+ public static final WrappedHierarchyLookup> itemStorageProviders = WrappedHierarchyLookup.forAccessor(); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return new ResourceLocation(PROTOCOL_ID, path); -+ } -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation mc_id(String path) { -+ return ResourceLocation.withDefaultNamespace(path); -+ } -+ -+ @ProtocolHandler.Init -+ public static void init() { -+ priorities = new PriorityStore<>(IJadeProvider::getDefaultPriority, IJadeProvider::getUid); -+ -+ // core plugin -+ blockDataProviders.register(BlockEntity.class, ObjectNameProvider.ForBlock.INSTANCE); -+ -+ // universal plugin -+ entityDataProviders.register(Entity.class, ItemStorageProvider.getEntity()); -+ blockDataProviders.register(Block.class, ItemStorageProvider.getBlock()); -+ -+ itemStorageProviders.register(Object.class, ItemStorageExtensionProvider.INSTANCE); -+ itemStorageProviders.register(Block.class, ItemStorageExtensionProvider.INSTANCE); -+ -+ // vanilla plugin -+ entityDataProviders.register(Entity.class, AnimalOwnerProvider.INSTANCE); -+ entityDataProviders.register(LivingEntity.class, StatusEffectsProvider.INSTANCE); -+ entityDataProviders.register(AgeableMob.class, MobGrowthProvider.INSTANCE); -+ entityDataProviders.register(Tadpole.class, MobGrowthProvider.INSTANCE); -+ entityDataProviders.register(Animal.class, MobBreedingProvider.INSTANCE); -+ entityDataProviders.register(Allay.class, MobBreedingProvider.INSTANCE); -+ -+ entityDataProviders.register(Chicken.class, NextEntityDropProvider.INSTANCE); -+ entityDataProviders.register(Armadillo.class, NextEntityDropProvider.INSTANCE); -+ -+ entityDataProviders.register(ZombieVillager.class, ZombieVillagerProvider.INSTANCE); -+ -+ blockDataProviders.register(BrewingStandBlockEntity.class, BrewingStandProvider.INSTANCE); -+ blockDataProviders.register(BeehiveBlockEntity.class, BeehiveProvider.INSTANCE); -+ blockDataProviders.register(CommandBlockEntity.class, CommandBlockProvider.INSTANCE); -+ blockDataProviders.register(JukeboxBlockEntity.class, JukeboxProvider.INSTANCE); -+ blockDataProviders.register(LecternBlockEntity.class, LecternProvider.INSTANCE); -+ -+ blockDataProviders.register(ComparatorBlockEntity.class, RedstoneProvider.INSTANCE); -+ blockDataProviders.register(HopperBlockEntity.class, HopperLockProvider.INSTANCE); -+ blockDataProviders.register(CalibratedSculkSensorBlockEntity.class, RedstoneProvider.INSTANCE); -+ -+ blockDataProviders.register(AbstractFurnaceBlockEntity.class, FurnaceProvider.INSTANCE); -+ blockDataProviders.register(ChiseledBookShelfBlockEntity.class, ChiseledBookshelfProvider.INSTANCE); -+ blockDataProviders.register(TrialSpawnerBlockEntity.class, MobSpawnerCooldownProvider.INSTANCE); -+ -+ blockDataProviders.idMapped(); -+ entityDataProviders.idMapped(); -+ itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE); -+ -+ blockDataProviders.loadComplete(priorities); -+ entityDataProviders.loadComplete(priorities); -+ itemStorageProviders.loadComplete(priorities); -+ -+ try { -+ shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute( -+ MinecraftServer.getServer().reloadableRegistries().lookup().lookupOrThrow(Registries.LOOT_TABLE), -+ Items.SHEARS.getDefaultInstance() -+ )); -+ } catch (Throwable ignore) { -+ shearableBlocks = List.of(); -+ LeavesLogger.LOGGER.severe("Failed to collect shearable blocks"); -+ } -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerJoin(ServerPlayer player) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ return; -+ } -+ -+ sendPingPacket(player); -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class, payloadId = "request_entity") -+ public static void requestEntityData(ServerPlayer player, RequestEntityPayload payload) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ return; -+ } -+ -+ MinecraftServer.getServer().execute(() -> { -+ EntityAccessor accessor = payload.data().unpack(player); -+ if (accessor == null) { -+ return; -+ } -+ -+ Entity entity = accessor.getEntity(); -+ double maxDistance = Mth.square(player.entityInteractionRange() + 21); -+ if (entity == null || player.distanceToSqr(entity) > maxDistance) { -+ return; -+ } -+ -+ List> providers = entityDataProviders.get(entity); -+ if (providers.isEmpty()) { -+ return; -+ } -+ -+ CompoundTag tag = new CompoundTag(); -+ for (IServerDataProvider provider : providers) { -+ if (!payload.dataProviders().contains(provider)) { -+ continue; -+ } -+ try { -+ provider.appendServerData(tag, accessor); -+ } catch (Exception e) { -+ LeavesLogger.LOGGER.warning("Error while saving data for entity " + entity); -+ } -+ } -+ tag.putInt("EntityId", entity.getId()); -+ -+ ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag)); -+ }); -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class, payloadId = "request_block") -+ public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ return; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ server.execute(() -> { -+ BlockAccessor accessor = payload.data().unpack(player); -+ if (accessor == null) { -+ return; -+ } -+ -+ BlockPos pos = accessor.getPosition(); -+ Block block = accessor.getBlock(); -+ BlockEntity blockEntity = accessor.getBlockEntity(); -+ double maxDistance = Mth.square(player.blockInteractionRange() + 21); -+ if (pos.distSqr(player.blockPosition()) > maxDistance || !accessor.getLevel().isLoaded(pos)) { -+ return; -+ } -+ -+ List> providers; -+ if (blockEntity != null) { -+ providers = blockDataProviders.getMerged(block, blockEntity); -+ } else { -+ providers = blockDataProviders.first.get(block); -+ } -+ -+ if (providers.isEmpty()) { -+ return; -+ } -+ -+ CompoundTag tag = new CompoundTag(); -+ for (IServerDataProvider provider : providers) { -+ if (!payload.dataProviders().contains(provider)) { -+ continue; -+ } -+ try { -+ provider.appendServerData(tag, accessor); -+ } catch (Exception e) { -+ LeavesLogger.LOGGER.warning("Error while saving data for block " + accessor.getBlockState()); -+ } -+ } -+ tag.putInt("x", pos.getX()); -+ tag.putInt("y", pos.getY()); -+ tag.putInt("z", pos.getZ()); -+ tag.putString("BlockId", BuiltInRegistries.BLOCK.getKey(block).toString()); -+ -+ ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag)); -+ }); -+ } -+ -+ @ProtocolHandler.ReloadServer -+ public static void onServerReload() { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ enableAllPlayer(); -+ } -+ } -+ -+ public static void enableAllPlayer() { -+ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().players) { -+ sendPingPacket(player); -+ } -+ } -+ -+ public static void sendPingPacket(ServerPlayer player) { -+ ProtocolUtils.sendPayloadPacket(player, new ServerPingPayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds())); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1a637045d9375ae357ca1e592ec0dc7b45fa47fa ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java -@@ -0,0 +1,32 @@ -+package org.leavesmc.leaves.protocol.jade.accessor; -+ -+import net.minecraft.nbt.Tag; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamEncoder; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.HitResult; -+import org.jetbrains.annotations.Nullable; -+ -+public interface Accessor { -+ -+ Level getLevel(); -+ -+ Player getPlayer(); -+ -+ Tag encodeAsNbt(StreamEncoder codec, D value); -+ -+ T getHitResult(); -+ -+ /** -+ * @return {@code true} if the dedicated server has Jade installed. -+ */ -+ boolean isServerConnected(); -+ -+ boolean showDetails(); -+ -+ @Nullable -+ Object getTarget(); -+ -+ float tickRate(); -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a04fbb45b466d8999b40717d8d48f650b81fe82a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java -@@ -0,0 +1,83 @@ -+package org.leavesmc.leaves.protocol.jade.accessor; -+ -+import java.util.function.Supplier; -+ -+import org.apache.commons.lang3.ArrayUtils; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.nbt.ByteArrayTag; -+import net.minecraft.nbt.Tag; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamEncoder; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.HitResult; -+ -+public abstract class AccessorImpl implements Accessor { -+ -+ private final Level level; -+ private final Player player; -+ private final Supplier hit; -+ private final boolean serverConnected; -+ private final boolean showDetails; -+ protected boolean verify; -+ private RegistryFriendlyByteBuf buffer; -+ -+ public AccessorImpl(Level level, Player player, Supplier hit, boolean serverConnected, boolean showDetails) { -+ this.level = level; -+ this.player = player; -+ this.hit = hit; -+ this.serverConnected = serverConnected; -+ this.showDetails = showDetails; -+ } -+ -+ @Override -+ public Level getLevel() { -+ return level; -+ } -+ -+ @Override -+ public Player getPlayer() { -+ return player; -+ } -+ -+ private RegistryFriendlyByteBuf buffer() { -+ if (buffer == null) { -+ buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), level.registryAccess()); -+ } -+ buffer.clear(); -+ return buffer; -+ } -+ -+ @Override -+ public Tag encodeAsNbt(StreamEncoder streamCodec, D value) { -+ RegistryFriendlyByteBuf buffer = buffer(); -+ streamCodec.encode(buffer, value); -+ ByteArrayTag tag = new ByteArrayTag(ArrayUtils.subarray(buffer.array(), 0, buffer.readableBytes())); -+ buffer.clear(); -+ return tag; -+ } -+ -+ @Override -+ public T getHitResult() { -+ return hit.get(); -+ } -+ -+ /** -+ * Returns true if dedicated server has Jade installed. -+ */ -+ @Override -+ public boolean isServerConnected() { -+ return serverConnected; -+ } -+ -+ @Override -+ public boolean showDetails() { -+ return showDetails; -+ } -+ -+ @Override -+ public float tickRate() { -+ return getLevel().tickRateManager().tickrate(); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..12d689ca80887dcd5dbf68ea2c38a8adcc5ddee4 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java -@@ -0,0 +1,50 @@ -+package org.leavesmc.leaves.protocol.jade.accessor; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.phys.BlockHitResult; -+import org.jetbrains.annotations.ApiStatus; -+ -+import java.util.function.Supplier; -+ -+public interface BlockAccessor extends Accessor { -+ -+ Block getBlock(); -+ -+ BlockState getBlockState(); -+ -+ BlockEntity getBlockEntity(); -+ -+ BlockPos getPosition(); -+ -+ Direction getSide(); -+ -+ @ApiStatus.NonExtendable -+ interface Builder { -+ Builder level(Level level); -+ -+ Builder player(Player player); -+ -+ Builder showDetails(boolean showDetails); -+ -+ Builder hit(BlockHitResult hit); -+ -+ Builder blockState(BlockState state); -+ -+ default Builder blockEntity(BlockEntity blockEntity) { -+ return blockEntity(() -> blockEntity); -+ } -+ -+ Builder blockEntity(Supplier blockEntity); -+ -+ Builder from(BlockAccessor accessor); -+ -+ BlockAccessor build(); -+ } -+ -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9e4a321b91a8afc480ef506487608255db2b9c89 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java -@@ -0,0 +1,163 @@ -+package org.leavesmc.leaves.protocol.jade.accessor; -+ -+import java.util.function.Supplier; -+ -+import org.jetbrains.annotations.Nullable; -+ -+import com.google.common.base.Suppliers; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.phys.BlockHitResult; -+ -+/** -+ * Class to get information of block target and context. -+ */ -+public class BlockAccessorImpl extends AccessorImpl implements BlockAccessor { -+ -+ private final BlockState blockState; -+ @Nullable -+ private final Supplier blockEntity; -+ -+ private BlockAccessorImpl(Builder builder) { -+ super(builder.level, builder.player, Suppliers.ofInstance(builder.hit), builder.connected, builder.showDetails); -+ blockState = builder.blockState; -+ blockEntity = builder.blockEntity; -+ } -+ -+ @Override -+ public Block getBlock() { -+ return getBlockState().getBlock(); -+ } -+ -+ @Override -+ public BlockState getBlockState() { -+ return blockState; -+ } -+ -+ @Override -+ public BlockEntity getBlockEntity() { -+ return blockEntity == null ? null : blockEntity.get(); -+ } -+ -+ @Override -+ public BlockPos getPosition() { -+ return getHitResult().getBlockPos(); -+ } -+ -+ @Override -+ public Direction getSide() { -+ return getHitResult().getDirection(); -+ } -+ -+ @Nullable -+ @Override -+ public Object getTarget() { -+ return getBlockEntity(); -+ } -+ -+ public static class Builder implements BlockAccessor.Builder { -+ -+ private Level level; -+ private Player player; -+ private boolean connected; -+ private boolean showDetails; -+ private BlockHitResult hit; -+ private BlockState blockState = Blocks.AIR.defaultBlockState(); -+ private Supplier blockEntity; -+ -+ @Override -+ public Builder level(Level level) { -+ this.level = level; -+ return this; -+ } -+ -+ @Override -+ public Builder player(Player player) { -+ this.player = player; -+ return this; -+ } -+ -+ @Override -+ public Builder showDetails(boolean showDetails) { -+ this.showDetails = showDetails; -+ return this; -+ } -+ -+ @Override -+ public Builder hit(BlockHitResult hit) { -+ this.hit = hit; -+ return this; -+ } -+ -+ @Override -+ public Builder blockState(BlockState blockState) { -+ this.blockState = blockState; -+ return this; -+ } -+ -+ @Override -+ public Builder blockEntity(Supplier blockEntity) { -+ this.blockEntity = blockEntity; -+ return this; -+ } -+ -+ @Override -+ public Builder from(BlockAccessor accessor) { -+ level = accessor.getLevel(); -+ player = accessor.getPlayer(); -+ connected = accessor.isServerConnected(); -+ showDetails = accessor.showDetails(); -+ hit = accessor.getHitResult(); -+ blockEntity = accessor::getBlockEntity; -+ blockState = accessor.getBlockState(); -+ return this; -+ } -+ -+ @Override -+ public BlockAccessor build() { -+ return new BlockAccessorImpl(this); -+ } -+ } -+ -+ public record SyncData(boolean showDetails, BlockHitResult hit, BlockState blockState, ItemStack fakeBlock) { -+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite( -+ ByteBufCodecs.BOOL, -+ SyncData::showDetails, -+ StreamCodec.of(FriendlyByteBuf::writeBlockHitResult, FriendlyByteBuf::readBlockHitResult), -+ SyncData::hit, -+ ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), -+ SyncData::blockState, -+ ItemStack.OPTIONAL_STREAM_CODEC, -+ SyncData::fakeBlock, -+ SyncData::new -+ ); -+ -+ public BlockAccessor unpack(ServerPlayer player) { -+ Supplier blockEntity = null; -+ if (blockState.hasBlockEntity()) { -+ blockEntity = Suppliers.memoize(() -> player.level().getBlockEntity(hit.getBlockPos())); -+ } -+ return new Builder() -+ .level(player.level()) -+ .player(player) -+ .showDetails(showDetails) -+ .hit(hit) -+ .blockState(blockState) -+ .blockEntity(blockEntity) -+ .build(); -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..454360d5e5c01cad3c197b078d536a9f34e2c6a2 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java -@@ -0,0 +1,44 @@ -+package org.leavesmc.leaves.protocol.jade.accessor; -+ -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.EntityHitResult; -+import org.jetbrains.annotations.ApiStatus; -+ -+import java.util.function.Supplier; -+ -+public interface EntityAccessor extends Accessor { -+ -+ Entity getEntity(); -+ -+ /** -+ * For part entity like ender dragon's, getEntity() will return the parent entity. -+ */ -+ Entity getRawEntity(); -+ -+ @ApiStatus.NonExtendable -+ interface Builder { -+ Builder level(Level level); -+ -+ Builder player(Player player); -+ -+ Builder showDetails(boolean showDetails); -+ -+ default Builder hit(EntityHitResult hit) { -+ return hit(() -> hit); -+ } -+ -+ Builder hit(Supplier hit); -+ -+ default Builder entity(Entity entity) { -+ return entity(() -> entity); -+ } -+ -+ Builder entity(Supplier entity); -+ -+ Builder from(EntityAccessor accessor); -+ -+ EntityAccessor build(); -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..65d16c0024372ede4cec230b7aad54de28de15f2 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java -@@ -0,0 +1,123 @@ -+package org.leavesmc.leaves.protocol.jade.accessor; -+ -+import com.google.common.base.Suppliers; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.EntityHitResult; -+import net.minecraft.world.phys.Vec3; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.util.CommonUtil; -+ -+import java.util.function.Supplier; -+ -+public class EntityAccessorImpl extends AccessorImpl implements EntityAccessor { -+ -+ private final Supplier entity; -+ -+ public EntityAccessorImpl(Builder builder) { -+ super(builder.level, builder.player, builder.hit, builder.connected, builder.showDetails); -+ entity = builder.entity; -+ } -+ -+ @Override -+ public Entity getEntity() { -+ return CommonUtil.wrapPartEntityParent(getRawEntity()); -+ } -+ -+ @Override -+ public Entity getRawEntity() { -+ return entity.get(); -+ } -+ -+ @NotNull -+ @Override -+ public Object getTarget() { -+ return getEntity(); -+ } -+ -+ public static class Builder implements EntityAccessor.Builder { -+ -+ public boolean showDetails; -+ private Level level; -+ private Player player; -+ private boolean connected; -+ private Supplier hit; -+ private Supplier entity; -+ -+ @Override -+ public Builder level(Level level) { -+ this.level = level; -+ return this; -+ } -+ -+ @Override -+ public Builder player(Player player) { -+ this.player = player; -+ return this; -+ } -+ -+ @Override -+ public Builder showDetails(boolean showDetails) { -+ this.showDetails = showDetails; -+ return this; -+ } -+ -+ @Override -+ public Builder hit(Supplier hit) { -+ this.hit = hit; -+ return this; -+ } -+ -+ @Override -+ public Builder entity(Supplier entity) { -+ this.entity = entity; -+ return this; -+ } -+ -+ @Override -+ public Builder from(EntityAccessor accessor) { -+ level = accessor.getLevel(); -+ player = accessor.getPlayer(); -+ connected = accessor.isServerConnected(); -+ showDetails = accessor.showDetails(); -+ hit = accessor::getHitResult; -+ entity = accessor::getEntity; -+ return this; -+ } -+ -+ @Override -+ public EntityAccessor build() { -+ return new EntityAccessorImpl(this); -+ } -+ } -+ -+ public record SyncData(boolean showDetails, int id, int partIndex, Vec3 hitVec) { -+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite( -+ ByteBufCodecs.BOOL, -+ SyncData::showDetails, -+ ByteBufCodecs.VAR_INT, -+ SyncData::id, -+ ByteBufCodecs.VAR_INT, -+ SyncData::partIndex, -+ ByteBufCodecs.VECTOR3F.map(Vec3::new, Vec3::toVector3f), -+ SyncData::hitVec, -+ SyncData::new -+ ); -+ -+ public EntityAccessor unpack(ServerPlayer player) { -+ Supplier entity = Suppliers.memoize(() -> CommonUtil.getPartEntity(player.level().getEntity(id), partIndex)); -+ return new EntityAccessorImpl.Builder() -+ .level(player.level()) -+ .player(player) -+ .showDetails(showDetails) -+ .entity(entity) -+ .hit(Suppliers.memoize(() -> new EntityHitResult(entity.get(), hitVec))) -+ .build(); -+ } -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1b474ea8c1075b3dbaa7cd27e5bd95aa904fbe97 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java -@@ -0,0 +1,28 @@ -+package org.leavesmc.leaves.protocol.jade.payload; -+ -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+ -+public record ReceiveDataPayload(CompoundTag tag) implements LeavesCustomPayload { -+ -+ private static final ResourceLocation PACKET_RECEIVE_DATA = JadeProtocol.id("receive_data"); -+ -+ @New -+ public ReceiveDataPayload(ResourceLocation id, FriendlyByteBuf buf) { -+ this(buf.readNbt()); -+ } -+ -+ @Override -+ public void write(@NotNull FriendlyByteBuf buf) { -+ buf.writeNbt(tag); -+ } -+ -+ @Override -+ public ResourceLocation id() { -+ return PACKET_RECEIVE_DATA; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..480ec35f28c850bfbe4f787d080fd7bbd84ca24c ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java -@@ -0,0 +1,51 @@ -+package org.leavesmc.leaves.protocol.jade.payload; -+ -+import io.netty.buffer.ByteBuf; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessorImpl; -+import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; -+ -+import java.util.List; -+import java.util.Objects; -+ -+import static org.leavesmc.leaves.protocol.jade.JadeProtocol.blockDataProviders; -+ -+public record RequestBlockPayload(BlockAccessorImpl.SyncData data, List<@Nullable IServerDataProvider> dataProviders) implements LeavesCustomPayload { -+ -+ private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block"); -+ private static final StreamCodec CODEC = StreamCodec.composite( -+ BlockAccessorImpl.SyncData.STREAM_CODEC, -+ RequestBlockPayload::data, -+ ByteBufCodecs.>list() -+ .apply(ByteBufCodecs.idMapper( -+ $ -> Objects.requireNonNull(blockDataProviders.idMapper()).byId($), -+ $ -> Objects.requireNonNull(blockDataProviders.idMapper()).getIdOrThrow($))), -+ RequestBlockPayload::dataProviders, -+ RequestBlockPayload::new); -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); -+ } -+ -+ @New -+ public static RequestBlockPayload create(ResourceLocation location, FriendlyByteBuf buf) { -+ return CODEC.decode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess())); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return PACKET_REQUEST_BLOCK; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..395138d427413e562a5a3df80b33b53cffacc637 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java -@@ -0,0 +1,53 @@ -+package org.leavesmc.leaves.protocol.jade.payload; -+ -+import io.netty.buffer.ByteBuf; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessorImpl; -+import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; -+ -+import java.util.List; -+import java.util.Objects; -+ -+import static org.leavesmc.leaves.protocol.jade.JadeProtocol.entityDataProviders; -+ -+public record RequestEntityPayload(EntityAccessorImpl.SyncData data, List<@Nullable IServerDataProvider> dataProviders) implements LeavesCustomPayload { -+ -+ private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity"); -+ private static final StreamCodec CODEC = StreamCodec.composite( -+ EntityAccessorImpl.SyncData.STREAM_CODEC, -+ RequestEntityPayload::data, -+ ByteBufCodecs.>list() -+ .apply(ByteBufCodecs.idMapper( -+ $ -> Objects.requireNonNull(entityDataProviders.idMapper()).byId($), -+ $ -> Objects.requireNonNull(entityDataProviders.idMapper()).getIdOrThrow($) -+ )), -+ RequestEntityPayload::dataProviders, -+ RequestEntityPayload::new); -+ -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); -+ } -+ -+ @New -+ public static RequestEntityPayload create(ResourceLocation location, FriendlyByteBuf buf) { -+ return CODEC.decode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess())); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return PACKET_REQUEST_ENTITY; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f4419962a7fedaea05140bbf6eaa01cc94c05049 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java -@@ -0,0 +1,49 @@ -+package org.leavesmc.leaves.protocol.jade.payload; -+ -+import com.google.common.collect.Maps; -+import io.netty.buffer.ByteBuf; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.level.block.Block; -+import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+ -+import java.util.List; -+import java.util.Map; -+ -+import static org.leavesmc.leaves.protocol.jade.util.JadeCodec.PRIMITIVE_STREAM_CODEC; -+ -+public record ServerPingPayload( -+ Map serverConfig, -+ List shearableBlocks, -+ List blockProviderIds, -+ List entityProviderIds) implements LeavesCustomPayload { -+ -+ private static final ResourceLocation PACKET_SERVER_HANDSHAKE = JadeProtocol.id("server_ping_v1"); -+ private static final StreamCodec CODEC = StreamCodec.composite( -+ ByteBufCodecs.map(Maps::newHashMapWithExpectedSize, ResourceLocation.STREAM_CODEC, PRIMITIVE_STREAM_CODEC), -+ ServerPingPayload::serverConfig, -+ ByteBufCodecs.registry(Registries.BLOCK).apply(ByteBufCodecs.list()), -+ ServerPingPayload::shearableBlocks, -+ ByteBufCodecs.list().apply(ResourceLocation.STREAM_CODEC), -+ ServerPingPayload::blockProviderIds, -+ ByteBufCodecs.list().apply(ResourceLocation.STREAM_CODEC), -+ ServerPingPayload::entityProviderIds, -+ ServerPingPayload::new); -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); -+ } -+ -+ @Override -+ public ResourceLocation id() { -+ return PACKET_SERVER_HANDSHAKE; -+ } -+} -+ -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d62fc8f96fcdee7dbb0204d2460ff6fee4074e1a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java -@@ -0,0 +1,12 @@ -+package org.leavesmc.leaves.protocol.jade.provider; -+ -+import net.minecraft.resources.ResourceLocation; -+ -+public interface IJadeProvider { -+ -+ ResourceLocation getUid(); -+ -+ default int getDefaultPriority() { -+ return 0; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7d839f17601ca4d2b8717222989cf566a0eb6524 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java -@@ -0,0 +1,8 @@ -+package org.leavesmc.leaves.protocol.jade.provider; -+ -+import net.minecraft.nbt.CompoundTag; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+ -+public interface IServerDataProvider> extends IJadeProvider { -+ void appendServerData(CompoundTag data, T accessor); -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6e32eed15f028020223e2500849b4db3892f68c3 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java -@@ -0,0 +1,10 @@ -+package org.leavesmc.leaves.protocol.jade.provider; -+ -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+import org.leavesmc.leaves.protocol.jade.util.ViewGroup; -+ -+import java.util.List; -+ -+public interface IServerExtensionProvider extends IJadeProvider { -+ List> getGroups(Accessor request); -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3efa3ceb9654bc53adb61ded96c0ad08eb9b083d ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java -@@ -0,0 +1,138 @@ -+package org.leavesmc.leaves.protocol.jade.provider; -+ -+import com.google.common.cache.Cache; -+import com.google.common.cache.CacheBuilder; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.Container; -+import net.minecraft.world.LockCode; -+import net.minecraft.world.RandomizableContainer; -+import net.minecraft.world.WorldlyContainerHolder; -+import net.minecraft.world.entity.animal.horse.AbstractHorse; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.entity.vehicle.ContainerEntity; -+import net.minecraft.world.inventory.PlayerEnderChestContainer; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.ChestBlock; -+import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; -+import net.minecraft.world.level.block.entity.ChestBlockEntity; -+import net.minecraft.world.level.block.entity.EnderChestBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.util.ItemCollector; -+import org.leavesmc.leaves.protocol.jade.util.ItemIterator; -+import org.leavesmc.leaves.protocol.jade.util.ViewGroup; -+ -+import java.util.List; -+import java.util.concurrent.ExecutionException; -+import java.util.concurrent.TimeUnit; -+ -+public enum ItemStorageExtensionProvider implements IServerExtensionProvider { -+ INSTANCE; -+ -+ public static final Cache> targetCache = CacheBuilder.newBuilder().weakKeys().expireAfterAccess(60, TimeUnit.SECONDS).build(); -+ -+ private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default"); -+ -+ @Override -+ public List> getGroups(Accessor request) { -+ Object target = request.getTarget(); -+ -+ switch (target) { -+ case null -> { -+ return createItemCollector(request).update(request); -+ } -+ case RandomizableContainer te when te.getLootTable() != null -> { -+ return List.of(); -+ } -+ case ContainerEntity containerEntity when containerEntity.getContainerLootTable() != null -> { -+ return List.of(); -+ } -+ default -> { -+ } -+ } -+ -+ Player player = request.getPlayer(); -+ if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { -+ if (te.lockKey != LockCode.NO_LOCK) { -+ return List.of(); -+ } -+ } -+ -+ if (target instanceof EnderChestBlockEntity) { -+ PlayerEnderChestContainer inventory = player.getEnderChestInventory(); -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(x -> inventory, 0)).update(request); -+ } -+ -+ ItemCollector itemCollector; -+ try { -+ itemCollector = targetCache.get(target, () -> createItemCollector(request)); -+ } catch (ExecutionException e) { -+ LeavesLogger.LOGGER.severe("Failed to get item collector for " + target); -+ return null; -+ } -+ -+ return itemCollector.update(request); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return UNIVERSAL_ITEM_STORAGE; -+ } -+ -+ public static ItemCollector createItemCollector(Accessor request) { -+ if (request.getTarget() instanceof AbstractHorse) { -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { -+ if (o instanceof AbstractHorse horse) { -+ return horse.inventory; -+ } -+ return null; -+ }, 2)); -+ } -+ -+ // TODO BlockEntity like fabric's ItemStorage -+ -+ final Container container = findContainer(request); -+ if (container != null) { -+ if (container instanceof ChestBlockEntity) { -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { -+ if (o instanceof ChestBlockEntity blockEntity) { -+ if (blockEntity.getBlockState().getBlock() instanceof ChestBlock chestBlock) { -+ Container compound = null; -+ if (blockEntity.getLevel() != null) { -+ compound = ChestBlock.getContainer(chestBlock, blockEntity.getBlockState(), blockEntity.getLevel(), blockEntity.getBlockPos(), false); -+ } -+ if (compound != null) { -+ return compound; -+ } -+ } -+ return blockEntity; -+ } -+ return null; -+ }, 0)); -+ } -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0)); -+ } -+ -+ return ItemCollector.EMPTY; -+ } -+ -+ public static @Nullable Container findContainer(@NotNull Accessor accessor) { -+ Object target = accessor.getTarget(); -+ if (target == null && accessor instanceof BlockAccessor blockAccessor && -+ blockAccessor.getBlock() instanceof WorldlyContainerHolder holder) { -+ return holder.getContainer(blockAccessor.getBlockState(), accessor.getLevel(), blockAccessor.getPosition()); -+ } else if (target instanceof Container container) { -+ return container; -+ } -+ return null; -+ } -+ -+ @Override -+ public int getDefaultPriority() { -+ return IServerExtensionProvider.super.getDefaultPriority() + 1000; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8289b5c48213348a7346dfdbb30ee1b8787d2a2d ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageProvider.java -@@ -0,0 +1,88 @@ -+package org.leavesmc.leaves.protocol.jade.provider; -+ -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.LockCode; -+import net.minecraft.world.RandomizableContainer; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; -+import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.util.CommonUtil; -+import org.leavesmc.leaves.protocol.jade.util.ItemCollector; -+import org.leavesmc.leaves.protocol.jade.util.ViewGroup; -+ -+import java.util.List; -+import java.util.Map; -+ -+public abstract class ItemStorageProvider> implements IServerDataProvider { -+ -+ private static final StreamCodec>>> STREAM_CODEC = ViewGroup.listCodec( -+ ItemStack.OPTIONAL_STREAM_CODEC); -+ -+ private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage"); -+ -+ public static ForBlock getBlock() { -+ return ForBlock.INSTANCE; -+ } -+ -+ public static ForEntity getEntity() { -+ return ForEntity.INSTANCE; -+ } -+ -+ public static class ForBlock extends ItemStorageProvider { -+ private static final ForBlock INSTANCE = new ForBlock(); -+ } -+ -+ public static class ForEntity extends ItemStorageProvider { -+ private static final ForEntity INSTANCE = new ForEntity(); -+ } -+ -+ public static void putData(CompoundTag tag, @NotNull Accessor accessor) { -+ Object target = accessor.getTarget(); -+ Player player = accessor.getPlayer(); -+ Map.Entry>> entry = CommonUtil.getServerExtensionData(accessor, JadeProtocol.itemStorageProviders); -+ if (entry != null) { -+ List> groups = entry.getValue(); -+ for (ViewGroup group : groups) { -+ if (group.views.size() > ItemCollector.MAX_SIZE) { -+ group.views = group.views.subList(0, ItemCollector.MAX_SIZE); -+ } -+ } -+ tag.put(UNIVERSAL_ITEM_STORAGE.toString(), accessor.encodeAsNbt(STREAM_CODEC, entry)); -+ return; -+ } -+ if (target instanceof RandomizableContainer containerEntity && containerEntity.getLootTable() != null) { -+ tag.putBoolean("Loot", true); -+ } else if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { -+ if (te.lockKey != LockCode.NO_LOCK) { -+ tag.putBoolean("Locked", true); -+ } -+ } -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return UNIVERSAL_ITEM_STORAGE; -+ } -+ -+ @Override -+ public void appendServerData(CompoundTag tag, @NotNull T accessor) { -+ if (accessor.getTarget() instanceof AbstractFurnaceBlockEntity) { -+ return; -+ } -+ putData(tag, accessor); -+ } -+ -+ @Override -+ public int getDefaultPriority() { -+ return 9999; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..52887edb3359c5eb1900cd1eec912e52afef2c9f ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java -@@ -0,0 +1,26 @@ -+package org.leavesmc.leaves.protocol.jade.provider; -+ -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.Tag; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+ -+import java.util.Optional; -+ -+public interface StreamServerDataProvider, D> extends IServerDataProvider { -+ -+ @Override -+ default void appendServerData(CompoundTag data, T accessor) { -+ D value = streamData(accessor); -+ if (value != null) { -+ data.put(getUid().toString(), accessor.encodeAsNbt(streamCodec(), value)); -+ } -+ } -+ -+ @Nullable -+ D streamData(T accessor); -+ -+ StreamCodec streamCodec(); -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ee92d79bf4d328c95c51178a2ad43beb0a54cd29 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java -@@ -0,0 +1,34 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum BeehiveProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_BEEHIVE = JadeProtocol.mc_id("beehive"); -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.BYTE.cast(); -+ } -+ -+ @Override -+ public Byte streamData(@NotNull BlockAccessor accessor) { -+ BeehiveBlockEntity beehive = (BeehiveBlockEntity) accessor.getBlockEntity(); -+ int bees = beehive.getOccupantCount(); -+ return (byte) (beehive.isFull() ? bees : -bees); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_BEEHIVE; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e6f15f87819d4a7c4d4383db735d8318ea30a03c ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java -@@ -0,0 +1,43 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import io.netty.buffer.ByteBuf; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum BrewingStandProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_BREWING_STAND = JadeProtocol.mc_id("brewing_stand"); -+ -+ @Override -+ public @NotNull Data streamData(@NotNull BlockAccessor accessor) { -+ BrewingStandBlockEntity brewingStand = (BrewingStandBlockEntity) accessor.getBlockEntity(); -+ return new Data(brewingStand.fuel, brewingStand.brewTime); -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return Data.STREAM_CODEC.cast(); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_BREWING_STAND; -+ } -+ -+ public record Data(int fuel, int time) { -+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite( -+ ByteBufCodecs.VAR_INT, -+ Data::fuel, -+ ByteBufCodecs.VAR_INT, -+ Data::time, -+ Data::new); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2deb3777a320d6a50168e06f234ba4c21da48e9a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java -@@ -0,0 +1,55 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import com.google.common.collect.Lists; -+import com.mojang.serialization.Codec; -+import com.mojang.serialization.MapCodec; -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.component.CustomData; -+import net.minecraft.world.level.block.entity.CampfireBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.jetbrains.annotations.Unmodifiable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; -+import org.leavesmc.leaves.protocol.jade.util.ViewGroup; -+ -+import java.util.List; -+ -+public enum CampfireProvider implements IServerExtensionProvider { -+ INSTANCE; -+ -+ private static final MapCodec COOKING_TIME_CODEC = Codec.INT.fieldOf("jade:cooking"); -+ private static final ResourceLocation MC_CAMPFIRE = JadeProtocol.mc_id("campfire"); -+ -+ @Override -+ public @Nullable @Unmodifiable List> getGroups(@NotNull Accessor request) { -+ if (request.getTarget() instanceof CampfireBlockEntity campfire) { -+ List list = Lists.newArrayList(); -+ for (int i = 0; i < campfire.cookingTime.length; i++) { -+ ItemStack stack = campfire.getItems().get(i); -+ if (stack.isEmpty()) { -+ continue; -+ } -+ stack = stack.copy(); -+ -+ CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) -+ .update(NbtOps.INSTANCE, COOKING_TIME_CODEC, campfire.cookingTime[i] - campfire.cookingProgress[i]) -+ .getOrThrow(); -+ stack.set(DataComponents.CUSTOM_DATA, customData); -+ -+ list.add(stack); -+ } -+ return List.of(new ViewGroup<>(list)); -+ } -+ return null; -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_CAMPFIRE; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bde872cc5ebd9d79af307c8a4b38acd385cec11b ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java -@@ -0,0 +1,39 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.ChiseledBookShelfBlock; -+import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum ChiseledBookshelfProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_CHISELED_BOOKSHELF = JadeProtocol.mc_id("chiseled_bookshelf"); -+ -+ @Override -+ public @Nullable ItemStack streamData(@NotNull BlockAccessor accessor) { -+ int slot = ((ChiseledBookShelfBlock) accessor.getBlock()).getHitSlot(accessor.getHitResult(), accessor.getBlockState()).orElse(-1); -+ if (slot == -1) { -+ return null; -+ } -+ return ((ChiseledBookShelfBlockEntity) accessor.getBlockEntity()).getItem(slot); -+ } -+ -+ @Override -+ public StreamCodec streamCodec() { -+ return ItemStack.OPTIONAL_STREAM_CODEC; -+ } -+ -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_CHISELED_BOOKSHELF; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5f71fadae8fe95e3386e3ee5465eb33f850a37b0 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java -@@ -0,0 +1,40 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.block.entity.CommandBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum CommandBlockProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_COMMAND_BLOCK = JadeProtocol.mc_id("command_block"); -+ -+ @Nullable -+ public String streamData(@NotNull BlockAccessor accessor) { -+ if (!accessor.getPlayer().canUseGameMasterBlocks()) { -+ return null; -+ } -+ String command = ((CommandBlockEntity) accessor.getBlockEntity()).getCommandBlock().getCommand(); -+ if (command.length() > 40) { -+ command = command.substring(0, 37) + "..."; -+ } -+ return command; -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.STRING_UTF8.cast(); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_COMMAND_BLOCK; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..090e6a350a5c19c0204ecf9a2c2c42e8d012cb3d ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java -@@ -0,0 +1,60 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+import java.util.List; -+ -+public enum FurnaceProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_FURNACE = JadeProtocol.mc_id("furnace"); -+ -+ @Override -+ public @Nullable Data streamData(@NotNull BlockAccessor accessor) { -+ if (!(accessor.getTarget() instanceof AbstractFurnaceBlockEntity furnace)) { -+ return null; -+ } -+ -+ if (furnace.isEmpty()) { -+ return null; -+ } -+ -+ CompoundTag furnaceTag = furnace.saveWithoutMetadata(accessor.getLevel().registryAccess()); -+ return new Data( -+ furnaceTag.getInt("CookTime"), -+ furnaceTag.getInt("CookTimeTotal"), -+ List.of(furnace.getItem(0), furnace.getItem(1), furnace.getItem(2))); -+ } -+ -+ @Override -+ public StreamCodec streamCodec() { -+ return Data.STREAM_CODEC; -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_FURNACE; -+ } -+ -+ public record Data(int progress, int total, List inventory) { -+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite( -+ ByteBufCodecs.VAR_INT, -+ Data::progress, -+ ByteBufCodecs.VAR_INT, -+ Data::total, -+ ItemStack.OPTIONAL_LIST_STREAM_CODEC, -+ Data::inventory, -+ Data::new); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a3937081bd923d3b6f2ee966dc95aa235c3eb57c ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java -@@ -0,0 +1,32 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.block.state.properties.BlockStateProperties; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum HopperLockProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_HOPPER_LOCK = JadeProtocol.mc_id("hopper_lock"); -+ -+ @Override -+ public Boolean streamData(@NotNull BlockAccessor accessor) { -+ return !accessor.getBlockState().getValue(BlockStateProperties.ENABLED); -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.BOOL.cast(); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_HOPPER_LOCK; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b6e224ebc8d6acdc29abf51f7d98b667baf0984 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java -@@ -0,0 +1,32 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.entity.JukeboxBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum JukeboxProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_JUKEBOX = JadeProtocol.mc_id("jukebox"); -+ -+ @Override -+ public @NotNull ItemStack streamData(BlockAccessor accessor) { -+ return ((JukeboxBlockEntity) accessor.getBlockEntity()).getTheItem(); -+ } -+ -+ @Override -+ public StreamCodec streamCodec() { -+ return ItemStack.OPTIONAL_STREAM_CODEC; -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_JUKEBOX; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c363bd616fa41eca3266ccb485432cfd90ad7473 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java -@@ -0,0 +1,33 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.entity.LecternBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum LecternProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_LECTERN = JadeProtocol.mc_id("lectern"); -+ -+ @Override -+ public @NotNull ItemStack streamData(@NotNull BlockAccessor accessor) { -+ return ((LecternBlockEntity) accessor.getBlockEntity()).getBook(); -+ } -+ -+ @Override -+ public StreamCodec streamCodec() { -+ return ItemStack.OPTIONAL_STREAM_CODEC; -+ } -+ -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_LECTERN; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a70f4a81166221ec1971b1fbf06e4c73efffcbe4 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java -@@ -0,0 +1,42 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; -+import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum MobSpawnerCooldownProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_MOB_SPAWNER_COOLDOWN = JadeProtocol.mc_id("mob_spawner.cooldown"); -+ -+ @Override -+ public @Nullable Integer streamData(@NotNull BlockAccessor accessor) { -+ TrialSpawnerBlockEntity spawner = (TrialSpawnerBlockEntity) accessor.getBlockEntity(); -+ TrialSpawnerData spawnerData = spawner.getTrialSpawner().getData(); -+ ServerLevel level = ((ServerLevel) accessor.getLevel()); -+ if (spawner.getTrialSpawner().canSpawnInLevel(level) && level.getGameTime() < spawnerData.cooldownEndsAt) { -+ return (int) (spawnerData.cooldownEndsAt - level.getGameTime()); -+ } -+ return null; -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.VAR_INT.cast(); -+ } -+ -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_MOB_SPAWNER_COOLDOWN; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6a060c8a64f18a3bff232bcb43f20121b1da36b1 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java -@@ -0,0 +1,63 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.chat.Component; -+import net.minecraft.network.chat.ComponentSerialization; -+import net.minecraft.network.chat.contents.TranslatableContents; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.MenuProvider; -+import net.minecraft.world.Nameable; -+import net.minecraft.world.level.block.ChestBlock; -+import net.minecraft.world.level.block.entity.ChestBlockEntity; -+import net.minecraft.world.level.block.state.properties.ChestType; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public abstract class ObjectNameProvider implements StreamServerDataProvider { -+ -+ private static final ResourceLocation CORE_OBJECT_NAME = JadeProtocol.id("object_name"); -+ -+ public static class ForBlock extends ObjectNameProvider implements StreamServerDataProvider { -+ public static final ForBlock INSTANCE = new ForBlock(); -+ -+ @Override -+ @Nullable -+ public Component streamData(@NotNull BlockAccessor accessor) { -+ if (!(accessor.getBlockEntity() instanceof Nameable nameable)) { -+ return null; -+ } -+ if (nameable instanceof ChestBlockEntity && accessor.getBlock() instanceof ChestBlock && accessor.getBlockState().getValue(ChestBlock.TYPE) != ChestType.SINGLE) { -+ MenuProvider menuProvider = accessor.getBlockState().getMenuProvider(accessor.getLevel(), accessor.getPosition()); -+ if (menuProvider != null) { -+ Component name = menuProvider.getDisplayName(); -+ if (!(name.getContents() instanceof TranslatableContents contents) || !"container.chestDouble".equals(contents.getKey())) { -+ return name; -+ } -+ } -+ } else if (nameable.hasCustomName()) { -+ return nameable.getDisplayName(); -+ } -+ return accessor.getBlockEntity().components().get(DataComponents.ITEM_NAME); -+ } -+ -+ @Override -+ public StreamCodec streamCodec() { -+ return ComponentSerialization.STREAM_CODEC; -+ } -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return CORE_OBJECT_NAME; -+ } -+ -+ @Override -+ public int getDefaultPriority() { -+ return -10100; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1cdcf21ed69744f96f47673100d8bf0114850f4f ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java -@@ -0,0 +1,36 @@ -+package org.leavesmc.leaves.protocol.jade.provider.block; -+ -+import net.minecraft.core.Direction; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.block.CalibratedSculkSensorBlock; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; -+import net.minecraft.world.level.block.entity.ComparatorBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; -+ -+public enum RedstoneProvider implements IServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_REDSTONE = JadeProtocol.mc_id("redstone"); -+ -+ @Override -+ public void appendServerData(CompoundTag data, @NotNull BlockAccessor accessor) { -+ BlockEntity blockEntity = accessor.getBlockEntity(); -+ if (blockEntity instanceof ComparatorBlockEntity comparator) { -+ data.putInt("Signal", comparator.getOutputSignal()); -+ } else if (blockEntity instanceof CalibratedSculkSensorBlockEntity) { -+ Direction direction = accessor.getBlockState().getValue(CalibratedSculkSensorBlock.FACING).getOpposite(); -+ int signal = accessor.getLevel().getSignal(accessor.getPosition().relative(direction), direction); -+ data.putInt("Signal", signal); -+ } -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_REDSTONE; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ff50ef60e3b37ab231161c870c432ef8e3018458 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java -@@ -0,0 +1,43 @@ -+package org.leavesmc.leaves.protocol.jade.provider.entity; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.OwnableEntity; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+import org.leavesmc.leaves.protocol.jade.util.CommonUtil; -+ -+import java.util.UUID; -+ -+public enum AnimalOwnerProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_ANIMAL_OWNER = JadeProtocol.mc_id("animal_owner"); -+ -+ @Override -+ public String streamData(@NotNull EntityAccessor accessor) { -+ return CommonUtil.getLastKnownUsername(getOwnerUUID(accessor.getEntity())); -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.STRING_UTF8.cast(); -+ } -+ -+ public static UUID getOwnerUUID(Entity entity) { -+ if (entity instanceof OwnableEntity ownableEntity) { -+ return ownableEntity.getOwnerUUID(); -+ } -+ return null; -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_ANIMAL_OWNER; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0acba2f9700e4a65e764077b22e65e18d787be2a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java -@@ -0,0 +1,44 @@ -+package org.leavesmc.leaves.protocol.jade.provider.entity; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.allay.Allay; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum MobBreedingProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_MOB_BREEDING = JadeProtocol.mc_id("mob_breeding"); -+ -+ @Override -+ public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { -+ int time = 0; -+ Entity entity = accessor.getEntity(); -+ if (entity instanceof Allay allay) { -+ if (allay.duplicationCooldown > 0 && allay.duplicationCooldown < Integer.MAX_VALUE) { -+ time = (int) allay.duplicationCooldown; -+ } -+ } else { -+ time = ((Animal) entity).getAge(); -+ } -+ return time > 0 ? time : null; -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.VAR_INT.cast(); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_MOB_BREEDING; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..44f5f4b8bf3b9fe66b2f8b93b36284c3ae5d1d87 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java -@@ -0,0 +1,43 @@ -+package org.leavesmc.leaves.protocol.jade.provider.entity; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.AgeableMob; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.animal.frog.Tadpole; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum MobGrowthProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_MOB_GROWTH = JadeProtocol.mc_id("mob_growth"); -+ -+ @Override -+ public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { -+ int time = -1; -+ Entity entity = accessor.getEntity(); -+ if (entity instanceof AgeableMob ageable) { -+ time = -ageable.getAge(); -+ } else if (entity instanceof Tadpole tadpole) { -+ time = tadpole.getTicksLeftUntilAdult(); -+ } -+ return time > 0 ? time : null; -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.VAR_INT.cast(); -+ } -+ -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_MOB_GROWTH; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..892911a3d0087a5bf48b2df8326e3c5ce27835a0 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java -@@ -0,0 +1,35 @@ -+package org.leavesmc.leaves.protocol.jade.provider.entity; -+ -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.animal.Chicken; -+import net.minecraft.world.entity.animal.armadillo.Armadillo; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; -+ -+public enum NextEntityDropProvider implements IServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_NEXT_ENTITY_DROP = JadeProtocol.mc_id("next_entity_drop"); -+ -+ @Override -+ public void appendServerData(CompoundTag tag, @NotNull EntityAccessor accessor) { -+ int max = 24000 * 2; -+ if (accessor.getEntity() instanceof Chicken chicken) { -+ if (!chicken.isBaby() && chicken.eggTime < max) { -+ tag.putInt("NextEggIn", chicken.eggTime); -+ } -+ } else if (accessor.getEntity() instanceof Armadillo armadillo) { -+ if (!armadillo.isBaby() && armadillo.scuteTime < max) { -+ tag.putInt("NextScuteIn", armadillo.scuteTime); -+ } -+ } -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_NEXT_ENTITY_DROP; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ffc571875f620fe53b2210583c246d6579c3df1f ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java -@@ -0,0 +1,45 @@ -+package org.leavesmc.leaves.protocol.jade.provider.entity; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.effect.MobEffectInstance; -+import net.minecraft.world.entity.LivingEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+import java.util.List; -+ -+public enum StatusEffectsProvider implements StreamServerDataProvider> { -+ INSTANCE; -+ -+ -+ private static final StreamCodec> STREAM_CODEC = ByteBufCodecs.list() -+ .apply(MobEffectInstance.STREAM_CODEC); -+ private static final ResourceLocation MC_POTION_EFFECTS = JadeProtocol.mc_id("potion_effects"); -+ -+ @Override -+ @Nullable -+ public List streamData(@NotNull EntityAccessor accessor) { -+ List effects = ((LivingEntity) accessor.getEntity()).getActiveEffects() -+ .stream() -+ .filter(MobEffectInstance::isVisible) -+ .toList(); -+ return effects.isEmpty() ? null : effects; -+ } -+ -+ @Override -+ public StreamCodec> streamCodec() { -+ return STREAM_CODEC; -+ } -+ -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_POTION_EFFECTS; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b7c9afd29f3ddead6871c8f2b1f2b0815605cea5 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java -@@ -0,0 +1,34 @@ -+package org.leavesmc.leaves.protocol.jade.provider.entity; -+ -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.monster.ZombieVillager; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; -+ -+public enum ZombieVillagerProvider implements StreamServerDataProvider { -+ INSTANCE; -+ -+ private static final ResourceLocation MC_ZOMBIE_VILLAGER = JadeProtocol.mc_id("zombie_villager"); -+ -+ @Override -+ public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { -+ int time = ((ZombieVillager) accessor.getEntity()).villagerConversionTime; -+ return time > 0 ? time : null; -+ } -+ -+ @Override -+ public @NotNull StreamCodec streamCodec() { -+ return ByteBufCodecs.VAR_INT.cast(); -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return MC_ZOMBIE_VILLAGER; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6a9cd9f0e71331c4fd3bef7bbeabebcc5d0e80cb ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java -@@ -0,0 +1,32 @@ -+package org.leavesmc.leaves.protocol.jade.tool; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+ -+import java.util.List; -+ -+public class ShearsToolHandler extends SimpleToolHandler { -+ -+ private static final ShearsToolHandler INSTANCE = new ShearsToolHandler(); -+ -+ public static ShearsToolHandler getInstance() { -+ return INSTANCE; -+ } -+ -+ public ShearsToolHandler() { -+ super(JadeProtocol.id("shears"), List.of(Items.SHEARS.getDefaultInstance()), true); -+ } -+ -+ @Override -+ public ItemStack test(BlockState state, Level world, BlockPos pos) { -+ if (state.is(Blocks.TRIPWIRE)) { -+ return tools.getFirst(); -+ } -+ return super.test(state, world, pos); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d45ecdb17a78d7e0c5eb280ee584960761ced1d2 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java -@@ -0,0 +1,71 @@ -+package org.leavesmc.leaves.protocol.jade.tool; -+ -+import com.google.common.base.Preconditions; -+import com.google.common.collect.Lists; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.component.Tool; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.List; -+ -+public class SimpleToolHandler implements ToolHandler { -+ -+ protected final List tools = Lists.newArrayList(); -+ private final ResourceLocation uid; -+ private final boolean skipInstaBreakingBlock; -+ -+ protected SimpleToolHandler(ResourceLocation uid, @NotNull List tools, boolean skipInstaBreakingBlock) { -+ this.uid = uid; -+ Preconditions.checkArgument(!tools.isEmpty(), "tools cannot be empty"); -+ this.tools.addAll(tools); -+ this.skipInstaBreakingBlock = skipInstaBreakingBlock; -+ } -+ -+ @Contract("_, _ -> new") -+ public static @NotNull SimpleToolHandler create(ResourceLocation uid, List tools) { -+ return create(uid, tools, true); -+ } -+ -+ @Contract("_, _, _ -> new") -+ public static @NotNull SimpleToolHandler create(ResourceLocation uid, List tools, boolean skipInstaBreakingBlock) { -+ return new SimpleToolHandler(uid, Lists.transform(tools, Item::getDefaultInstance), skipInstaBreakingBlock); -+ } -+ -+ @Override -+ public ItemStack test(BlockState state, Level world, BlockPos pos) { -+ if (skipInstaBreakingBlock && !state.requiresCorrectToolForDrops() && state.getDestroySpeed(world, pos) == 0) { -+ return ItemStack.EMPTY; -+ } -+ return test(state); -+ } -+ -+ public ItemStack test(BlockState state) { -+ for (ItemStack toolItem : tools) { -+ if (toolItem.isCorrectToolForDrops(state)) { -+ return toolItem; -+ } -+ Tool tool = toolItem.get(DataComponents.TOOL); -+ if (tool != null && tool.getMiningSpeed(state) > tool.defaultMiningSpeed()) { -+ return toolItem; -+ } -+ } -+ return ItemStack.EMPTY; -+ } -+ -+ @Override -+ public List getTools() { -+ return tools; -+ } -+ -+ @Override -+ public ResourceLocation getUid() { -+ return uid; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..18f11e701189ce3615e08c631e31112d53ea5686 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java -@@ -0,0 +1,17 @@ -+package org.leavesmc.leaves.protocol.jade.tool; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; -+ -+import java.util.List; -+ -+public interface ToolHandler extends IJadeProvider { -+ -+ ItemStack test(BlockState state, Level world, BlockPos pos); -+ -+ List getTools(); -+ -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a0a85361693980eb5b0bf7f9d486475c77b646f7 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java -@@ -0,0 +1,79 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import com.mojang.authlib.GameProfile; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.boss.EnderDragonPart; -+import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.entity.SkullBlockEntity; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; -+ -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+import java.util.UUID; -+ -+public class CommonUtil { -+ -+ public static @NotNull ResourceLocation getId(Block block) { -+ return BuiltInRegistries.BLOCK.getKey(block); -+ } -+ -+ public static Entity wrapPartEntityParent(Entity target) { -+ if (target instanceof EnderDragonPart part) { -+ return part.parentMob; -+ } -+ return target; -+ } -+ -+ public static Entity getPartEntity(Entity parent, int index) { -+ if (parent == null) { -+ return null; -+ } -+ if (index < 0) { -+ return parent; -+ } -+ if (parent instanceof EnderDragon dragon) { -+ EnderDragonPart[] parts = dragon.getSubEntities(); -+ if (index < parts.length) { -+ return parts[index]; -+ } -+ } -+ return parent; -+ } -+ -+ -+ @Nullable -+ public static String getLastKnownUsername(@Nullable UUID uuid) { -+ if (uuid == null) { -+ return null; -+ } -+ Optional optional = SkullBlockEntity.fetchGameProfile(String.valueOf(uuid)).getNow(Optional.empty()); -+ return optional.map(GameProfile::getName).orElse(null); -+ } -+ -+ -+ public static Map.Entry>> getServerExtensionData( -+ Accessor accessor, -+ WrappedHierarchyLookup> lookup) { -+ for (var provider : lookup.wrappedGet(accessor)) { -+ List> groups; -+ try { -+ groups = provider.getGroups(accessor); -+ } catch (Exception e) { -+ LeavesLogger.LOGGER.severe(e.toString()); -+ continue; -+ } -+ if (groups != null) { -+ return Map.entry(provider.getUid(), groups); -+ } -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0070fd22b096281a094d1cd19c93fdbccc03a3cc ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java -@@ -0,0 +1,139 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import com.google.common.base.Preconditions; -+import com.google.common.cache.Cache; -+import com.google.common.cache.CacheBuilder; -+import com.google.common.collect.ArrayListMultimap; -+import com.google.common.collect.ImmutableList; -+import com.google.common.collect.ImmutableListMultimap; -+import com.google.common.collect.ListMultimap; -+import com.google.common.collect.Lists; -+import com.google.common.collect.Sets; -+import net.minecraft.core.IdMapper; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; -+ -+import java.util.Collection; -+import java.util.Comparator; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.Set; -+import java.util.concurrent.ExecutionException; -+import java.util.stream.Stream; -+ -+public class HierarchyLookup implements IHierarchyLookup { -+ private final Class baseClass; -+ private final Cache, List> resultCache = CacheBuilder.newBuilder().build(); -+ private final boolean singleton; -+ protected boolean idMapped; -+ @Nullable -+ protected IdMapper idMapper; -+ private ListMultimap, T> objects = ArrayListMultimap.create(); -+ -+ public HierarchyLookup(Class baseClass) { -+ this(baseClass, false); -+ } -+ -+ public HierarchyLookup(Class baseClass, boolean singleton) { -+ this.baseClass = baseClass; -+ this.singleton = singleton; -+ } -+ -+ @Override -+ public void idMapped() { -+ this.idMapped = true; -+ } -+ -+ @Override -+ @Nullable -+ public IdMapper idMapper() { -+ return idMapper; -+ } -+ -+ @Override -+ public void register(Class clazz, T provider) { -+ Preconditions.checkArgument(isClassAcceptable(clazz), "Class %s is not acceptable", clazz); -+ Objects.requireNonNull(provider.getUid()); -+ JadeProtocol.priorities.put(provider); -+ objects.put(clazz, provider); -+ } -+ -+ @Override -+ public boolean isClassAcceptable(Class clazz) { -+ return baseClass.isAssignableFrom(clazz); -+ } -+ -+ @Override -+ public List get(Class clazz) { -+ try { -+ return resultCache.get(clazz, () -> { -+ List list = Lists.newArrayList(); -+ getInternal(clazz, list); -+ list = ImmutableList.sortedCopyOf(Comparator.comparingInt(JadeProtocol.priorities::byValue), list); -+ if (singleton && !list.isEmpty()) { -+ return ImmutableList.of(list.getFirst()); -+ } -+ return list; -+ }); -+ } catch (ExecutionException e) { -+ LeavesLogger.LOGGER.warning("HierarchyLookup error", e); -+ } -+ return List.of(); -+ } -+ -+ private void getInternal(Class clazz, List list) { -+ if (clazz != baseClass && clazz != Object.class) { -+ getInternal(clazz.getSuperclass(), list); -+ } -+ list.addAll(objects.get(clazz)); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return objects.isEmpty(); -+ } -+ -+ @Override -+ public Stream, Collection>> entries() { -+ return objects.asMap().entrySet().stream(); -+ } -+ -+ @Override -+ public void invalidate() { -+ resultCache.invalidateAll(); -+ } -+ -+ @Override -+ public void loadComplete(PriorityStore priorityStore) { -+ objects.asMap().forEach((clazz, list) -> { -+ if (list.size() < 2) { -+ return; -+ } -+ Set set = Sets.newHashSetWithExpectedSize(list.size()); -+ for (T provider : list) { -+ if (set.contains(provider.getUid())) { -+ throw new IllegalStateException("Duplicate UID: %s for %s".formatted(provider.getUid(), list.stream() -+ .filter(p -> p.getUid().equals(provider.getUid())) -+ .map(p -> p.getClass().getName()) -+ .toList() -+ )); -+ } -+ set.add(provider.getUid()); -+ } -+ }); -+ -+ objects = ImmutableListMultimap., T>builder() -+ .orderValuesBy(Comparator.comparingInt(priorityStore::byValue)) -+ .putAll(objects) -+ .build(); -+ -+ if (idMapped) { -+ idMapper = createIdMapper(); -+ } -+ } -+ -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0536309cb016f05f296daaeb17dd9275777a7333 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java -@@ -0,0 +1,66 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import com.google.common.collect.Streams; -+import net.minecraft.core.IdMapper; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; -+ -+import java.util.Collection; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.stream.Stream; -+ -+public interface IHierarchyLookup { -+ default IHierarchyLookup cast() { -+ return this; -+ } -+ -+ void idMapped(); -+ -+ @Nullable -+ IdMapper idMapper(); -+ -+ default List mappedIds() { -+ return Streams.stream(Objects.requireNonNull(idMapper())) -+ .map(IJadeProvider::getUid) -+ .toList(); -+ } -+ -+ void register(Class clazz, T provider); -+ -+ boolean isClassAcceptable(Class clazz); -+ -+ default List get(Object obj) { -+ if (obj == null) { -+ return List.of(); -+ } -+ return get(obj.getClass()); -+ } -+ -+ List get(Class clazz); -+ -+ boolean isEmpty(); -+ -+ Stream, Collection>> entries(); -+ -+ void invalidate(); -+ -+ void loadComplete(PriorityStore priorityStore); -+ -+ default IdMapper createIdMapper() { -+ List list = entries().flatMap(entry -> entry.getValue().stream()).toList(); -+ IdMapper idMapper = idMapper(); -+ if (idMapper == null) { -+ idMapper = new IdMapper<>(list.size()); -+ } -+ for (T provider : list) { -+ if (idMapper.getId(provider) == IdMapper.DEFAULT) { -+ idMapper.add(provider); -+ } -+ } -+ return idMapper; -+ } -+} -+ -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java -new file mode 100644 -index 0000000000000000000000000000000000000000..408c81a0d0762f7d4b32aedc9b67c7ff546b43f3 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java -@@ -0,0 +1,118 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -+import net.minecraft.core.component.DataComponentPatch; -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.component.CustomData; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+ -+import java.util.List; -+import java.util.Locale; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Predicate; -+ -+public class ItemCollector { -+ public static final int MAX_SIZE = 54; -+ public static final ItemCollector EMPTY = new ItemCollector<>(null); -+ private static final Predicate NON_EMPTY = stack -> { -+ if (stack.isEmpty()) { -+ return false; -+ } -+ CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); -+ if (customData.contains("CustomModelData")) { -+ CompoundTag tag = customData.copyTag(); -+ for (String key : tag.getAllKeys()) { -+ if (key.toLowerCase(Locale.ENGLISH).endsWith("clear") && tag.getBoolean(key)) { -+ return false; -+ } -+ } -+ } -+ return true; -+ }; -+ private final Object2IntLinkedOpenHashMap items = new Object2IntLinkedOpenHashMap<>(); -+ private final ItemIterator iterator; -+ public long version; -+ public long lastTimeFinished; -+ public boolean lastTimeIsEmpty; -+ public List> mergedResult; -+ -+ public ItemCollector(ItemIterator iterator) { -+ this.iterator = iterator; -+ } -+ -+ public List> update(Accessor request) { -+ if (iterator == null) { -+ return null; -+ } -+ T container = iterator.find(request.getTarget()); -+ if (container == null) { -+ return null; -+ } -+ long currentVersion = iterator.getVersion(container); -+ long gameTime = request.getLevel().getGameTime(); -+ if (mergedResult != null && iterator.isFinished()) { -+ if (version == currentVersion) { -+ return mergedResult; // content not changed -+ } -+ if (lastTimeFinished + 5 > gameTime) { -+ return mergedResult; // avoid update too frequently -+ } -+ iterator.reset(); -+ } -+ AtomicInteger count = new AtomicInteger(); -+ iterator.populate(container).forEach(stack -> { -+ count.incrementAndGet(); -+ if (NON_EMPTY.test(stack)) { -+ ItemDefinition def = new ItemDefinition(stack); -+ items.addTo(def, stack.getCount()); -+ } -+ }); -+ iterator.afterPopulate(count.get()); -+ if (mergedResult != null && !iterator.isFinished()) { -+ updateCollectingProgress(mergedResult.getFirst()); -+ return mergedResult; -+ } -+ List partialResult = items.object2IntEntrySet().stream().limit(MAX_SIZE ).map(entry -> { -+ ItemDefinition def = entry.getKey(); -+ return def.toStack(entry.getIntValue()); -+ }).toList(); -+ List> groups = List.of(updateCollectingProgress(new ViewGroup<>(partialResult))); -+ if (iterator.isFinished()) { -+ mergedResult = groups; -+ lastTimeIsEmpty = mergedResult.getFirst().views.isEmpty(); -+ version = currentVersion; -+ lastTimeFinished = gameTime; -+ items.clear(); -+ } -+ return groups; -+ } -+ -+ protected ViewGroup updateCollectingProgress(ViewGroup group) { -+ if (lastTimeIsEmpty && group.views.isEmpty()) { -+ return group; -+ } -+ float progress = iterator.getCollectingProgress(); -+ CompoundTag data = group.getExtraData(); -+ if (Float.isNaN(progress) || progress >= 1) { -+ data.remove("Collecting"); -+ } else { -+ data.putFloat("Collecting", progress); -+ } -+ return group; -+ } -+ -+ public record ItemDefinition(Item item, DataComponentPatch components) { -+ ItemDefinition(ItemStack stack) { -+ this(stack.getItem(), stack.getComponentsPatch()); -+ } -+ -+ public ItemStack toStack(int count) { -+ ItemStack itemStack = new ItemStack(item, count); -+ itemStack.applyComponents(components); -+ return itemStack; -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4d65e9a8b5224bd268b1bf18bc39a58dc0113850 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java -@@ -0,0 +1,102 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import net.minecraft.world.Container; -+import net.minecraft.world.item.ItemStack; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Function; -+import java.util.stream.IntStream; -+import java.util.stream.Stream; -+ -+public abstract class ItemIterator { -+ public static final AtomicLong version = new AtomicLong(); -+ protected final Function containerFinder; -+ protected final int fromIndex; -+ protected boolean finished; -+ protected int currentIndex; -+ -+ protected ItemIterator(Function containerFinder, int fromIndex) { -+ this.containerFinder = containerFinder; -+ this.currentIndex = this.fromIndex = fromIndex; -+ } -+ -+ public @Nullable T find(Object target) { -+ return containerFinder.apply(target); -+ } -+ -+ public final boolean isFinished() { -+ return finished; -+ } -+ -+ public long getVersion(T container) { -+ return version.getAndIncrement(); -+ } -+ -+ public abstract Stream populate(T container); -+ -+ public void reset() { -+ currentIndex = fromIndex; -+ finished = false; -+ } -+ -+ public void afterPopulate(int count) { -+ currentIndex += count; -+ if (count == 0 || currentIndex >= 10000) { -+ finished = true; -+ } -+ } -+ -+ public float getCollectingProgress() { -+ return Float.NaN; -+ } -+ -+ public static abstract class SlottedItemIterator extends ItemIterator { -+ protected float progress; -+ -+ public SlottedItemIterator(Function containerFinder, int fromIndex) { -+ super(containerFinder, fromIndex); -+ } -+ -+ protected abstract int getSlotCount(T container); -+ -+ protected abstract ItemStack getItemInSlot(T container, int slot); -+ -+ @Override -+ public Stream populate(T container) { -+ int slotCount = getSlotCount(container); -+ int toIndex = currentIndex + ItemCollector.MAX_SIZE * 2; -+ if (toIndex >= slotCount) { -+ toIndex = slotCount; -+ finished = true; -+ } -+ progress = (float) (currentIndex - fromIndex) / (slotCount - fromIndex); -+ return IntStream.range(currentIndex, toIndex).mapToObj(slot -> getItemInSlot(container, slot)); -+ } -+ -+ @Override -+ public float getCollectingProgress() { -+ return progress; -+ } -+ } -+ -+ public static class ContainerItemIterator extends SlottedItemIterator { -+ public ContainerItemIterator(int fromIndex) { -+ this(Container.class::cast, fromIndex); -+ } -+ -+ public ContainerItemIterator(Function containerFinder, int fromIndex) { -+ super(containerFinder, fromIndex); -+ } -+ -+ @Override -+ protected int getSlotCount(Container container) { -+ return container.getContainerSize(); -+ } -+ -+ @Override -+ protected ItemStack getItemInSlot(Container container, int slot) { -+ return container.getItem(slot); -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a046ae4e542efcadd0001b7225440c309a7a7570 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java -@@ -0,0 +1,59 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import io.netty.buffer.ByteBuf; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import org.jetbrains.annotations.NotNull; -+ -+public class JadeCodec { -+ public static final StreamCodec PRIMITIVE_STREAM_CODEC = new StreamCodec<>() { -+ @Override -+ public @NotNull Object decode(@NotNull ByteBuf buf) { -+ byte b = buf.readByte(); -+ if (b == 0) { -+ return false; -+ } else if (b == 1) { -+ return true; -+ } else if (b == 2) { -+ return ByteBufCodecs.VAR_INT.decode(buf); -+ } else if (b == 3) { -+ return ByteBufCodecs.FLOAT.decode(buf); -+ } else if (b == 4) { -+ return ByteBufCodecs.STRING_UTF8.decode(buf); -+ } else if (b > 20) { -+ return b - 20; -+ } -+ throw new IllegalArgumentException("Unknown primitive type: " + b); -+ } -+ -+ @Override -+ public void encode(@NotNull ByteBuf buf, @NotNull Object o) { -+ switch (o) { -+ case Boolean b -> buf.writeByte(b ? 1 : 0); -+ case Number n -> { -+ float f = n.floatValue(); -+ if (f != (int) f) { -+ buf.writeByte(3); -+ ByteBufCodecs.FLOAT.encode(buf, f); -+ } -+ int i = n.intValue(); -+ if (i <= Byte.MAX_VALUE - 20 && i >= 0) { -+ buf.writeByte(i + 20); -+ } else { -+ ByteBufCodecs.VAR_INT.encode(buf, i); -+ } -+ } -+ case String s -> { -+ buf.writeByte(4); -+ ByteBufCodecs.STRING_UTF8.encode(buf, s); -+ } -+ case Enum anEnum -> { -+ buf.writeByte(4); -+ ByteBufCodecs.STRING_UTF8.encode(buf, anEnum.name()); -+ } -+ case null -> throw new NullPointerException(); -+ default -> throw new IllegalArgumentException("Unknown primitive type: %s (%s)".formatted(o, o.getClass())); -+ } -+ } -+ }; -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java -new file mode 100644 -index 0000000000000000000000000000000000000000..81575cfa4c5a6f1a1aadbf9ea7743cd1d053f925 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java -@@ -0,0 +1,109 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import com.google.common.collect.Lists; -+import net.minecraft.advancements.critereon.ItemPredicate; -+import net.minecraft.core.Holder; -+import net.minecraft.core.HolderGetter; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.storage.loot.LootPool; -+import net.minecraft.world.level.storage.loot.LootTable; -+import net.minecraft.world.level.storage.loot.entries.AlternativesEntry; -+import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; -+import net.minecraft.world.level.storage.loot.entries.NestedLootTable; -+import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; -+import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; -+import net.minecraft.world.level.storage.loot.predicates.MatchTool; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.jade.tool.ShearsToolHandler; -+ -+import java.util.List; -+import java.util.Optional; -+import java.util.function.Function; -+ -+public class LootTableMineableCollector { -+ -+ private final HolderGetter lootRegistry; -+ private final ItemStack toolItem; -+ -+ public LootTableMineableCollector(HolderGetter lootRegistry, ItemStack toolItem) { -+ this.lootRegistry = lootRegistry; -+ this.toolItem = toolItem; -+ } -+ -+ public static @NotNull List execute(HolderGetter lootRegistry, ItemStack toolItem) { -+ LootTableMineableCollector collector = new LootTableMineableCollector(lootRegistry, toolItem); -+ List list = Lists.newArrayList(); -+ for (Block block : BuiltInRegistries.BLOCK) { -+ if (!ShearsToolHandler.getInstance().test(block.defaultBlockState()).isEmpty()) { -+ continue; -+ } -+ -+ if (block.getLootTable().isPresent()) { -+ LootTable lootTable = lootRegistry.get(block.getLootTable().get()).map(Holder::value).orElse(null); -+ if (collector.doLootTable(lootTable)) { -+ list.add(block); -+ } -+ } -+ } -+ return list; -+ } -+ -+ private boolean doLootTable(LootTable lootTable) { -+ if (lootTable == null || lootTable == LootTable.EMPTY) { -+ return false; -+ } -+ -+ for (LootPool pool : lootTable.pools) { -+ if (doLootPool(pool)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private boolean doLootPool(@NotNull LootPool lootPool) { -+ for (LootPoolEntryContainer entry : lootPool.entries) { -+ if (doLootPoolEntry(entry)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private boolean doLootPoolEntry(LootPoolEntryContainer entry) { -+ if (entry instanceof AlternativesEntry alternativesEntry) { -+ for (LootPoolEntryContainer child : alternativesEntry.children) { -+ if (doLootPoolEntry(child)) { -+ return true; -+ } -+ } -+ } else if (entry instanceof NestedLootTable nestedLootTable) { -+ LootTable lootTable = nestedLootTable.contents.map($ -> lootRegistry.get($).map(Holder::value).orElse(null), Function.identity()); -+ return doLootTable(lootTable); -+ } else { -+ return isCorrectConditions(entry.conditions, toolItem); -+ } -+ return false; -+ } -+ -+ public static boolean isCorrectConditions(@NotNull List conditions, ItemStack toolItem) { -+ if (conditions.size() != 1) { -+ return false; -+ } -+ -+ LootItemCondition condition = conditions.getFirst(); -+ if (condition instanceof MatchTool(Optional predicate)) { -+ ItemPredicate itemPredicate = predicate.orElse(null); -+ return itemPredicate != null && itemPredicate.test(toolItem); -+ } else if (condition instanceof AnyOfCondition anyOfCondition) { -+ for (LootItemCondition child : anyOfCondition.terms) { -+ if (isCorrectConditions(List.of(child), toolItem)) { -+ return true; -+ } -+ } -+ } -+ return false; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cb5c8201958b3f444d990082d7aac615090cc2a8 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java -@@ -0,0 +1,120 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import com.google.common.cache.Cache; -+import com.google.common.cache.CacheBuilder; -+import com.google.common.collect.ImmutableList; -+import com.google.common.collect.Iterables; -+import net.minecraft.core.IdMapper; -+import net.minecraft.resources.ResourceLocation; -+import org.apache.commons.lang3.tuple.Pair; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.protocol.jade.JadeProtocol; -+import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; -+ -+import java.util.Collection; -+import java.util.Comparator; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.concurrent.ExecutionException; -+import java.util.stream.Stream; -+ -+public class PairHierarchyLookup implements IHierarchyLookup { -+ public final IHierarchyLookup first; -+ public final IHierarchyLookup second; -+ private final Cache, Class>, List> mergedCache = CacheBuilder.newBuilder().build(); -+ protected boolean idMapped; -+ @Nullable -+ protected IdMapper idMapper; -+ -+ public PairHierarchyLookup(IHierarchyLookup first, IHierarchyLookup second) { -+ this.first = first; -+ this.second = second; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public List getMerged(Object first, Object second) { -+ Objects.requireNonNull(first); -+ Objects.requireNonNull(second); -+ try { -+ return (List) mergedCache.get(Pair.of(first.getClass(), second.getClass()), () -> { -+ List firstList = this.first.get(first); -+ List secondList = this.second.get(second); -+ if (firstList.isEmpty()) { -+ return secondList; -+ } else if (secondList.isEmpty()) { -+ return firstList; -+ } -+ return ImmutableList.sortedCopyOf( -+ Comparator.comparingInt(JadeProtocol.priorities::byValue), -+ Iterables.concat(firstList, secondList) -+ ); -+ }); -+ } catch (ExecutionException e) { -+ LeavesLogger.LOGGER.severe(e.toString()); -+ } -+ return List.of(); -+ } -+ -+ @Override -+ public void idMapped() { -+ idMapped = true; -+ } -+ -+ @Override -+ public @Nullable IdMapper idMapper() { -+ return idMapper; -+ } -+ -+ @Override -+ public void register(Class clazz, T provider) { -+ if (first.isClassAcceptable(clazz)) { -+ first.register(clazz, provider); -+ } else if (second.isClassAcceptable(clazz)) { -+ second.register(clazz, provider); -+ } else { -+ throw new IllegalArgumentException("Class " + clazz + " is not acceptable"); -+ } -+ } -+ -+ @Override -+ public boolean isClassAcceptable(Class clazz) { -+ return first.isClassAcceptable(clazz) || second.isClassAcceptable(clazz); -+ } -+ -+ @Override -+ public List get(Class clazz) { -+ List result = first.get(clazz); -+ if (result.isEmpty()) { -+ result = second.get(clazz); -+ } -+ return result; -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return first.isEmpty() && second.isEmpty(); -+ } -+ -+ @Override -+ public Stream, Collection>> entries() { -+ return Stream.concat(first.entries(), second.entries()); -+ } -+ -+ @Override -+ public void invalidate() { -+ first.invalidate(); -+ second.invalidate(); -+ mergedCache.invalidateAll(); -+ } -+ -+ @Override -+ public void loadComplete(PriorityStore priorityStore) { -+ first.loadComplete(priorityStore); -+ second.loadComplete(priorityStore); -+ if (idMapped) { -+ idMapper = createIdMapper(); -+ } -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java -new file mode 100644 -index 0000000000000000000000000000000000000000..da4d5a7751b1076417e63b63dc1f91c0fcc73ff9 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java -@@ -0,0 +1,40 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Object2IntMap; -+ -+import java.util.Objects; -+import java.util.function.Function; -+import java.util.function.ToIntFunction; -+ -+public class PriorityStore { -+ -+ private final Object2IntMap priorities = new Object2IntLinkedOpenHashMap<>(); -+ private final Function keyGetter; -+ private final ToIntFunction defaultPriorityGetter; -+ -+ public PriorityStore(ToIntFunction defaultPriorityGetter, Function keyGetter) { -+ this.defaultPriorityGetter = defaultPriorityGetter; -+ this.keyGetter = keyGetter; -+ } -+ -+ public void put(V provider) { -+ Objects.requireNonNull(provider); -+ put(provider, defaultPriorityGetter.applyAsInt(provider)); -+ } -+ -+ public void put(V provider, int priority) { -+ Objects.requireNonNull(provider); -+ K uid = keyGetter.apply(provider); -+ Objects.requireNonNull(uid); -+ priorities.put(uid, priority); -+ } -+ -+ public int byValue(V value) { -+ return byKey(keyGetter.apply(value)); -+ } -+ -+ public int byKey(K id) { -+ return priorities.getInt(id); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..520eadbf6de55141524741b4e4063cd542ef7128 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java -@@ -0,0 +1,62 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import io.netty.buffer.ByteBuf; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.network.codec.ByteBufCodecs; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+ -+public class ViewGroup { -+ public static StreamCodec> codec(StreamCodec viewCodec) { -+ return StreamCodec.composite( -+ ByteBufCodecs.list().apply(viewCodec), -+ $ -> $.views, -+ ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), -+ $ -> Optional.ofNullable($.id), -+ ByteBufCodecs.optional(ByteBufCodecs.COMPOUND_TAG), -+ $ -> Optional.ofNullable($.extraData), -+ ViewGroup::new); -+ } -+ -+ public static StreamCodec>>> listCodec(StreamCodec viewCodec) { -+ return StreamCodec.composite( -+ ResourceLocation.STREAM_CODEC, -+ Map.Entry::getKey, -+ ByteBufCodecs.>list().apply(codec(viewCodec)), -+ Map.Entry::getValue, -+ Map::entry); -+ } -+ -+ public List views; -+ @Nullable -+ public String id; -+ @Nullable -+ protected CompoundTag extraData; -+ -+ public ViewGroup(List views) { -+ this(views, Optional.empty(), Optional.empty()); -+ } -+ -+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") -+ public ViewGroup(List views, Optional id, Optional extraData) { -+ this.views = views; -+ this.id = id.orElse(null); -+ this.extraData = extraData.orElse(null); -+ } -+ -+ public CompoundTag getExtraData() { -+ if (extraData == null) { -+ extraData = new CompoundTag(); -+ } -+ return extraData; -+ } -+ -+ public void setProgress(float progress) { -+ getExtraData().putFloat("Progress", progress); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9b49efd3255e4521824e1b21b88e3625eedcc7a5 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java -@@ -0,0 +1,104 @@ -+package org.leavesmc.leaves.protocol.jade.util; -+ -+import com.google.common.collect.Lists; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.block.Block; -+import org.apache.commons.lang3.tuple.Pair; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.protocol.jade.accessor.Accessor; -+import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; -+import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; -+ -+import java.util.Collection; -+import java.util.List; -+import java.util.Map; -+import java.util.function.Function; -+import java.util.stream.Stream; -+ -+public class WrappedHierarchyLookup extends HierarchyLookup { -+ public final List, Function, @Nullable Object>>> overrides = Lists.newArrayList(); -+ private boolean empty = true; -+ -+ public WrappedHierarchyLookup() { -+ super(Object.class); -+ } -+ -+ @NotNull -+ public static WrappedHierarchyLookup forAccessor() { -+ WrappedHierarchyLookup lookup = new WrappedHierarchyLookup<>(); -+ lookup.overrides.add(Pair.of( -+ new HierarchyLookup<>(Block.class), accessor -> { -+ if (accessor instanceof BlockAccessor blockAccessor) { -+ return blockAccessor.getBlock(); -+ } -+ return null; -+ })); -+ return lookup; -+ } -+ -+ public List wrappedGet(Accessor accessor) { -+ List list = Lists.newArrayList(); -+ for (var override : overrides) { -+ Object o = override.getRight().apply(accessor); -+ if (o != null) { -+ list.addAll(override.getLeft().get(o)); -+ } -+ } -+ list.addAll(get(accessor.getTarget())); -+ return list; -+ } -+ -+ @Override -+ public void register(Class clazz, T provider) { -+ for (var override : overrides) { -+ if (override.getLeft().isClassAcceptable(clazz)) { -+ override.getLeft().register(clazz, provider); -+ empty = false; -+ return; -+ } -+ } -+ super.register(clazz, provider); -+ empty = false; -+ } -+ -+ @Override -+ public boolean isClassAcceptable(Class clazz) { -+ for (var override : overrides) { -+ if (override.getLeft().isClassAcceptable(clazz)) { -+ return true; -+ } -+ } -+ return super.isClassAcceptable(clazz); -+ } -+ -+ @Override -+ public void invalidate() { -+ for (var override : overrides) { -+ override.getLeft().invalidate(); -+ } -+ super.invalidate(); -+ } -+ -+ @Override -+ public void loadComplete(PriorityStore priorityStore) { -+ for (var override : overrides) { -+ override.getLeft().loadComplete(priorityStore); -+ } -+ super.loadComplete(priorityStore); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return empty; -+ } -+ -+ @Override -+ public Stream, Collection>> entries() { -+ Stream, Collection>> stream = super.entries(); -+ for (var override : overrides) { -+ stream = Stream.concat(stream, override.getLeft().entries()); -+ } -+ return stream; -+ } -+} diff --git a/patches/server/0030-Leaves-Appleskin-Protocol.patch b/patches/server/0030-Leaves-Appleskin-Protocol.patch deleted file mode 100644 index 5132b019..00000000 --- a/patches/server/0030-Leaves-Appleskin-Protocol.patch +++ /dev/null @@ -1,160 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Wed, 25 Jan 2023 11:03:53 +0800 -Subject: [PATCH] Leaves: Appleskin Protocol - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -This patch is Powered by AppleSkin (https://github.com/squeek502/AppleSkin) - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index 4e91895387b214cadc658b150ed6b88efb58d6cd..90ab5ec9906207cbe835e56eb4560cd89af9fcfe 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -10,9 +10,13 @@ public class ProtocolSupport extends ConfigModules { - } - - public static boolean jadeProtocol = false; -+ public static boolean appleskinProtocol = false; -+ public static int appleskinSyncTickInterval = 20; - - @Override - public void onLoaded() { - jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); -+ appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol); -+ appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); - } - } -diff --git a/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..eb51acf0351769af0910ebb2d4a5abf542cbfb90 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java -@@ -0,0 +1,126 @@ -+package org.leavesmc.leaves.protocol; -+ -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.food.FoodData; -+import net.minecraft.world.level.GameRules; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.core.LeavesProtocol; -+import org.leavesmc.leaves.protocol.core.ProtocolHandler; -+import org.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+@LeavesProtocol(namespace = "appleskin") -+public class AppleSkinProtocol { -+ -+ public static final String PROTOCOL_ID = "appleskin"; -+ -+ private static final ResourceLocation SATURATION_KEY = id("saturation"); -+ private static final ResourceLocation EXHAUSTION_KEY = id("exhaustion"); -+ private static final ResourceLocation NATURAL_REGENERATION_KEY = id("natural_regeneration"); -+ -+ private static final float MINIMUM_EXHAUSTION_CHANGE_THRESHOLD = 0.01F; -+ -+ private static final Map previousSaturationLevels = new HashMap<>(); -+ private static final Map previousExhaustionLevels = new HashMap<>(); -+ private static final Map previousNaturalRegeneration = new HashMap<>(); -+ -+ private static final Map> subscribedChannels = new HashMap<>(); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return new ResourceLocation(PROTOCOL_ID, path); -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { -+ resetPlayerData(player); -+ } -+ } -+ -+ @ProtocolHandler.PlayerLeave -+ public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { -+ subscribedChannels.remove(player); -+ resetPlayerData(player); -+ } -+ } -+ -+ @ProtocolHandler.MinecraftRegister(ignoreId = true) -+ public static void onPlayerSubscribed(@NotNull ServerPlayer player, String channel) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { -+ subscribedChannels.computeIfAbsent(player, k -> new HashSet<>()).add(channel); -+ } -+ } -+ -+ @ProtocolHandler.Ticker -+ public static void tick() { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { -+ if (MinecraftServer.getServer().getTickCount() % org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinSyncTickInterval != 0) { -+ return; -+ } -+ -+ for (Map.Entry> entry : subscribedChannels.entrySet()) { -+ ServerPlayer player = entry.getKey(); -+ FoodData data = player.getFoodData(); -+ -+ for (String channel : entry.getValue()) { -+ switch (channel) { -+ case "saturation" -> { -+ float saturation = data.getSaturationLevel(); -+ Float previousSaturation = previousSaturationLevels.get(player); -+ if (previousSaturation == null || saturation != previousSaturation) { -+ ProtocolUtils.sendPayloadPacket(player, SATURATION_KEY, buf -> buf.writeFloat(saturation)); -+ previousSaturationLevels.put(player, saturation); -+ } -+ } -+ -+ case "exhaustion" -> { -+ float exhaustion = data.exhaustionLevel; -+ Float previousExhaustion = previousExhaustionLevels.get(player); -+ if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= MINIMUM_EXHAUSTION_CHANGE_THRESHOLD) { -+ ProtocolUtils.sendPayloadPacket(player, EXHAUSTION_KEY, buf -> buf.writeFloat(exhaustion)); -+ previousExhaustionLevels.put(player, exhaustion); -+ } -+ } -+ -+ case "natural_regeneration" -> { -+ boolean regeneration = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION); -+ Boolean previousRegeneration = previousNaturalRegeneration.get(player); -+ if (previousRegeneration == null || regeneration != previousRegeneration) { -+ ProtocolUtils.sendPayloadPacket(player, NATURAL_REGENERATION_KEY, buf -> buf.writeBoolean(regeneration)); -+ previousNaturalRegeneration.put(player, regeneration); -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ @ProtocolHandler.ReloadServer -+ public static void onServerReload() { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.appleskinProtocol) { -+ disableAllPlayer(); -+ } -+ } -+ -+ public static void disableAllPlayer() { -+ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { -+ onPlayerLoggedOut(player); -+ } -+ } -+ -+ private static void resetPlayerData(@NotNull ServerPlayer player) { -+ previousExhaustionLevels.remove(player); -+ previousSaturationLevels.remove(player); -+ previousNaturalRegeneration.remove(player); -+ } -+} diff --git a/patches/server/0031-Leaves-Xaero-Map-Protocol.patch b/patches/server/0031-Leaves-Xaero-Map-Protocol.patch deleted file mode 100644 index cb51144e..00000000 --- a/patches/server/0031-Leaves-Xaero-Map-Protocol.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Fri, 27 Jan 2023 09:42:57 +0800 -Subject: [PATCH] Leaves: Xaero Map Protocol - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -This patch is Powered by Xaero Map - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index f846281cf11357249aea45e1add3e05150bb5615..243d5f479a11d9cbc867f034f9fa55ff7e27fb92 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1305,6 +1305,7 @@ public abstract class PlayerList { - player.connection.send(new ClientboundInitializeBorderPacket(worldborder)); - player.connection.send(new ClientboundSetTimePacket(world.getGameTime(), world.getDayTime(), world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); - player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(world.getSharedSpawnPos(), world.getSharedSpawnAngle())); -+ org.leavesmc.leaves.protocol.XaeroMapProtocol.onSendWorldInfo(player); // Leaves - xaero map protocol - if (world.isRaining()) { - // CraftBukkit start - handle player weather - // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F)); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index 90ab5ec9906207cbe835e56eb4560cd89af9fcfe..e7923e98fb05494e81412f40503b8cc1a219747b 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -3,6 +3,8 @@ package org.dreeam.leaf.config.modules.network; - import org.dreeam.leaf.config.ConfigModules; - import org.dreeam.leaf.config.EnumConfigCategory; - -+import java.util.Random; -+ - public class ProtocolSupport extends ConfigModules { - - public String getBasePath() { -@@ -12,11 +14,15 @@ public class ProtocolSupport extends ConfigModules { - public static boolean jadeProtocol = false; - public static boolean appleskinProtocol = false; - public static int appleskinSyncTickInterval = 20; -+ public static boolean xaeroMapProtocol = false; -+ public static int xaeroMapServerID = new Random().nextInt(); - - @Override - public void onLoaded() { - jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); - appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol); - appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); -+ xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol); -+ xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID); - } - } -diff --git a/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9e35dfaf8bb5511b4cd0a71175d7ecb6d835042f ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/XaeroMapProtocol.java -@@ -0,0 +1,41 @@ -+package org.leavesmc.leaves.protocol; -+ -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.core.LeavesProtocol; -+import org.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+@LeavesProtocol(namespace = {"xaerominimap", "xaeroworldmap"}) -+public class XaeroMapProtocol { -+ -+ public static final String PROTOCOL_ID_MINI = "xaerominimap"; -+ public static final String PROTOCOL_ID_WORLD = "xaeroworldmap"; -+ -+ private static final ResourceLocation MINIMAP_KEY = idMini("main"); -+ private static final ResourceLocation WORLDMAP_KEY = idWorld("main"); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation idMini(String path) { -+ return new ResourceLocation(PROTOCOL_ID_MINI, path); -+ } -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation idWorld(String path) { -+ return new ResourceLocation(PROTOCOL_ID_WORLD, path); -+ } -+ -+ public static void onSendWorldInfo(@NotNull ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.xaeroMapProtocol) { -+ ProtocolUtils.sendPayloadPacket(player, MINIMAP_KEY, buf -> { -+ buf.writeByte(0); -+ buf.writeInt(org.dreeam.leaf.config.modules.network.ProtocolSupport.xaeroMapServerID); -+ }); -+ ProtocolUtils.sendPayloadPacket(player, WORLDMAP_KEY, buf -> { -+ buf.writeByte(0); -+ buf.writeInt(org.dreeam.leaf.config.modules.network.ProtocolSupport.xaeroMapServerID); -+ }); -+ } -+ } -+} diff --git a/patches/server/0032-Leaves-Syncmatica-Protocol.patch b/patches/server/0032-Leaves-Syncmatica-Protocol.patch deleted file mode 100644 index 085f5684..00000000 --- a/patches/server/0032-Leaves-Syncmatica-Protocol.patch +++ /dev/null @@ -1,2092 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Thu, 18 May 2023 16:16:56 +0800 -Subject: [PATCH] Leaves: Syncmatica Protocol - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -This patch is Powered by Syncmatica (https://github.com/End-Tech/syncmatica) - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 8018b41a8e36a8b27de369f00e250c7dd9340907..6f00bc85205a4c481d85f95e30aeb61ab3db1e8a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -324,6 +324,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - Objects.requireNonNull(server); - this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, server::enforceSecureProfile); - this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat -+ this.exchangeTarget = new org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget(this); // Leaves - Syncmatica Protocol - } - - // CraftBukkit start - add fields and methods -@@ -356,6 +357,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - ); - // Purpur end - -+ public final org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget exchangeTarget; // Leaves - Syncmatica Protocol -+ - @Override - public void tick() { - if (this.ackBlockChangesUpTo > -1) { -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index e7923e98fb05494e81412f40503b8cc1a219747b..db48291d191e1eefb7e497dda7430fc1644bb5ea 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -16,6 +16,9 @@ public class ProtocolSupport extends ConfigModules { - public static int appleskinSyncTickInterval = 20; - public static boolean xaeroMapProtocol = false; - public static int xaeroMapServerID = new Random().nextInt(); -+ public static boolean syncmaticaProtocol = false; -+ public static boolean syncmaticaQuota = false; -+ public static int syncmaticaQuotaLimit = 40000000; - - @Override - public void onLoaded() { -@@ -24,5 +27,12 @@ public class ProtocolSupport extends ConfigModules { - appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); - xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol); - xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID); -+ syncmaticaProtocol = config.getBoolean(getBasePath() + ".syncmatica-protocol", syncmaticaProtocol); -+ syncmaticaQuota = config.getBoolean(getBasePath() + ".syncmatica-quota", syncmaticaQuota); -+ syncmaticaQuotaLimit = config.getInt(getBasePath() + ".syncmatica-quota-limit", syncmaticaQuotaLimit); -+ -+ if (syncmaticaProtocol) { -+ org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init(); -+ } - } - } -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0704ac7825c69e69097b3e7c77763044f9fa9e1e ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java -@@ -0,0 +1,396 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.mojang.authlib.GameProfile; -+import io.netty.buffer.Unpooled; -+import net.minecraft.core.BlockPos; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.chat.Component; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.core.LeavesProtocol; -+import org.leavesmc.leaves.protocol.core.LeavesProtocolManager; -+import org.leavesmc.leaves.protocol.core.ProtocolHandler; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.DownloadExchange; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.Exchange; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.ModifyExchangeServer; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.UploadExchange; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.VersionHandshakeServer; -+ -+import java.io.File; -+import java.io.FileNotFoundException; -+import java.io.IOException; -+import java.security.NoSuchAlgorithmException; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.UUID; -+ -+@LeavesProtocol(namespace = "syncmatica") -+public class CommunicationManager { -+ -+ private static final Map> downloadingFile = new HashMap<>(); -+ private static final Map playerMap = new HashMap<>(); -+ -+ protected static final Collection broadcastTargets = new ArrayList<>(); -+ -+ protected static final Map downloadState = new HashMap<>(); -+ protected static final Map modifyState = new HashMap<>(); -+ -+ protected static final Rotation[] rotOrdinals = Rotation.values(); -+ protected static final Mirror[] mirOrdinals = Mirror.values(); -+ -+ public CommunicationManager() { -+ } -+ -+ public static GameProfile getGameProfile(final ExchangeTarget exchangeTarget) { -+ return playerMap.get(exchangeTarget).getGameProfile(); -+ } -+ -+ public void sendMessage(final @NotNull ExchangeTarget client, final MessageType type, final String identifier) { -+ if (client.getFeatureSet().hasFeature(Feature.MESSAGE)) { -+ final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ newPacketBuf.writeUtf(type.toString()); -+ newPacketBuf.writeUtf(identifier); -+ client.sendPacket(PacketType.MESSAGE.identifier, newPacketBuf); -+ } else if (playerMap.containsKey(client)) { -+ final ServerPlayer player = playerMap.get(client); -+ player.sendSystemMessage(Component.literal("Syncmatica " + type.toString() + " " + identifier)); -+ } -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerJoin(ServerPlayer player) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaProtocol) { -+ return; -+ } -+ final ExchangeTarget newPlayer = player.connection.exchangeTarget; -+ final VersionHandshakeServer hi = new VersionHandshakeServer(newPlayer); -+ playerMap.put(newPlayer, player); -+ final GameProfile profile = player.getGameProfile(); -+ SyncmaticaProtocol.getPlayerIdentifierProvider().updateName(profile.getId(), profile.getName()); -+ startExchangeUnchecked(hi); -+ } -+ -+ @ProtocolHandler.PlayerLeave -+ public static void onPlayerLeave(ServerPlayer player) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaProtocol) { -+ return; -+ } -+ final ExchangeTarget oldPlayer = player.connection.exchangeTarget; -+ final Collection potentialMessageTarget = oldPlayer.getExchanges(); -+ if (potentialMessageTarget != null) { -+ for (final Exchange target : potentialMessageTarget) { -+ target.close(false); -+ handleExchange(target); -+ } -+ } -+ broadcastTargets.remove(oldPlayer); -+ playerMap.remove(oldPlayer); -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = SyncmaticaPayload.class, payloadId = "main") -+ public static void onPacketGet(ServerPlayer player, SyncmaticaPayload payload) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaProtocol) { -+ return; -+ } -+ onPacket(player.connection.exchangeTarget, payload.packetType(), payload.data()); -+ } -+ -+ public static void onPacket(final @NotNull ExchangeTarget source, final ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ Exchange handler = null; -+ final Collection potentialMessageTarget = source.getExchanges(); -+ if (potentialMessageTarget != null) { -+ for (final Exchange target : potentialMessageTarget) { -+ if (target.checkPacket(id, packetBuf)) { -+ target.handle(id, packetBuf); -+ handler = target; -+ break; -+ } -+ } -+ } -+ if (handler == null) { -+ handle(source, id, packetBuf); -+ } else if (handler.isFinished()) { -+ notifyClose(handler); -+ } -+ } -+ -+ protected static void handle(ExchangeTarget source, @NotNull ResourceLocation id, FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.REQUEST_LITEMATIC.identifier)) { -+ final UUID syncmaticaId = packetBuf.readUUID(); -+ final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(syncmaticaId); -+ if (placement == null) { -+ return; -+ } -+ final File toUpload = SyncmaticaProtocol.getFileStorage().getLocalLitematic(placement); -+ final UploadExchange upload; -+ try { -+ upload = new UploadExchange(placement, toUpload, source); -+ } catch (final FileNotFoundException e) { -+ e.printStackTrace(); -+ return; -+ } -+ startExchange(upload); -+ return; -+ } -+ if (id.equals(PacketType.REGISTER_METADATA.identifier)) { -+ final ServerPlacement placement = receiveMetaData(packetBuf, source); -+ if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) { -+ cancelShare(source, placement); -+ return; -+ } -+ -+ final GameProfile profile = playerMap.get(source).getGameProfile(); -+ final PlayerIdentifier playerIdentifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(profile); -+ if (!placement.getOwner().equals(playerIdentifier)) { -+ placement.setOwner(playerIdentifier); -+ placement.setLastModifiedBy(playerIdentifier); -+ } -+ -+ if (!SyncmaticaProtocol.getFileStorage().getLocalState(placement).isLocalFileReady()) { -+ if (SyncmaticaProtocol.getFileStorage().getLocalState(placement) == LocalLitematicState.DOWNLOADING_LITEMATIC) { -+ downloadingFile.computeIfAbsent(placement.getHash(), key -> new ArrayList<>()).add(placement); -+ return; -+ } -+ try { -+ download(placement, source); -+ } catch (final Exception e) { -+ e.printStackTrace(); -+ } -+ return; -+ } -+ -+ addPlacement(source, placement); -+ return; -+ } -+ if (id.equals(PacketType.REMOVE_SYNCMATIC.identifier)) { -+ final UUID placementId = packetBuf.readUUID(); -+ final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId); -+ if (placement != null) { -+ if (!getGameProfile(source).getId().equals(placement.getOwner().uuid)) { -+ return; -+ } -+ -+ final Exchange modifier = getModifier(placement); -+ if (modifier != null) { -+ modifier.close(true); -+ notifyClose(modifier); -+ } -+ SyncmaticaProtocol.getSyncmaticManager().removePlacement(placement); -+ for (final ExchangeTarget client : broadcastTargets) { -+ final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ newPacketBuf.writeUUID(placement.getId()); -+ client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, newPacketBuf); -+ } -+ } -+ } -+ if (id.equals(PacketType.MODIFY_REQUEST.identifier)) { -+ final UUID placementId = packetBuf.readUUID(); -+ final ModifyExchangeServer modifier = new ModifyExchangeServer(placementId, source); -+ startExchange(modifier); -+ } -+ } -+ -+ protected static void handleExchange(Exchange exchange) { -+ if (exchange instanceof DownloadExchange) { -+ final ServerPlacement p = ((DownloadExchange) exchange).getPlacement(); -+ -+ if (exchange.isSuccessful()) { -+ addPlacement(exchange.getPartner(), p); -+ if (downloadingFile.containsKey(p.getHash())) { -+ for (final ServerPlacement placement : downloadingFile.get(p.getHash())) { -+ addPlacement(exchange.getPartner(), placement); -+ } -+ } -+ } else { -+ cancelShare(exchange.getPartner(), p); -+ if (downloadingFile.containsKey(p.getHash())) { -+ for (final ServerPlacement placement : downloadingFile.get(p.getHash())) { -+ cancelShare(exchange.getPartner(), placement); -+ } -+ } -+ } -+ -+ downloadingFile.remove(p.getHash()); -+ return; -+ } -+ if (exchange instanceof VersionHandshakeServer && exchange.isSuccessful()) { -+ broadcastTargets.add(exchange.getPartner()); -+ } -+ if (exchange instanceof ModifyExchangeServer && exchange.isSuccessful()) { -+ final ServerPlacement placement = ((ModifyExchangeServer) exchange).getPlacement(); -+ for (final ExchangeTarget client : broadcastTargets) { -+ if (client.getFeatureSet().hasFeature(Feature.MODIFY)) { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placement.getId()); -+ putPositionData(placement, buf, client); -+ if (client.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ buf.writeUUID(placement.getLastModifiedBy().uuid); -+ buf.writeUtf(placement.getLastModifiedBy().getName()); -+ } -+ client.sendPacket(PacketType.MODIFY.identifier, buf); -+ } else { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placement.getId()); -+ client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, buf); -+ sendMetaData(placement, client); -+ } -+ } -+ } -+ } -+ -+ private static void addPlacement(final ExchangeTarget t, final @NotNull ServerPlacement placement) { -+ if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) { -+ cancelShare(t, placement); -+ return; -+ } -+ SyncmaticaProtocol.getSyncmaticManager().addPlacement(placement); -+ for (final ExchangeTarget target : broadcastTargets) { -+ sendMetaData(placement, target); -+ } -+ } -+ -+ private static void cancelShare(final @NotNull ExchangeTarget source, final @NotNull ServerPlacement placement) { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(placement.getId()); -+ source.sendPacket(PacketType.CANCEL_SHARE.identifier, FriendlyByteBuf); -+ } -+ -+ public static void sendMetaData(final ServerPlacement metaData, final ExchangeTarget target) { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ putMetaData(metaData, buf, target); -+ target.sendPacket(PacketType.REGISTER_METADATA.identifier, buf); -+ } -+ -+ public static void putMetaData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ buf.writeUUID(metaData.getId()); -+ -+ buf.writeUtf(SyncmaticaProtocol.sanitizeFileName(metaData.getName())); -+ buf.writeUUID(metaData.getHash()); -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ buf.writeUUID(metaData.getOwner().uuid); -+ buf.writeUtf(metaData.getOwner().getName()); -+ buf.writeUUID(metaData.getLastModifiedBy().uuid); -+ buf.writeUtf(metaData.getLastModifiedBy().getName()); -+ } -+ -+ putPositionData(metaData, buf, exchangeTarget); -+ } -+ -+ public static void putPositionData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ buf.writeBlockPos(metaData.getPosition()); -+ buf.writeUtf(metaData.getDimension()); -+ buf.writeInt(metaData.getRotation().ordinal()); -+ buf.writeInt(metaData.getMirror().ordinal()); -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ if (metaData.getSubRegionData().getModificationData() == null) { -+ buf.writeInt(0); -+ return; -+ } -+ -+ final Collection regionData = metaData.getSubRegionData().getModificationData().values(); -+ buf.writeInt(regionData.size()); -+ -+ for (final SubRegionPlacementModification subPlacement : regionData) { -+ buf.writeUtf(subPlacement.name); -+ buf.writeBlockPos(subPlacement.position); -+ buf.writeInt(subPlacement.rotation.ordinal()); -+ buf.writeInt(subPlacement.mirror.ordinal()); -+ } -+ } -+ } -+ -+ public static ServerPlacement receiveMetaData(final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ final UUID id = buf.readUUID(); -+ -+ final String fileName = SyncmaticaProtocol.sanitizeFileName(buf.readUtf(32767)); -+ final UUID hash = buf.readUUID(); -+ -+ PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER; -+ PlayerIdentifier lastModifiedBy = PlayerIdentifier.MISSING_PLAYER; -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ final PlayerIdentifierProvider provider = SyncmaticaProtocol.getPlayerIdentifierProvider(); -+ owner = provider.createOrGet(buf.readUUID(), buf.readUtf(32767)); -+ lastModifiedBy = provider.createOrGet(buf.readUUID(), buf.readUtf(32767)); -+ } -+ -+ final ServerPlacement placement = new ServerPlacement(id, fileName, hash, owner); -+ placement.setLastModifiedBy(lastModifiedBy); -+ -+ receivePositionData(placement, buf, exchangeTarget); -+ -+ return placement; -+ } -+ -+ public static void receivePositionData(final @NotNull ServerPlacement placement, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ final BlockPos pos = buf.readBlockPos(); -+ final String dimensionId = buf.readUtf(32767); -+ final Rotation rot = rotOrdinals[buf.readInt()]; -+ final Mirror mir = mirOrdinals[buf.readInt()]; -+ placement.move(dimensionId, pos, rot, mir); -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ final SubRegionData subRegionData = placement.getSubRegionData(); -+ subRegionData.reset(); -+ final int limit = buf.readInt(); -+ for (int i = 0; i < limit; i++) { -+ subRegionData.modify(buf.readUtf(32767), buf.readBlockPos(), rotOrdinals[buf.readInt()], mirOrdinals[buf.readInt()]); -+ } -+ } -+ } -+ -+ public static void download(final ServerPlacement syncmatic, final ExchangeTarget source) throws NoSuchAlgorithmException, IOException { -+ if (!SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).isReadyForDownload()) { -+ throw new IllegalArgumentException(syncmatic.toString() + " is not ready for download local state is: " + SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).toString()); -+ } -+ final File toDownload = SyncmaticaProtocol.getFileStorage().createLocalLitematic(syncmatic); -+ final Exchange downloadExchange = new DownloadExchange(syncmatic, toDownload, source); -+ setDownloadState(syncmatic, true); -+ startExchange(downloadExchange); -+ } -+ -+ public static void setDownloadState(final @NotNull ServerPlacement syncmatic, final boolean b) { -+ downloadState.put(syncmatic.getHash(), b); -+ } -+ -+ public static boolean getDownloadState(final @NotNull ServerPlacement syncmatic) { -+ return downloadState.getOrDefault(syncmatic.getHash(), false); -+ } -+ -+ public static void setModifier(final @NotNull ServerPlacement syncmatic, final Exchange exchange) { -+ modifyState.put(syncmatic.getHash(), exchange); -+ } -+ -+ public static Exchange getModifier(final @NotNull ServerPlacement syncmatic) { -+ return modifyState.get(syncmatic.getHash()); -+ } -+ -+ public static void startExchange(final @NotNull Exchange newExchange) { -+ if (!broadcastTargets.contains(newExchange.getPartner())) { -+ throw new IllegalArgumentException(newExchange.getPartner().toString() + " is not a valid ExchangeTarget"); -+ } -+ startExchangeUnchecked(newExchange); -+ } -+ -+ protected static void startExchangeUnchecked(final @NotNull Exchange newExchange) { -+ newExchange.getPartner().getExchanges().add(newExchange); -+ newExchange.init(); -+ if (newExchange.isFinished()) { -+ notifyClose(newExchange); -+ } -+ } -+ -+ public static void notifyClose(final @NotNull Exchange e) { -+ e.getPartner().getExchanges().remove(e); -+ handleExchange(e); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7cb3465b88411c46e79ce661ac7a4bddcf5b33e2 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/Feature.java -@@ -0,0 +1,23 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.Nullable; -+ -+public enum Feature { -+ CORE, -+ FEATURE, -+ MODIFY, -+ MESSAGE, -+ QUOTA, -+ DEBUG, -+ CORE_EX; -+ -+ @Nullable -+ public static Feature fromString(final String s) { -+ for (final Feature f : Feature.values()) { -+ if (f.toString().equals(s)) { -+ return f; -+ } -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ddd0f498feb2ad62134ae15a3ddb21527f2f24bf ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FeatureSet.java -@@ -0,0 +1,67 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Map; -+ -+public class FeatureSet { -+ -+ private static final Map versionFeatures; -+ private final Collection features; -+ -+ @Nullable -+ public static FeatureSet fromVersionString(@NotNull String version) { -+ if (version.matches("^\\d+(\\.\\d+){2,4}$")) { -+ final int minSize = version.indexOf("."); -+ while (version.length() > minSize) { -+ if (versionFeatures.containsKey(version)) { -+ return versionFeatures.get(version); -+ } -+ final int lastDot = version.lastIndexOf("."); -+ version = version.substring(0, lastDot); -+ } -+ } -+ return null; -+ } -+ -+ @NotNull -+ public static FeatureSet fromString(final @NotNull String features) { -+ final FeatureSet featureSet = new FeatureSet(new ArrayList<>()); -+ for (final String feature : features.split("\n")) { -+ final Feature f = Feature.fromString(feature); -+ if (f != null) { -+ featureSet.features.add(f); -+ } -+ } -+ return featureSet; -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder output = new StringBuilder(); -+ boolean b = false; -+ for (final Feature feature : features) { -+ output.append(b ? "\n" + feature.toString() : feature.toString()); -+ b = true; -+ } -+ return output.toString(); -+ } -+ -+ public FeatureSet(final Collection features) { -+ this.features = features; -+ } -+ -+ public boolean hasFeature(final Feature f) { -+ return features.contains(f); -+ } -+ -+ static { -+ versionFeatures = new HashMap<>(); -+ versionFeatures.put("0.1", new FeatureSet(Collections.singletonList(Feature.CORE))); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9139394e87e23190fbfdd82295314b0d50f1acca ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/FileStorage.java -@@ -0,0 +1,80 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.IOException; -+import java.util.HashMap; -+import java.util.UUID; -+ -+public class FileStorage { -+ -+ private final HashMap buffer = new HashMap<>(); -+ -+ public LocalLitematicState getLocalState(final ServerPlacement placement) { -+ final File localFile = getSchematicPath(placement); -+ if (localFile.isFile()) { -+ if (isDownloading(placement)) { -+ return LocalLitematicState.DOWNLOADING_LITEMATIC; -+ } -+ if ((buffer.containsKey(placement) && buffer.get(placement) == localFile.lastModified()) || hashCompare(localFile, placement)) { -+ return LocalLitematicState.LOCAL_LITEMATIC_PRESENT; -+ } -+ return LocalLitematicState.LOCAL_LITEMATIC_DESYNC; -+ } -+ return LocalLitematicState.NO_LOCAL_LITEMATIC; -+ } -+ -+ private boolean isDownloading(final ServerPlacement placement) { -+ return SyncmaticaProtocol.getCommunicationManager().getDownloadState(placement); -+ } -+ -+ public File getLocalLitematic(final ServerPlacement placement) { -+ if (getLocalState(placement).isLocalFileReady()) { -+ return getSchematicPath(placement); -+ } else { -+ return null; -+ } -+ } -+ -+ public File createLocalLitematic(final ServerPlacement placement) { -+ if (getLocalState(placement).isLocalFileReady()) { -+ throw new IllegalArgumentException(""); -+ } -+ final File file = getSchematicPath(placement); -+ if (file.exists()) { -+ file.delete(); -+ } -+ try { -+ file.createNewFile(); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ } -+ return file; -+ } -+ -+ private boolean hashCompare(final File localFile, final ServerPlacement placement) { -+ UUID hash = null; -+ try { -+ hash = SyncmaticaProtocol.createChecksum(new FileInputStream(localFile)); -+ } catch (final Exception e) { -+ e.printStackTrace(); -+ } -+ -+ if (hash == null) { -+ return false; -+ } -+ if (hash.equals(placement.getHash())) { -+ buffer.put(placement, localFile.lastModified()); -+ return true; -+ } -+ return false; -+ } -+ -+ @Contract("_ -> new") -+ private @NotNull File getSchematicPath(final @NotNull ServerPlacement placement) { -+ return new File(SyncmaticaProtocol.getLitematicFolder(), placement.getHash().toString() + ".litematic"); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java -new file mode 100644 -index 0000000000000000000000000000000000000000..299c57397371b368461a532d2eab695cf4f01fff ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java -@@ -0,0 +1,24 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+public enum LocalLitematicState { -+ NO_LOCAL_LITEMATIC(true, false), -+ LOCAL_LITEMATIC_DESYNC(true, false), -+ DOWNLOADING_LITEMATIC(false, false), -+ LOCAL_LITEMATIC_PRESENT(false, true); -+ -+ private final boolean downloadReady; -+ private final boolean fileReady; -+ -+ LocalLitematicState(final boolean downloadReady, final boolean fileReady) { -+ this.downloadReady = downloadReady; -+ this.fileReady = fileReady; -+ } -+ -+ public boolean isReadyForDownload() { -+ return downloadReady; -+ } -+ -+ public boolean isLocalFileReady() { -+ return fileReady; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b56ca12c650edd13dd7ff52e13c9d3aa465c32ec ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/MessageType.java -@@ -0,0 +1,8 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+public enum MessageType { -+ SUCCESS, -+ INFO, -+ WARNING, -+ ERROR -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..36c87c5cc586ad247e9aed26518c890f884010ae ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PacketType.java -@@ -0,0 +1,30 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import net.minecraft.resources.ResourceLocation; -+ -+public enum PacketType { -+ REGISTER_METADATA("register_metadata"), -+ CANCEL_SHARE("cancel_share"), -+ REQUEST_LITEMATIC("request_download"), -+ SEND_LITEMATIC("send_litematic"), -+ RECEIVED_LITEMATIC("received_litematic"), -+ FINISHED_LITEMATIC("finished_litematic"), -+ CANCEL_LITEMATIC("cancel_litematic"), -+ REMOVE_SYNCMATIC("remove_syncmatic"), -+ REGISTER_VERSION("register_version"), -+ CONFIRM_USER("confirm_user"), -+ FEATURE_REQUEST("feature_request"), -+ FEATURE("feature"), -+ MODIFY("modify"), -+ MODIFY_REQUEST("modify_request"), -+ MODIFY_REQUEST_DENY("modify_request_deny"), -+ MODIFY_REQUEST_ACCEPT("modify_request_accept"), -+ MODIFY_FINISH("modify_finish"), -+ MESSAGE("mesage"); -+ -+ public final ResourceLocation identifier; -+ -+ PacketType(final String id) { -+ identifier = new ResourceLocation(SyncmaticaProtocol.PROTOCOL_ID, id); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5891b0b49173acfb1a94051b98cb03b7a5ec9cd ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java -@@ -0,0 +1,37 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+ -+import java.util.UUID; -+ -+public class PlayerIdentifier { -+ -+ public static final UUID MISSING_PLAYER_UUID = UUID.fromString("4c1b738f-56fa-4011-8273-498c972424ea"); -+ public static final PlayerIdentifier MISSING_PLAYER = new PlayerIdentifier(MISSING_PLAYER_UUID, "No Player"); -+ -+ public final UUID uuid; -+ private String bufferedPlayerName; -+ -+ PlayerIdentifier(final UUID uuid, final String bufferedPlayerName) { -+ this.uuid = uuid; -+ this.bufferedPlayerName = bufferedPlayerName; -+ } -+ -+ public String getName() { -+ return bufferedPlayerName; -+ } -+ -+ public void updatePlayerName(final String name) { -+ bufferedPlayerName = name; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject jsonObject = new JsonObject(); -+ -+ jsonObject.add("uuid", new JsonPrimitive(uuid.toString())); -+ jsonObject.add("name", new JsonPrimitive(bufferedPlayerName)); -+ -+ return jsonObject; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..df2254edf89e17eec73a692577e77c613cd4c8e4 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java -@@ -0,0 +1,46 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonObject; -+import com.mojang.authlib.GameProfile; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget; -+ -+import java.util.HashMap; -+import java.util.Map; -+import java.util.UUID; -+ -+public class PlayerIdentifierProvider { -+ -+ private final Map identifiers = new HashMap<>(); -+ -+ public PlayerIdentifierProvider() { -+ identifiers.put(PlayerIdentifier.MISSING_PLAYER_UUID, PlayerIdentifier.MISSING_PLAYER); -+ } -+ -+ public PlayerIdentifier createOrGet(final ExchangeTarget exchangeTarget) { -+ return createOrGet(SyncmaticaProtocol.getCommunicationManager().getGameProfile(exchangeTarget)); -+ } -+ -+ public PlayerIdentifier createOrGet(final @NotNull GameProfile gameProfile) { -+ return createOrGet(gameProfile.getId(), gameProfile.getName()); -+ } -+ -+ public PlayerIdentifier createOrGet(final UUID uuid, final String playerName) { -+ return identifiers.computeIfAbsent(uuid, id -> new PlayerIdentifier(uuid, playerName)); -+ } -+ -+ public void updateName(final UUID uuid, final String playerName) { -+ createOrGet(uuid, playerName).updatePlayerName(playerName); -+ } -+ -+ public PlayerIdentifier fromJson(final @NotNull JsonObject obj) { -+ if (!obj.has("uuid") || !obj.has("name")) { -+ return PlayerIdentifier.MISSING_PLAYER; -+ } -+ -+ final UUID jsonUUID = UUID.fromString(obj.get("uuid").getAsString()); -+ return identifiers.computeIfAbsent(jsonUUID, -+ key -> new PlayerIdentifier(jsonUUID, obj.get("name").getAsString()) -+ ); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java -new file mode 100644 -index 0000000000000000000000000000000000000000..70759c9d3c01959169230503954f1f48c5392075 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java -@@ -0,0 +1,166 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.UUID; -+ -+public class ServerPlacement { -+ -+ private final UUID id; -+ -+ private final String fileName; -+ private final UUID hashValue; -+ -+ private PlayerIdentifier owner; -+ private PlayerIdentifier lastModifiedBy; -+ -+ private ServerPosition origin; -+ private Rotation rotation; -+ private Mirror mirror; -+ -+ private SubRegionData subRegionData = new SubRegionData(); -+ -+ public ServerPlacement(final UUID id, final String fileName, final UUID hashValue, final PlayerIdentifier owner) { -+ this.id = id; -+ this.fileName = fileName; -+ this.hashValue = hashValue; -+ this.owner = owner; -+ lastModifiedBy = owner; -+ } -+ -+ public UUID getId() { -+ return id; -+ } -+ -+ public String getName() { -+ return fileName; -+ } -+ -+ public UUID getHash() { -+ return hashValue; -+ } -+ -+ public String getDimension() { -+ return origin.getDimensionId(); -+ } -+ -+ public BlockPos getPosition() { -+ return origin.getBlockPosition(); -+ } -+ -+ public ServerPosition getOrigin() { -+ return origin; -+ } -+ -+ public Rotation getRotation() { -+ return rotation; -+ } -+ -+ public Mirror getMirror() { -+ return mirror; -+ } -+ -+ public ServerPlacement move(final String dimensionId, final BlockPos origin, final Rotation rotation, final Mirror mirror) { -+ move(new ServerPosition(origin, dimensionId), rotation, mirror); -+ return this; -+ } -+ -+ public ServerPlacement move(final ServerPosition origin, final Rotation rotation, final Mirror mirror) { -+ this.origin = origin; -+ this.rotation = rotation; -+ this.mirror = mirror; -+ return this; -+ } -+ -+ public PlayerIdentifier getOwner() { -+ return owner; -+ } -+ -+ public void setOwner(final PlayerIdentifier playerIdentifier) { -+ owner = playerIdentifier; -+ } -+ -+ public PlayerIdentifier getLastModifiedBy() { -+ return lastModifiedBy; -+ } -+ -+ public void setLastModifiedBy(final PlayerIdentifier lastModifiedBy) { -+ this.lastModifiedBy = lastModifiedBy; -+ } -+ -+ public SubRegionData getSubRegionData() { -+ return subRegionData; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject obj = new JsonObject(); -+ obj.add("id", new JsonPrimitive(id.toString())); -+ -+ obj.add("file_name", new JsonPrimitive(fileName)); -+ obj.add("hash", new JsonPrimitive(hashValue.toString())); -+ -+ obj.add("origin", origin.toJson()); -+ obj.add("rotation", new JsonPrimitive(rotation.name())); -+ obj.add("mirror", new JsonPrimitive(mirror.name())); -+ -+ obj.add("owner", owner.toJson()); -+ if (!owner.equals(lastModifiedBy)) { -+ obj.add("lastModifiedBy", lastModifiedBy.toJson()); -+ } -+ -+ if (subRegionData.isModified()) { -+ obj.add("subregionData", subRegionData.toJson()); -+ } -+ -+ return obj; -+ } -+ -+ @Nullable -+ public static ServerPlacement fromJson(final @NotNull JsonObject obj) { -+ if (obj.has("id") -+ && obj.has("file_name") -+ && obj.has("hash") -+ && obj.has("origin") -+ && obj.has("rotation") -+ && obj.has("mirror")) { -+ final UUID id = UUID.fromString(obj.get("id").getAsString()); -+ final String name = obj.get("file_name").getAsString(); -+ final UUID hashValue = UUID.fromString(obj.get("hash").getAsString()); -+ -+ PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER; -+ if (obj.has("owner")) { -+ owner = SyncmaticaProtocol.getPlayerIdentifierProvider().fromJson(obj.get("owner").getAsJsonObject()); -+ } -+ -+ final ServerPlacement newPlacement = new ServerPlacement(id, name, hashValue, owner); -+ final ServerPosition pos = ServerPosition.fromJson(obj.get("origin").getAsJsonObject()); -+ if (pos == null) { -+ return null; -+ } -+ newPlacement.origin = pos; -+ newPlacement.rotation = Rotation.valueOf(obj.get("rotation").getAsString()); -+ newPlacement.mirror = Mirror.valueOf(obj.get("mirror").getAsString()); -+ -+ if (obj.has("lastModifiedBy")) { -+ newPlacement.lastModifiedBy = SyncmaticaProtocol.getPlayerIdentifierProvider() -+ .fromJson(obj.get("lastModifiedBy").getAsJsonObject()); -+ } else { -+ newPlacement.lastModifiedBy = owner; -+ } -+ -+ if (obj.has("subregionData")) { -+ newPlacement.subRegionData = SubRegionData.fromJson(obj.get("subregionData")); -+ } -+ -+ return newPlacement; -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9775c6c42a253aaaf1ac7576dba3764c8593d7fe ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/ServerPosition.java -@@ -0,0 +1,51 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import net.minecraft.core.BlockPos; -+ -+public class ServerPosition { -+ -+ private final BlockPos position; -+ private final String dimensionId; -+ -+ public ServerPosition(final BlockPos pos, final String dim) { -+ position = pos; -+ dimensionId = dim; -+ } -+ -+ public BlockPos getBlockPosition() { -+ return position; -+ } -+ -+ public String getDimensionId() { -+ return dimensionId; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject obj = new JsonObject(); -+ final JsonArray arr = new JsonArray(); -+ arr.add(new JsonPrimitive(position.getX())); -+ arr.add(new JsonPrimitive(position.getY())); -+ arr.add(new JsonPrimitive(position.getZ())); -+ obj.add("position", arr); -+ obj.add("dimension", new JsonPrimitive(dimensionId)); -+ return obj; -+ } -+ -+ public static ServerPosition fromJson(final JsonObject obj) { -+ if (obj.has("position") && obj.has("dimension")) { -+ final int x; -+ final int y; -+ final int z; -+ final JsonArray arr = obj.get("position").getAsJsonArray(); -+ x = arr.get(0).getAsInt(); -+ y = arr.get(1).getAsInt(); -+ z = arr.get(2).getAsInt(); -+ final BlockPos pos = new BlockPos(x, y, z); -+ return new ServerPosition(pos, obj.get("dimension").getAsString()); -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..22fdf92dd686a2dc573eb60cd4d9a08ba7faec5a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionData.java -@@ -0,0 +1,90 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public class SubRegionData { -+ -+ private boolean isModified; -+ private Map modificationData; // is null when isModified is false -+ -+ public SubRegionData() { -+ this(false, null); -+ } -+ -+ public SubRegionData(final boolean isModified, final Map modificationData) { -+ this.isModified = isModified; -+ this.modificationData = modificationData; -+ } -+ -+ public void reset() { -+ isModified = false; -+ modificationData = null; -+ } -+ -+ public void modify(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { -+ modify(new SubRegionPlacementModification(name, position, rotation, mirror)); -+ } -+ -+ public void modify(final SubRegionPlacementModification subRegionPlacementModification) { -+ if (subRegionPlacementModification == null) { -+ return; -+ } -+ isModified = true; -+ if (modificationData == null) { -+ modificationData = new HashMap<>(); -+ } -+ modificationData.put(subRegionPlacementModification.name, subRegionPlacementModification); -+ } -+ -+ public boolean isModified() { -+ return isModified; -+ } -+ -+ public Map getModificationData() { -+ return modificationData; -+ } -+ -+ public JsonElement toJson() { -+ return modificationDataToJson(); -+ } -+ -+ @NotNull -+ private JsonElement modificationDataToJson() { -+ final JsonArray arr = new JsonArray(); -+ -+ for (final Map.Entry entry : modificationData.entrySet()) { -+ arr.add(entry.getValue().toJson()); -+ } -+ -+ return arr; -+ } -+ -+ @NotNull -+ public static SubRegionData fromJson(final @NotNull JsonElement obj) { -+ final SubRegionData newSubRegionData = new SubRegionData(); -+ -+ newSubRegionData.isModified = true; -+ -+ for (final JsonElement modification : obj.getAsJsonArray()) { -+ newSubRegionData.modify(SubRegionPlacementModification.fromJson(modification.getAsJsonObject())); -+ } -+ -+ return newSubRegionData; -+ } -+ -+ @Override -+ public String toString() { -+ if (!isModified) { -+ return "[]"; -+ } -+ return modificationData == null ? "[ERROR:null]" : modificationData.toString(); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a52e299be26d1ec13507dac8d68f7e5736117762 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java -@@ -0,0 +1,65 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public class SubRegionPlacementModification { -+ -+ public final String name; -+ public final BlockPos position; -+ public final Rotation rotation; -+ public final Mirror mirror; -+ -+ SubRegionPlacementModification(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { -+ this.name = name; -+ this.position = position; -+ this.rotation = rotation; -+ this.mirror = mirror; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject obj = new JsonObject(); -+ -+ final JsonArray arr = new JsonArray(); -+ arr.add(position.getX()); -+ arr.add(position.getY()); -+ arr.add(position.getZ()); -+ obj.add("position", arr); -+ -+ obj.add("name", new JsonPrimitive(name)); -+ obj.add("rotation", new JsonPrimitive(rotation.name())); -+ obj.add("mirror", new JsonPrimitive(mirror.name())); -+ -+ return obj; -+ } -+ -+ @Nullable -+ public static SubRegionPlacementModification fromJson(final @NotNull JsonObject obj) { -+ if (!obj.has("name") || !obj.has("position") || !obj.has("rotation") || !obj.has("mirror")) { -+ return null; -+ } -+ -+ final String name = obj.get("name").getAsString(); -+ final JsonArray arr = obj.get("position").getAsJsonArray(); -+ if (arr.size() != 3) { -+ return null; -+ } -+ -+ final BlockPos position = new BlockPos(arr.get(0).getAsInt(), arr.get(1).getAsInt(), arr.get(2).getAsInt()); -+ final Rotation rotation = Rotation.valueOf(obj.get("rotation").getAsString()); -+ final Mirror mirror = Mirror.valueOf(obj.get("mirror").getAsString()); -+ -+ return new SubRegionPlacementModification(name, position, rotation, mirror); -+ } -+ -+ @Override -+ public String toString() { -+ return String.format("[name=%s, position=%s, rotation=%s, mirror=%s]", name, position, rotation, mirror); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..27a056b306daa91400946a30e68ce01d47089ac8 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java -@@ -0,0 +1,108 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.GsonBuilder; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonParser; -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.File; -+import java.io.FileReader; -+import java.io.FileWriter; -+import java.io.IOException; -+import java.util.Collection; -+import java.util.HashMap; -+import java.util.Map; -+import java.util.UUID; -+ -+public class SyncmaticManager { -+ -+ public static final String PLACEMENTS_JSON_KEY = "placements"; -+ private final Map schematics = new HashMap<>(); -+ -+ public void addPlacement(final ServerPlacement placement) { -+ schematics.put(placement.getId(), placement); -+ updateServerPlacement(); -+ } -+ -+ public ServerPlacement getPlacement(final UUID id) { -+ return schematics.get(id); -+ } -+ -+ public Collection getAll() { -+ return schematics.values(); -+ } -+ -+ public void removePlacement(final @NotNull ServerPlacement placement) { -+ schematics.remove(placement.getId()); -+ updateServerPlacement(); -+ } -+ -+ public void updateServerPlacement() { -+ saveServer(); -+ } -+ -+ public void startup() { -+ loadServer(); -+ } -+ -+ private void saveServer() { -+ final JsonObject obj = new JsonObject(); -+ final JsonArray arr = new JsonArray(); -+ -+ for (final ServerPlacement p : getAll()) { -+ arr.add(p.toJson()); -+ } -+ -+ obj.add(PLACEMENTS_JSON_KEY, arr); -+ final File backup = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.bak"); -+ final File incoming = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.new"); -+ final File current = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json"); -+ -+ try (final FileWriter writer = new FileWriter(incoming)) { -+ writer.write(new GsonBuilder().setPrettyPrinting().create().toJson(obj)); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ return; -+ } -+ -+ SyncmaticaProtocol.backupAndReplace(backup.toPath(), current.toPath(), incoming.toPath()); -+ } -+ -+ private void loadServer() { -+ final File f = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json"); -+ if (f.exists() && f.isFile() && f.canRead()) { -+ JsonElement element = null; -+ try { -+ final JsonParser parser = new JsonParser(); -+ final FileReader reader = new FileReader(f); -+ -+ element = parser.parse(reader); -+ reader.close(); -+ -+ } catch (final Exception e) { -+ e.printStackTrace(); -+ } -+ if (element == null) { -+ return; -+ } -+ try { -+ final JsonObject obj = element.getAsJsonObject(); -+ if (obj == null || !obj.has(PLACEMENTS_JSON_KEY)) { -+ return; -+ } -+ final JsonArray arr = obj.getAsJsonArray(PLACEMENTS_JSON_KEY); -+ for (final JsonElement elem : arr) { -+ final ServerPlacement placement = ServerPlacement.fromJson(elem.getAsJsonObject()); -+ if (placement != null) { -+ schematics.put(placement.getId(), placement); -+ } -+ } -+ -+ } catch (final IllegalStateException | NullPointerException e) { -+ e.printStackTrace(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cb5dffe81c4f0becad0ae2fbf7e9143f4fa577ef ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaPayload.java -@@ -0,0 +1,27 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; -+ -+public record SyncmaticaPayload(ResourceLocation packetType, -+ FriendlyByteBuf data) implements LeavesCustomPayload { -+ -+ private static final ResourceLocation NETWORK_ID = new ResourceLocation(SyncmaticaProtocol.PROTOCOL_ID, "main"); -+ -+ @New -+ public static SyncmaticaPayload decode(ResourceLocation location, FriendlyByteBuf buf) { -+ return new SyncmaticaPayload(buf.readResourceLocation(), new FriendlyByteBuf(buf.readBytes(buf.readableBytes()))); -+ } -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ buf.writeResourceLocation(this.packetType); -+ buf.writeBytes(this.data.readBytes(this.data.readableBytes())); -+ } -+ -+ @Override -+ public ResourceLocation id() { -+ return NETWORK_ID; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ee092cea91cb50d0f9269d4dc22117f0fd0a60a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java -@@ -0,0 +1,126 @@ -+package org.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.File; -+import java.io.IOException; -+import java.io.InputStream; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.Arrays; -+import java.util.UUID; -+ -+public class SyncmaticaProtocol { -+ -+ public static final String PROTOCOL_ID = "syncmatica"; -+ public static final String PROTOCOL_VERSION = "leaves-syncmatica-1.1.0"; -+ -+ private static boolean loaded = false; -+ private static final File litematicFolder = new File("." + File.separator + "syncmatics"); -+ private static final PlayerIdentifierProvider playerIdentifierProvider = new PlayerIdentifierProvider(); -+ private static final CommunicationManager communicationManager = new CommunicationManager(); -+ private static final FeatureSet featureSet = new FeatureSet(Arrays.asList(Feature.values())); -+ private static final SyncmaticManager syncmaticManager = new SyncmaticManager(); -+ private static final FileStorage fileStorage = new FileStorage(); -+ -+ public static File getLitematicFolder() { -+ return litematicFolder; -+ } -+ -+ public static PlayerIdentifierProvider getPlayerIdentifierProvider() { -+ return playerIdentifierProvider; -+ } -+ -+ public static CommunicationManager getCommunicationManager() { -+ return communicationManager; -+ } -+ -+ public static FeatureSet getFeatureSet() { -+ return featureSet; -+ } -+ -+ public static SyncmaticManager getSyncmaticManager() { -+ return syncmaticManager; -+ } -+ -+ public static FileStorage getFileStorage() { -+ return fileStorage; -+ } -+ -+ public static void init() { -+ if (!loaded) { -+ litematicFolder.mkdirs(); -+ syncmaticManager.startup(); -+ loaded = true; -+ } -+ } -+ -+ @NotNull -+ public static UUID createChecksum(final @NotNull InputStream fis) throws NoSuchAlgorithmException, IOException { -+ final byte[] buffer = new byte[4096]; -+ final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); -+ int numRead; -+ -+ do { -+ numRead = fis.read(buffer); -+ if (numRead > 0) { -+ messageDigest.update(buffer, 0, numRead); -+ } -+ } while (numRead != -1); -+ -+ fis.close(); -+ return UUID.nameUUIDFromBytes(messageDigest.digest()); -+ } -+ -+ private static final int[] ILLEGAL_CHARS = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; -+ private static final String ILLEGAL_PATTERNS = "(^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$)|(^\\.\\.*$)"; -+ -+ @NotNull -+ public static String sanitizeFileName(final @NotNull String badFileName) { -+ final StringBuilder sanitized = new StringBuilder(); -+ final int len = badFileName.codePointCount(0, badFileName.length()); -+ -+ for (int i = 0; i < len; i++) { -+ final int c = badFileName.codePointAt(i); -+ if (Arrays.binarySearch(ILLEGAL_CHARS, c) < 0) { -+ sanitized.appendCodePoint(c); -+ if (sanitized.length() == 255) { -+ break; -+ } -+ } -+ } -+ -+ return sanitized.toString().replaceAll(ILLEGAL_PATTERNS, "_"); -+ } -+ -+ public static boolean isOverQuota(int sent) { -+ return org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaQuota && sent > org.dreeam.leaf.config.modules.network.ProtocolSupport.syncmaticaQuotaLimit; -+ } -+ -+ public static void backupAndReplace(final Path backup, final Path current, final Path incoming) { -+ if (!Files.exists(incoming)) { -+ return; -+ } -+ if (overwrite(backup, current, 2) && !overwrite(current, incoming, 4)) { -+ overwrite(current, backup, 8); -+ } -+ } -+ -+ private static boolean overwrite(final Path backup, final Path current, final int tries) { -+ if (!Files.exists(current)) { -+ return true; -+ } -+ try { -+ Files.deleteIfExists(backup); -+ Files.move(current, backup); -+ } catch (final IOException exception) { -+ if (tries <= 0) { -+ return false; -+ } -+ return overwrite(backup, current, tries - 1); -+ } -+ return true; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b06ffeacf699b78f34253a26018ccdf723d5d0ce ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java -@@ -0,0 +1,66 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager; -+import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.util.UUID; -+ -+public abstract class AbstractExchange implements Exchange { -+ -+ private boolean success = false; -+ private boolean finished = false; -+ private final ExchangeTarget partner; -+ -+ protected AbstractExchange(final ExchangeTarget partner) { -+ this.partner = partner; -+ } -+ -+ @Override -+ public ExchangeTarget getPartner() { -+ return partner; -+ } -+ -+ @Override -+ public boolean isFinished() { -+ return finished; -+ } -+ -+ @Override -+ public boolean isSuccessful() { -+ return success; -+ } -+ -+ @Override -+ public void close(final boolean notifyPartner) { -+ finished = true; -+ success = false; -+ onClose(); -+ if (notifyPartner) { -+ sendCancelPacket(); -+ } -+ } -+ -+ public CommunicationManager getManager() { -+ return SyncmaticaProtocol.getCommunicationManager(); -+ } -+ -+ protected void sendCancelPacket() { -+ } -+ -+ protected void onClose() { -+ } -+ -+ protected void succeed() { -+ finished = true; -+ success = true; -+ onClose(); -+ } -+ -+ protected static boolean checkUUID(final FriendlyByteBuf sourceBuf, final UUID targetId) { -+ final int r = sourceBuf.readerIndex(); -+ final UUID sourceId = sourceBuf.readUUID(); -+ sourceBuf.readerIndex(r); -+ return sourceId.equals(targetId); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b0463dc8dc6f204cd48b73056dc4eb321e7ba602 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java -@@ -0,0 +1,125 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.syncmatica.MessageType; -+import org.leavesmc.leaves.protocol.syncmatica.PacketType; -+import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.io.File; -+import java.io.FileOutputStream; -+import java.io.IOException; -+import java.io.OutputStream; -+import java.security.DigestOutputStream; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.UUID; -+ -+public class DownloadExchange extends AbstractExchange { -+ -+ private final ServerPlacement toDownload; -+ private final OutputStream outputStream; -+ private final MessageDigest md5; -+ private final File downloadFile; -+ private int bytesSent; -+ -+ public DownloadExchange(final ServerPlacement syncmatic, final File downloadFile, final ExchangeTarget partner) throws IOException, NoSuchAlgorithmException { -+ super(partner); -+ this.downloadFile = downloadFile; -+ final OutputStream os = new FileOutputStream(downloadFile); -+ toDownload = syncmatic; -+ md5 = MessageDigest.getInstance("MD5"); -+ outputStream = new DigestOutputStream(os, md5); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.SEND_LITEMATIC.identifier) -+ || id.equals(PacketType.FINISHED_LITEMATIC.identifier) -+ || id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ return checkUUID(packetBuf, toDownload.getId()); -+ } -+ return false; -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { -+ packetBuf.readUUID(); -+ if (id.equals(PacketType.SEND_LITEMATIC.identifier)) { -+ final int size = packetBuf.readInt(); -+ bytesSent += size; -+ if (SyncmaticaProtocol.isOverQuota(bytesSent)) { -+ close(true); -+ SyncmaticaProtocol.getCommunicationManager().sendMessage( -+ getPartner(), -+ MessageType.ERROR, -+ "syncmatica.error.cancelled_transmit_exceed_quota" -+ ); -+ } -+ try { -+ packetBuf.readBytes(outputStream, size); -+ } catch (final IOException e) { -+ close(true); -+ e.printStackTrace(); -+ return; -+ } -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toDownload.getId()); -+ getPartner().sendPacket(PacketType.RECEIVED_LITEMATIC.identifier, FriendlyByteBuf); -+ return; -+ } -+ if (id.equals(PacketType.FINISHED_LITEMATIC.identifier)) { -+ try { -+ outputStream.flush(); -+ } catch (final IOException e) { -+ close(false); -+ e.printStackTrace(); -+ return; -+ } -+ final UUID downloadHash = UUID.nameUUIDFromBytes(md5.digest()); -+ if (downloadHash.equals(toDownload.getHash())) { -+ succeed(); -+ } else { -+ close(false); -+ } -+ return; -+ } -+ if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ close(false); -+ } -+ } -+ -+ @Override -+ public void init() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toDownload.getId()); -+ getPartner().sendPacket(PacketType.REQUEST_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+ -+ @Override -+ protected void onClose() { -+ getManager().setDownloadState(toDownload, false); -+ try { -+ outputStream.close(); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ } -+ if (!isSuccessful() && downloadFile.exists()) { -+ downloadFile.delete(); -+ } -+ } -+ -+ @Override -+ protected void sendCancelPacket() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toDownload.getId()); -+ getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+ -+ public ServerPlacement getPlacement() { -+ return toDownload; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0f45ef7f4abcd7cff627e5a3df2a9fca8d6e7585 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java -@@ -0,0 +1,20 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+ -+public interface Exchange { -+ ExchangeTarget getPartner(); -+ -+ boolean checkPacket(ResourceLocation id, FriendlyByteBuf packetBuf); -+ -+ void handle(ResourceLocation id, FriendlyByteBuf packetBuf); -+ -+ boolean isFinished(); -+ -+ boolean isSuccessful(); -+ -+ void close(boolean notifyPartner); -+ -+ void init(); -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b0da7abf0748d4bdb3141c28f201906157a0eaad ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java -@@ -0,0 +1,39 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import org.leavesmc.leaves.protocol.core.ProtocolUtils; -+import org.leavesmc.leaves.protocol.syncmatica.FeatureSet; -+import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaPayload; -+ -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.List; -+ -+public class ExchangeTarget { -+ -+ private final List ongoingExchanges = new ArrayList<>(); -+ private final ServerGamePacketListenerImpl client; -+ private FeatureSet features; -+ -+ public ExchangeTarget(final ServerGamePacketListenerImpl client) { -+ this.client = client; -+ } -+ -+ public void sendPacket(final ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ ProtocolUtils.sendPayloadPacket(client.player, new SyncmaticaPayload(id, packetBuf)); -+ } -+ -+ public FeatureSet getFeatureSet() { -+ return features; -+ } -+ -+ public void setFeatureSet(final FeatureSet f) { -+ features = f; -+ } -+ -+ public Collection getExchanges() { -+ return ongoingExchanges; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..45fc01915b18a47bcdf7a7ce55161266e5cd1221 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java -@@ -0,0 +1,48 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.syncmatica.FeatureSet; -+import org.leavesmc.leaves.protocol.syncmatica.PacketType; -+import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+public abstract class FeatureExchange extends AbstractExchange { -+ -+ protected FeatureExchange(final ExchangeTarget partner) { -+ super(partner); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ return id.equals(PacketType.FEATURE_REQUEST.identifier) -+ || id.equals(PacketType.FEATURE.identifier); -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.FEATURE_REQUEST.identifier)) { -+ sendFeatures(); -+ } else if (id.equals(PacketType.FEATURE.identifier)) { -+ final FeatureSet fs = FeatureSet.fromString(packetBuf.readUtf(32767)); -+ getPartner().setFeatureSet(fs); -+ onFeatureSetReceive(); -+ } -+ } -+ -+ protected void onFeatureSetReceive() { -+ succeed(); -+ } -+ -+ public void requestFeatureSet() { -+ getPartner().sendPacket(PacketType.FEATURE_REQUEST.identifier, new FriendlyByteBuf(Unpooled.buffer())); -+ } -+ -+ private void sendFeatures() { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ final FeatureSet fs = SyncmaticaProtocol.getFeatureSet(); -+ buf.writeUtf(fs.toString(), 32767); -+ getPartner().sendPacket(PacketType.FEATURE.identifier, buf); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c691201f0af82c4ac19df27639b32637b7f46caf ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java -@@ -0,0 +1,81 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.syncmatica.PacketType; -+import org.leavesmc.leaves.protocol.syncmatica.PlayerIdentifier; -+import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.util.UUID; -+ -+public class ModifyExchangeServer extends AbstractExchange { -+ -+ private final ServerPlacement placement; -+ UUID placementId; -+ -+ public ModifyExchangeServer(final UUID placeId, final ExchangeTarget partner) { -+ super(partner); -+ placementId = placeId; -+ placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ return id.equals(PacketType.MODIFY_FINISH.identifier) && checkUUID(packetBuf, placement.getId()); -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { -+ packetBuf.readUUID(); -+ if (id.equals(PacketType.MODIFY_FINISH.identifier)) { -+ SyncmaticaProtocol.getCommunicationManager().receivePositionData(placement, packetBuf, getPartner()); -+ final PlayerIdentifier identifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet( -+ getPartner() -+ ); -+ placement.setLastModifiedBy(identifier); -+ SyncmaticaProtocol.getSyncmaticManager().updateServerPlacement(); -+ succeed(); -+ } -+ } -+ -+ @Override -+ public void init() { -+ if (getPlacement() == null || SyncmaticaProtocol.getCommunicationManager().getModifier(placement) != null) { -+ close(true); -+ } else { -+ if (SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(this.getPartner()).uuid.equals(placement.getOwner().uuid)) { -+ accept(); -+ } else { -+ close(true); -+ } -+ } -+ } -+ -+ private void accept() { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placement.getId()); -+ getPartner().sendPacket(PacketType.MODIFY_REQUEST_ACCEPT.identifier, buf); -+ SyncmaticaProtocol.getCommunicationManager().setModifier(placement, this); -+ } -+ -+ @Override -+ protected void sendCancelPacket() { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placementId); -+ getPartner().sendPacket(PacketType.MODIFY_REQUEST_DENY.identifier, buf); -+ } -+ -+ public ServerPlacement getPlacement() { -+ return placement; -+ } -+ -+ @Override -+ protected void onClose() { -+ if (SyncmaticaProtocol.getCommunicationManager().getModifier(placement) == this) { -+ SyncmaticaProtocol.getCommunicationManager().setModifier(placement, null); -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..065a39942603f7b884cca40d8d7b4b47b46d7985 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java -@@ -0,0 +1,101 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.syncmatica.PacketType; -+import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+ -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.FileNotFoundException; -+import java.io.IOException; -+import java.io.InputStream; -+ -+public class UploadExchange extends AbstractExchange { -+ -+ private static final int BUFFER_SIZE = 16384; -+ -+ private final ServerPlacement toUpload; -+ private final InputStream inputStream; -+ private final byte[] buffer = new byte[BUFFER_SIZE]; -+ -+ public UploadExchange(final ServerPlacement syncmatic, final File uploadFile, final ExchangeTarget partner) throws FileNotFoundException { -+ super(partner); -+ toUpload = syncmatic; -+ inputStream = new FileInputStream(uploadFile); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier) -+ || id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ return checkUUID(packetBuf, toUpload.getId()); -+ } -+ return false; -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { -+ packetBuf.readUUID(); -+ if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier)) { -+ send(); -+ } -+ if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ close(false); -+ } -+ } -+ -+ private void send() { -+ final int bytesRead; -+ try { -+ bytesRead = inputStream.read(buffer); -+ } catch (final IOException e) { -+ close(true); -+ e.printStackTrace(); -+ return; -+ } -+ if (bytesRead == -1) { -+ sendFinish(); -+ } else { -+ sendData(bytesRead); -+ } -+ } -+ -+ private void sendData(final int bytesRead) { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toUpload.getId()); -+ FriendlyByteBuf.writeInt(bytesRead); -+ FriendlyByteBuf.writeBytes(buffer, 0, bytesRead); -+ getPartner().sendPacket(PacketType.SEND_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+ -+ private void sendFinish() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toUpload.getId()); -+ getPartner().sendPacket(PacketType.FINISHED_LITEMATIC.identifier, FriendlyByteBuf); -+ succeed(); -+ } -+ -+ @Override -+ public void init() { -+ send(); -+ } -+ -+ @Override -+ protected void onClose() { -+ try { -+ inputStream.close(); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ } -+ } -+ -+ @Override -+ protected void sendCancelPacket() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toUpload.getId()); -+ getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cb7aed6c0aa31cd46c071da7b483c63e755a6bff ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java -@@ -0,0 +1,66 @@ -+package org.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.syncmatica.CommunicationManager; -+import org.leavesmc.leaves.protocol.syncmatica.FeatureSet; -+import org.leavesmc.leaves.protocol.syncmatica.PacketType; -+import org.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+import org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.util.Collection; -+ -+public class VersionHandshakeServer extends FeatureExchange { -+ -+ public VersionHandshakeServer(final ExchangeTarget partner) { -+ super(partner); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ return id.equals(PacketType.REGISTER_VERSION.identifier) -+ || super.checkPacket(id, packetBuf); -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.REGISTER_VERSION.identifier)) { -+ String partnerVersion = packetBuf.readUtf(); -+ if (partnerVersion.equals("0.0.1")) { -+ close(false); -+ return; -+ } -+ final FeatureSet fs = FeatureSet.fromVersionString(partnerVersion); -+ if (fs == null) { -+ requestFeatureSet(); -+ } else { -+ getPartner().setFeatureSet(fs); -+ onFeatureSetReceive(); -+ } -+ } else { -+ super.handle(id, packetBuf); -+ } -+ -+ } -+ -+ @Override -+ public void onFeatureSetReceive() { -+ final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ final Collection l = SyncmaticaProtocol.getSyncmaticManager().getAll(); -+ newBuf.writeInt(l.size()); -+ for (final ServerPlacement p : l) { -+ CommunicationManager.putMetaData(p, newBuf, getPartner()); -+ } -+ getPartner().sendPacket(PacketType.CONFIRM_USER.identifier, newBuf); -+ succeed(); -+ } -+ -+ @Override -+ public void init() { -+ final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ newBuf.writeUtf(SyncmaticaProtocol.PROTOCOL_VERSION); -+ getPartner().sendPacket(PacketType.REGISTER_VERSION.identifier, newBuf); -+ } -+} diff --git a/patches/server/0033-Chat-Image-protocol.patch b/patches/server/0033-Chat-Image-protocol.patch deleted file mode 100644 index 70c2312a..00000000 --- a/patches/server/0033-Chat-Image-protocol.patch +++ /dev/null @@ -1,198 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH -Date: Wed, 13 Mar 2024 18:11:10 +0800 -Subject: [PATCH] Chat Image protocol - -This patch is Powered by ChatImage (https://github.com/kitUIN/ChatImage) - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index db48291d191e1eefb7e497dda7430fc1644bb5ea..4c38648af0a6f258c1618da7c162c44795ae36d6 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -14,6 +14,7 @@ public class ProtocolSupport extends ConfigModules { - public static boolean jadeProtocol = false; - public static boolean appleskinProtocol = false; - public static int appleskinSyncTickInterval = 20; -+ public static boolean chatImageProtocol = false; - public static boolean xaeroMapProtocol = false; - public static int xaeroMapServerID = new Random().nextInt(); - public static boolean syncmaticaProtocol = false; -@@ -25,6 +26,7 @@ public class ProtocolSupport extends ConfigModules { - jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); - appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol); - appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); -+ chatImageProtocol = config.getBoolean(getBasePath() + ".chatimage-protocol", chatImageProtocol); - xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol); - xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID); - syncmaticaProtocol = config.getBoolean(getBasePath() + ".syncmatica-protocol", syncmaticaProtocol); -diff --git a/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd652437c0e999f0b523b69bca8f5803611ead6c ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java -@@ -0,0 +1,143 @@ -+package org.leavesmc.leaves.protocol; -+ -+import com.google.common.collect.Lists; -+import com.google.gson.Gson; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import org.dreeam.leaf.config.modules.network.ProtocolSupport; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.chatimage.ChatImageIndex; -+import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; -+import org.leavesmc.leaves.protocol.core.LeavesProtocol; -+import org.leavesmc.leaves.protocol.core.LeavesProtocolManager; -+import org.leavesmc.leaves.protocol.core.ProtocolHandler; -+ -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.UUID; -+ -+@LeavesProtocol(namespace = "chatimage") -+public class ChatImageProtocol { -+ -+ public static final String PROTOCOL_ID = "chatimage"; -+ private static final Map> SERVER_BLOCK_CACHE = new HashMap<>(); -+ private static final Map FILE_COUNT_MAP = new HashMap<>(); -+ private static final Map> USER_CACHE_MAP = new HashMap<>(); -+ public static int MAX_STRING = 532767; -+ private static final Gson gson = new Gson(); -+ -+ public record FileInfoChannelPacket( -+ String message) implements LeavesCustomPayload { -+ private static final ResourceLocation FILE_INFO = ChatImageProtocol.id("file_info"); -+ -+ @New -+ public FileInfoChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { -+ this(buffer.readUtf(MAX_STRING)); -+ } -+ -+ @Override -+ public void write(final FriendlyByteBuf buffer) { -+ buffer.writeUtf(message(), MAX_STRING); -+ } -+ -+ @Override -+ public @NotNull ResourceLocation id() { -+ return FILE_INFO; -+ } -+ } -+ -+ public record DownloadFileChannelPacket( -+ String message) implements LeavesCustomPayload { -+ private static final ResourceLocation DOWNLOAD_FILE_CHANNEL = ChatImageProtocol.id("download_file_channel"); -+ -+ @New -+ public DownloadFileChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { -+ this(buffer.readUtf(MAX_STRING)); -+ } -+ -+ @Override -+ public void write(final FriendlyByteBuf buffer) { -+ buffer.writeUtf(message(), MAX_STRING); -+ } -+ -+ @Override -+ public @NotNull ResourceLocation id() { -+ return DOWNLOAD_FILE_CHANNEL; -+ } -+ -+ } -+ -+ public record FileChannelPacket( -+ String message) implements LeavesCustomPayload { -+ private static final ResourceLocation FILE_CHANNEL = ChatImageProtocol.id("file_channel"); -+ -+ @New -+ public FileChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { -+ this(buffer.readUtf(MAX_STRING)); -+ } -+ -+ @Override -+ public void write(final FriendlyByteBuf buffer) { -+ buffer.writeUtf(message(), MAX_STRING); -+ } -+ -+ @Override -+ public @NotNull ResourceLocation id() { -+ return FILE_CHANNEL; -+ } -+ -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = FileChannelPacket.class, payloadId = "file_channel") -+ public static void serverFileChannelReceived(ServerPlayer player, String res) { -+ ChatImageIndex title = gson.fromJson(res, ChatImageIndex.class); -+ HashMap blocks = SERVER_BLOCK_CACHE.containsKey(title.url) ? SERVER_BLOCK_CACHE.get(title.url) : new HashMap<>(); -+ blocks.put(title.index, res); -+ SERVER_BLOCK_CACHE.put(title.url, blocks); -+ FILE_COUNT_MAP.put(title.url, title.total); -+ if (title.total == blocks.size()) { -+ if (USER_CACHE_MAP.containsKey(title.url)) { -+ List names = USER_CACHE_MAP.get(title.url); -+ for (String uuid : names) { -+ ServerPlayer serverPlayer = player.server.getPlayerList().getPlayer(UUID.fromString(uuid)); -+ if (serverPlayer != null) { -+ sendToPlayer(new FileInfoChannelPacket("true->" + title.url), serverPlayer); -+ } -+ } -+ USER_CACHE_MAP.put(title.url, Lists.newArrayList()); -+ } -+ } -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = FileInfoChannelPacket.class, payloadId = "file_info") -+ public static void serverFileInfoChannelReceived(ServerPlayer player, String url) { -+ if (SERVER_BLOCK_CACHE.containsKey(url) && FILE_COUNT_MAP.containsKey(url)) { -+ HashMap list = SERVER_BLOCK_CACHE.get(url); -+ Integer total = FILE_COUNT_MAP.get(url); -+ if (total == list.size()) { -+ for (Map.Entry entry : list.entrySet()) { -+ sendToPlayer(new DownloadFileChannelPacket(entry.getValue()), player); -+ } -+ return; -+ } -+ } -+ sendToPlayer(new FileInfoChannelPacket("null->" + url), player); -+ List names = USER_CACHE_MAP.containsKey(url) ? USER_CACHE_MAP.get(url) : Lists.newArrayList(); -+ names.add(player.getStringUUID()); -+ USER_CACHE_MAP.put(url, names); -+ } -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return ResourceLocation.fromNamespaceAndPath(PROTOCOL_ID, path); -+ } -+ -+ public static void sendToPlayer(CustomPacketPayload payload, ServerPlayer player) { -+ player.connection.send((Packet) payload); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java b/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e4b55c5b606fcb92bf035910e82d9f32606d1c2b ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java -@@ -0,0 +1,16 @@ -+package org.leavesmc.leaves.protocol.chatimage; -+ -+public class ChatImageIndex { -+ -+ public int index; -+ public int total; -+ public String url; -+ public String bytes; -+ -+ public ChatImageIndex(int index, int total, String url, String bytes) { -+ this.index = index; -+ this.total = total; -+ this.url = url; -+ this.bytes = bytes; -+ } -+} diff --git a/patches/server/0034-Asteor-Bar-protocol.patch b/patches/server/0034-Asteor-Bar-protocol.patch deleted file mode 100644 index 89cdffd7..00000000 --- a/patches/server/0034-Asteor-Bar-protocol.patch +++ /dev/null @@ -1,139 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH -Date: Fri, 22 Mar 2024 04:36:25 +0800 -Subject: [PATCH] Asteor Bar protocol - -This patch is Powered by AsteorBar (https://github.com/afoxxvi/AsteorBarMod) - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index 4c38648af0a6f258c1618da7c162c44795ae36d6..a81c0a7bc71b86eb652abcd06099d36f4de71ae9 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -14,6 +14,7 @@ public class ProtocolSupport extends ConfigModules { - public static boolean jadeProtocol = false; - public static boolean appleskinProtocol = false; - public static int appleskinSyncTickInterval = 20; -+ public static boolean asteorBarProtocol = false; - public static boolean chatImageProtocol = false; - public static boolean xaeroMapProtocol = false; - public static int xaeroMapServerID = new Random().nextInt(); -@@ -26,6 +27,7 @@ public class ProtocolSupport extends ConfigModules { - jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); - appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol); - appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); -+ asteorBarProtocol = config.getBoolean(getBasePath() + ".asteorbar-protocol", asteorBarProtocol); - chatImageProtocol = config.getBoolean(getBasePath() + ".chatimage-protocol", chatImageProtocol); - xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol); - xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID); -diff --git a/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e6f3a52c3b6a23d8a8f7c4ae7828efa5dd51523e ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java -@@ -0,0 +1,106 @@ -+package org.leavesmc.leaves.protocol; -+ -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.food.FoodData; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.core.LeavesProtocol; -+import org.leavesmc.leaves.protocol.core.ProtocolHandler; -+import org.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+import java.util.UUID; -+ -+@LeavesProtocol(namespace = "asteorbar") -+public class AsteorBarProtocol { -+ -+ public static final String PROTOCOL_ID = "asteorbar"; -+ -+ private static final ResourceLocation NETWORK_KEY = id("network"); -+ -+ private static final Map previousSaturationLevels = new HashMap<>(); -+ private static final Map previousExhaustionLevels = new HashMap<>(); -+ -+ private static final float THRESHOLD = 0.01F; -+ -+ private static final Set players = new HashSet<>(); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return ResourceLocation.fromNamespaceAndPath(PROTOCOL_ID, path); -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol) { -+ resetPlayerData(player); -+ } -+ } -+ -+ @ProtocolHandler.PlayerLeave -+ public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol) { -+ players.remove(player); -+ resetPlayerData(player); -+ } -+ } -+ -+ @ProtocolHandler.MinecraftRegister(ignoreId = true) -+ public static void onPlayerSubscribed(@NotNull ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol) { -+ players.add(player); -+ } -+ } -+ -+ @ProtocolHandler.Ticker -+ public static void tick() { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol) { -+ for (ServerPlayer player : players) { -+ FoodData data = player.getFoodData(); -+ -+ float saturation = data.getSaturationLevel(); -+ Float previousSaturation = previousSaturationLevels.get(player.getUUID()); -+ if (previousSaturation == null || saturation != previousSaturation) { -+ ProtocolUtils.sendPayloadPacket(player, NETWORK_KEY, buf -> { -+ buf.writeByte(1); -+ buf.writeFloat(saturation); -+ }); -+ previousSaturationLevels.put(player.getUUID(), saturation); -+ } -+ -+ float exhaustion = data.exhaustionLevel; -+ Float previousExhaustion = previousExhaustionLevels.get(player.getUUID()); -+ if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= THRESHOLD) { -+ ProtocolUtils.sendPayloadPacket(player, NETWORK_KEY, buf -> { -+ buf.writeByte(0); -+ buf.writeFloat(exhaustion); -+ }); -+ previousExhaustionLevels.put(player.getUUID(), exhaustion); -+ } -+ } -+ } -+ } -+ -+ @ProtocolHandler.ReloadServer -+ public static void onServerReload() { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.asteorBarProtocol) { -+ disableAllPlayer(); -+ } -+ } -+ -+ public static void disableAllPlayer() { -+ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { -+ onPlayerLoggedOut(player); -+ } -+ } -+ -+ private static void resetPlayerData(@NotNull ServerPlayer player) { -+ previousExhaustionLevels.remove(player.getUUID()); -+ previousSaturationLevels.remove(player.getUUID()); -+ } -+} diff --git a/patches/server/0035-Leaves-Replay-Mod-API.patch b/patches/server/0035-Leaves-Replay-Mod-API.patch deleted file mode 100644 index b80e5135..00000000 --- a/patches/server/0035-Leaves-Replay-Mod-API.patch +++ /dev/null @@ -1,1667 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Thu, 3 Aug 2023 20:36:38 +0800 -Subject: [PATCH] Leaves: Replay Mod API - -Co-authored-by: alazeprt - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -This patch is Powered by ReplayMod(https://github.com/ReplayMod) - -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index a1c9726d25479b5326fe2fa2b0f5a98d6b2da4c5..50c0dd5591de5dd4b1977c557d91cc3c339069cc 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -@@ -40,6 +40,11 @@ class PaperEventManager { - } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { - throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); - } -+ // Leaves start - skip photographer -+ if (event instanceof org.bukkit.event.player.PlayerEvent playerEvent && playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.Photographer) { -+ return; -+ } -+ // Leaves end - skip photographer - - HandlerList handlers = event.getHandlers(); - RegisteredListener[] listeners = handlers.getRegisteredListeners(); -diff --git a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java -index 716d37ea7c398c4f15f362c7759daca9d3fe32cb..2b586b222bdeddfb5a6a746fa4e60501fb73e847 100644 ---- a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java -+++ b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java -@@ -154,6 +154,7 @@ public class EntityArgument implements ArgumentType { - if (icompletionprovider instanceof CommandSourceStack commandSourceStack && commandSourceStack.getEntity() instanceof ServerPlayer sourcePlayer) { - collection = new java.util.ArrayList<>(); - for (final ServerPlayer player : commandSourceStack.getServer().getPlayerList().getPlayers()) { -+ if (player instanceof org.leavesmc.leaves.replay.ServerPhotographer) continue; // Leaves - skip photographer - if (sourcePlayer.getBukkitEntity().canSee(player.getBukkitEntity())) { - collection.add(player.getGameProfile().getName()); - } -diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -index b455c7e9d18bac3654daa8510f85cc21202e254b..1f8d52ee2ad0463b8cb1072190f836ea708d0d0d 100644 ---- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -@@ -117,6 +117,7 @@ public class EntitySelector { - return this.findPlayers(source); - } else if (this.playerName != null) { - ServerPlayer entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); -+ entityplayer = entityplayer instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : entityplayer; // Leaves - skip photographer - - return entityplayer == null ? List.of() : List.of(entityplayer); - } else if (this.entityUUID != null) { -@@ -126,7 +127,7 @@ public class EntitySelector { - ServerLevel worldserver = (ServerLevel) iterator.next(); - Entity entity = worldserver.getEntity(this.entityUUID); - -- if (entity != null) { -+ if (entity != null && !(entity instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // Leaves - skip photographer - if (entity.getType().isEnabled(source.enabledFeatures())) { - return List.of(entity); - } -@@ -142,7 +143,7 @@ public class EntitySelector { - - if (this.currentEntity) { - predicate = this.getPredicate(vec3d, axisalignedbb, (FeatureFlagSet) null); -- return source.getEntity() != null && predicate.test(source.getEntity()) ? List.of(source.getEntity()) : List.of(); -+ return source.getEntity() != null && !(source.getEntity() instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(source.getEntity()) ? List.of(source.getEntity()) : List.of(); // Leaves - skip photographer - } else { - predicate = this.getPredicate(vec3d, axisalignedbb, source.enabledFeatures()); - List list = new ObjectArrayList(); -@@ -158,6 +159,7 @@ public class EntitySelector { - this.addEntities(list, worldserver1, axisalignedbb, predicate); - } - } -+ list.removeIf(entity -> entity instanceof org.leavesmc.leaves.replay.ServerPhotographer); // Leaves - skip photographer - - return this.sortAndLimit(vec3d, list); - } -@@ -198,9 +200,11 @@ public class EntitySelector { - - if (this.playerName != null) { - entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); -+ entityplayer = entityplayer instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : entityplayer; // Leaves - skip photographer - return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur - } else if (this.entityUUID != null) { - entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); -+ entityplayer = entityplayer instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : entityplayer; // Leaves - skip photographer - return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur - } else { - Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); -@@ -213,7 +217,7 @@ public class EntitySelector { - if (entity instanceof ServerPlayer) { - ServerPlayer entityplayer1 = (ServerPlayer) entity; - -- if (predicate.test(entityplayer1)) { -+ if (predicate.test(entityplayer1) && !(entityplayer1 instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // Leaves - skip photographer - return !canSee(source, entityplayer1) ? List.of() : List.of(entityplayer1); // Purpur - } - } -@@ -224,7 +228,7 @@ public class EntitySelector { - Object object; - - if (this.isWorldLimited()) { -- object = source.getLevel().getPlayers(predicate, i); -+ object = source.getLevel().getPlayers((entityplayer3 -> !(entityplayer3 instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(entityplayer3)), i); // Leaves - skip photographer - ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur - } else { - object = new ObjectArrayList(); -@@ -233,7 +237,7 @@ public class EntitySelector { - while (iterator.hasNext()) { - ServerPlayer entityplayer2 = (ServerPlayer) iterator.next(); - -- if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur -+ if (predicate.test(entityplayer2) && canSee(source, entityplayer2) && !(entityplayer2 instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // Purpur // Leaves - skip photographer - ((List) object).add(entityplayer2); - if (((List) object).size() >= i) { - return (List) object; -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f52eb8f076a007167647e49db2734cd43f88b0b6..2d231c01fe752d3a97965a0bef01e7c0f6cb1611 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1771,7 +1771,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = this.playerList.getPlayers(); -+ List list = this.playerList.realPlayers; // Leaves - only real player - int i = this.getMaxPlayers(); - - if (this.hidesOnlinePlayers()) { -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 67f7c397328c8fbdbd30e1f3b94821a785ff1036..d92fa5893fa030cedf63cab9cc5f2b941af02290 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -222,6 +222,11 @@ public class PlayerAdvancements { - - public boolean award(AdvancementHolder advancement, String criterionName) { - boolean flag = false; -+ // Leaves start - photographer can't get advancement -+ if (player instanceof org.leavesmc.leaves.replay.ServerPhotographer) { -+ return false; -+ } -+ // Leaves end - photographer can't get advancement - AdvancementProgress advancementprogress = this.getOrStartProgress(advancement); - boolean flag1 = advancementprogress.isDone(); - -diff --git a/src/main/java/net/minecraft/server/commands/OpCommand.java b/src/main/java/net/minecraft/server/commands/OpCommand.java -index e7b444a10b244828827b3c66c53465206ea8e0ec..47c0f4eec739c41b81a546dac97569c84bbabb6e 100644 ---- a/src/main/java/net/minecraft/server/commands/OpCommand.java -+++ b/src/main/java/net/minecraft/server/commands/OpCommand.java -@@ -25,7 +25,7 @@ public class OpCommand { - (context, builder) -> { - PlayerList playerList = context.getSource().getServer().getPlayerList(); - return SharedSuggestionProvider.suggest( -- playerList.getPlayers() -+ playerList.realPlayers // Leaves - skip - .stream() - .filter(player -> !playerList.isOp(player.getGameProfile())) - .map(player -> player.getGameProfile().getName()), -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e7e5dfcf465a5497d8f5ff5a06b479c495f28ef0..c8c777bc19e21fbf82b3a7e68d0e9753be7d41d3 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -232,6 +232,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent - private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) - public boolean hasRidableMoveEvent = false; // Purpur -+ final List realPlayers; // Leaves - skip - - public LevelChunk getChunkIfLoaded(int x, int z) { - return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -695,6 +696,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // Paper end - rewrite chunk system - this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit - this.preciseTime = this.serverLevelData.getDayTime(); // Purpur -+ this.realPlayers = Lists.newArrayList(); // Leaves - skip - } - - // Paper start -@@ -2740,6 +2742,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true - if (entity instanceof ServerPlayer entityplayer) { - ServerLevel.this.players.add(entityplayer); -+ // Leaves start - skip -+ if (!(entityplayer instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { -+ ServerLevel.this.realPlayers.add(entityplayer); -+ } -+ // Leaves end - skip - ServerLevel.this.updateSleepingPlayerList(); - } - -@@ -2821,6 +2828,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - ServerLevel.this.getChunkSource().removeEntity(entity); - if (entity instanceof ServerPlayer entityplayer) { - ServerLevel.this.players.remove(entityplayer); -+ // Leaves start - skip -+ if (!(entityplayer instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { -+ ServerLevel.this.realPlayers.remove(entityplayer); -+ } -+ // Leaves end - skip - ServerLevel.this.updateSleepingPlayerList(); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 35a73243ce9a27af0c12ef7fb8b1a183f3efaaa5..df6f21a7cc2be3e006814896c4a80ad53f9b02f5 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -232,7 +232,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - private static final AttributeModifier CREATIVE_ENTITY_INTERACTION_RANGE_MODIFIER = new AttributeModifier(ResourceLocation.withDefaultNamespace("creative_mode_entity_range"), 2.0D, AttributeModifier.Operation.ADD_VALUE); - public ServerGamePacketListenerImpl connection; - public final MinecraftServer server; -- public final ServerPlayerGameMode gameMode; -+ public ServerPlayerGameMode gameMode; // Leaves - final -> null - private final PlayerAdvancements advancements; - private final ServerStatsCounter stats; - private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 243d5f479a11d9cbc867f034f9fa55ff7e27fb92..a4e8aaf7f35cb1277fc24c4cd240f224e4eedea3 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -156,6 +156,7 @@ public abstract class PlayerList { - private boolean allowCommandsForAllPlayers; - private static final boolean ALLOW_LOGOUTIVATOR = false; - private int sendAllPlayerInfoIn; -+ public final List realPlayers = new java.util.concurrent.CopyOnWriteArrayList(); // Leaves - replay api - - // CraftBukkit start - private CraftServer cserver; -@@ -182,6 +183,105 @@ public abstract class PlayerList { - } - abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor - -+ // Leaves start - replay api -+ public void placeNewPhotographer(Connection connection, org.leavesmc.leaves.replay.ServerPhotographer player, ServerLevel worldserver, Location location) { -+ player.isRealPlayer = true; // Paper -+ player.loginTime = System.currentTimeMillis(); // Paper -+ -+ ServerLevel worldserver1 = worldserver; -+ -+ player.setServerLevel(worldserver1); -+ player.spawnIn(worldserver1); -+ player.gameMode.setLevel((ServerLevel) player.level()); -+ -+ LevelData worlddata = worldserver1.getLevelData(); -+ -+ player.loadGameTypes(null); -+ ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, CommonListenerCookie.createInitial(player.gameProfile, false)); -+ GameRules gamerules = worldserver1.getGameRules(); -+ boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN); -+ boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); -+ boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); -+ -+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); // Paper - replace old player chunk management -+ player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit -+ playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -+ playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); -+ playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected)); -+ RecipeManager craftingmanager = this.server.getRecipeManager(); -+ playerconnection.send(new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes())); -+ this.sendPlayerPermissionLevel(player); -+ player.getStats().markAllDirty(); -+ player.getRecipeBook().sendInitialRecipeBook(player); -+ this.updateEntireScoreboard(worldserver1.getScoreboard(), player); -+ this.server.invalidateStatus(); -+ -+ playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); -+ ServerStatus serverping = this.server.getStatus(); -+ -+ if (serverping != null) { -+ player.sendServerStatus(serverping); -+ } -+ -+ this.players.add(player); -+ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot -+ this.playersByUUID.put(player.getUUID(), player); -+ -+ player.supressTrackerForLogin = true; -+ worldserver1.addNewPlayer(player); -+ this.server.getCustomBossEvents().onPlayerConnect(player); -+ CraftPlayer bukkitPlayer = player.getBukkitEntity(); -+ -+ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); -+ if (!player.connection.isAcceptingMessages()) { -+ return; -+ } -+ -+ // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol -+ -+ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); -+ for (int i = 0; i < this.players.size(); ++i) { -+ ServerPlayer entityplayer1 = this.players.get(i); -+ -+ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { -+ continue; -+ } -+ -+ onlinePlayers.add(entityplayer1); -+ } -+ if (!onlinePlayers.isEmpty()) { -+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); -+ } -+ -+ player.sentListPacket = true; -+ player.supressTrackerForLogin = false; -+ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); -+ -+ this.sendLevelInfo(player, worldserver1); -+ -+ if (player.level() == worldserver1 && !worldserver1.players().contains(player)) { -+ worldserver1.addNewPlayer(player); -+ this.server.getCustomBossEvents().onPlayerConnect(player); -+ } -+ -+ worldserver1 = player.serverLevel(); -+ Iterator iterator = player.getActiveEffects().iterator(); -+ while (iterator.hasNext()) { -+ MobEffectInstance mobeffect = iterator.next(); -+ playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect, false)); -+ } -+ -+ if (player.isDeadOrDying()) { -+ net.minecraft.core.Holder plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) -+ .getOrThrow(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, false) -+ ); -+ } -+ } -+ // Leaves end - replay api -+ - public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - player.isRealPlayer = true; // Paper - player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed -@@ -337,6 +437,7 @@ public abstract class PlayerList { - - // entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below - this.players.add(player); -+ this.realPlayers.add(player); // Leaves - replay api - this.addToSendAllPlayerInfoBuckets(player); // Gale - Purpur - spread out sending all player info - this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot - this.playersByUUID.put(player.getUUID(), player); -@@ -397,6 +498,12 @@ public abstract class PlayerList { - continue; - } - -+ // Leaves start - skip photographer -+ if (entityplayer1 instanceof org.leavesmc.leaves.replay.ServerPhotographer) { -+ continue; -+ } -+ // Leaves end - skip photographer -+ - onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join - } - // Paper start - Use single player info update packet on join -@@ -554,6 +661,43 @@ public abstract class PlayerList { - - } - -+ // Leaves start - replay mod api -+ public void removePhotographer(org.leavesmc.leaves.replay.ServerPhotographer entityplayer) { -+ ServerLevel worldserver = entityplayer.serverLevel(); -+ -+ entityplayer.awardStat(Stats.LEAVE_GAME); -+ -+ if (entityplayer.containerMenu != entityplayer.inventoryMenu) { -+ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); -+ } -+ -+ if (server.isSameThread()) entityplayer.doTick(); -+ -+ if (this.collideRuleTeamName != null) { -+ final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); -+ final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName); -+ if (entityplayer.getTeam() == team && team != null) { -+ scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team); -+ } -+ } -+ -+ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); -+ entityplayer.retireScheduler(); -+ entityplayer.getAdvancements().stopListening(); -+ this.players.remove(entityplayer); -+ this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); -+ this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); -+ UUID uuid = entityplayer.getUUID(); -+ ServerPlayer entityplayer1 = this.playersByUUID.get(uuid); -+ -+ if (entityplayer1 == entityplayer) { -+ this.playersByUUID.remove(uuid); -+ } -+ -+ this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); -+ } -+ // Leaves stop - replay mod api -+ - public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component - // Paper start - Fix kick event leave message not being sent - return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); -@@ -637,6 +781,7 @@ public abstract class PlayerList { - entityplayer.retireScheduler(); // Paper - Folia schedulers - entityplayer.getAdvancements().stopListening(); - this.players.remove(entityplayer); -+ this.realPlayers.remove(entityplayer); // Leaves - replay api - this.removeFromSendAllPlayerInfoBuckets(entityplayer); // Gale - Purpur - spread out sending all player info - this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot - this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); -@@ -732,7 +877,7 @@ public abstract class PlayerList { - event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure - } else { - // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; -- if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur -+ if (this.realPlayers.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur // Leaves - only real player - event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 073ad95dc56f3b8d35913058a8eddcd610997124..b8a288da456c9cac7fd038f50c68548a0bb7b222 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -320,6 +320,8 @@ public final class CraftServer implements Server { - private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); - private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); - -+ private final org.leavesmc.leaves.entity.CraftPhotographerManager photographerManager = new org.leavesmc.leaves.entity.CraftPhotographerManager(); // Leaves -+ - @Override - public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { - return this.regionizedScheduler; -@@ -408,7 +410,7 @@ public final class CraftServer implements Server { - public CraftServer(DedicatedServer console, PlayerList playerList) { - this.console = console; - this.playerList = (DedicatedPlayerList) playerList; -- this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function() { -+ this.playerView = Collections.unmodifiableList(Lists.transform(playerList.realPlayers, new Function() { // Leaves - replay api - @Override - public CraftPlayer apply(ServerPlayer player) { - return player.getBukkitEntity(); -@@ -3431,4 +3433,11 @@ public final class CraftServer implements Server { - return getServer().lagging; - } - // Purpur end -+ -+ // Leaves start - replay mod api -+ @Override -+ public org.leavesmc.leaves.entity.CraftPhotographerManager getPhotographerManager() { -+ return photographerManager; -+ } -+ // Leaves end - replay mod api - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 51aee9a468f4ebfa9672fd9ce84883cf080859e3..80ff6427fd944280c7cc28c287332ed6dfccafdb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -112,6 +112,8 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return new CraftHumanEntity(server, (net.minecraft.world.entity.player.Player) entity); - } - -+ if (entity instanceof org.leavesmc.leaves.replay.ServerPhotographer photographer) { return new org.leavesmc.leaves.entity.CraftPhotographer(server, photographer); } // Leaves - replay mod api -+ - // Special case complex part, since there is no extra entity type for them - if (entity instanceof EnderDragonPart complexPart) { - if (complexPart.parentMob instanceof EnderDragon) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 414e30430eb7bcb935ef2cc038fcb7c27747bdd4..4129d894d6604f3b2495a35ad2d026c462f68567 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2285,7 +2285,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public boolean canSee(Player player) { -- return this.canSee((org.bukkit.entity.Entity) player); -+ return !(player instanceof org.leavesmc.leaves.entity.Photographer) && this.canSee((org.bukkit.entity.Entity) player); // Leaves - skip photographer - } - - @Override -diff --git a/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java b/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5bd34353b6ea86cd15ff48b8d6570167f35d75f0 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java -@@ -0,0 +1,38 @@ -+package org.leavesmc.leaves.bot; -+ -+import com.mojang.datafixers.DataFixer; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.stats.ServerStatsCounter; -+import net.minecraft.stats.Stat; -+import net.minecraft.world.entity.player.Player; -+ -+import java.io.File; -+ -+public class BotStatsCounter extends ServerStatsCounter { -+ -+ private static final File UNKOWN_FILE = new File("BOT_STATS_REMOVE_THIS"); -+ -+ public BotStatsCounter(MinecraftServer server) { -+ super(server, UNKOWN_FILE); -+ } -+ -+ @Override -+ public void save() { -+ -+ } -+ -+ @Override -+ public void setValue(Player player, Stat stat, int value) { -+ -+ } -+ -+ @Override -+ public void parseLocal(DataFixer dataFixer, String json) { -+ -+ } -+ -+ @Override -+ public int getValue(Stat stat) { -+ return 0; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java b/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fed2005cb711d0d15d5c87e5f0f7939c7a6a8ffa ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/entity/CraftPhotographer.java -@@ -0,0 +1,73 @@ -+package org.leavesmc.leaves.entity; -+ -+import net.minecraft.server.level.ServerPlayer; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.replay.ServerPhotographer; -+ -+import java.io.File; -+ -+public class CraftPhotographer extends CraftPlayer implements Photographer { -+ -+ public CraftPhotographer(CraftServer server, ServerPhotographer entity) { -+ super(server, entity); -+ } -+ -+ @Override -+ public void stopRecording() { -+ this.stopRecording(true); -+ } -+ -+ @Override -+ public void stopRecording(boolean async) { -+ this.stopRecording(async, true); -+ } -+ -+ @Override -+ public void stopRecording(boolean async, boolean save) { -+ this.getHandle().remove(async, save); -+ } -+ -+ @Override -+ public void pauseRecording() { -+ this.getHandle().pauseRecording(); -+ } -+ -+ @Override -+ public void resumeRecording() { -+ this.getHandle().resumeRecording(); -+ } -+ -+ @Override -+ public void setRecordFile(@NotNull File file) { -+ this.getHandle().setSaveFile(file); -+ } -+ -+ @Override -+ public void setFollowPlayer(@Nullable Player player) { -+ ServerPlayer serverPlayer = player != null ? ((CraftPlayer) player).getHandle() : null; -+ this.getHandle().setFollowPlayer(serverPlayer); -+ } -+ -+ @Override -+ public @NotNull String getId() { -+ return this.getHandle().createState.id; -+ } -+ -+ @Override -+ public ServerPhotographer getHandle() { -+ return (ServerPhotographer) entity; -+ } -+ -+ public void setHandle(final ServerPhotographer entity) { -+ super.setHandle(entity); -+ } -+ -+ @Override -+ public String toString() { -+ return "CraftPhotographer{" + "name=" + getName() + '}'; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java b/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e87d1e72902207dbcea67e8300c7375aa9161269 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/entity/CraftPhotographerManager.java -@@ -0,0 +1,82 @@ -+package org.leavesmc.leaves.entity; -+ -+import com.google.common.collect.Lists; -+import org.bukkit.Location; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.replay.BukkitRecorderOption; -+import org.leavesmc.leaves.replay.RecorderOption; -+import org.leavesmc.leaves.replay.ServerPhotographer; -+ -+import java.util.Collection; -+import java.util.Collections; -+import java.util.UUID; -+ -+public class CraftPhotographerManager implements PhotographerManager { -+ -+ private final Collection photographerViews = Collections.unmodifiableList(Lists.transform(ServerPhotographer.getPhotographers(), ServerPhotographer::getBukkitPlayer)); -+ -+ @Override -+ public @Nullable Photographer getPhotographer(@NotNull UUID uuid) { -+ ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid); -+ if (photographer != null) { -+ return photographer.getBukkitPlayer(); -+ } -+ return null; -+ } -+ -+ @Override -+ public @Nullable Photographer getPhotographer(@NotNull String id) { -+ ServerPhotographer photographer = ServerPhotographer.getPhotographer(id); -+ if (photographer != null) { -+ return photographer.getBukkitPlayer(); -+ } -+ return null; -+ } -+ -+ @Override -+ public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location) { -+ ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createDefaultOption()).createSync(); -+ if (photographer != null) { -+ return photographer.getBukkitPlayer(); -+ } -+ return null; -+ } -+ -+ @Override -+ public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location, @NotNull BukkitRecorderOption recorderOption) { -+ ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createFromBukkit(recorderOption)).createSync(); -+ if (photographer != null) { -+ return photographer.getBukkitPlayer(); -+ } -+ return null; -+ } -+ -+ @Override -+ public void removePhotographer(@NotNull String id) { -+ ServerPhotographer photographer = ServerPhotographer.getPhotographer(id); -+ if (photographer != null) { -+ photographer.remove(true); -+ } -+ } -+ -+ @Override -+ public void removePhotographer(@NotNull UUID uuid) { -+ ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid); -+ if (photographer != null) { -+ photographer.remove(true); -+ } -+ } -+ -+ @Override -+ public void removeAllPhotographers() { -+ for (ServerPhotographer photographer : ServerPhotographer.getPhotographers()) { -+ photographer.remove(true); -+ } -+ } -+ -+ @Override -+ public Collection getPhotographers() { -+ return photographerViews; -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java b/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e67ff063b7f50b4bfdaaaeb88f225eb768d89623 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/DigestOutputStream.java -@@ -0,0 +1,46 @@ -+package org.leavesmc.leaves.replay; -+ -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.IOException; -+import java.io.OutputStream; -+import java.util.zip.Checksum; -+ -+public class DigestOutputStream extends OutputStream { -+ -+ private final Checksum sum; -+ private final OutputStream out; -+ -+ public DigestOutputStream(OutputStream out, Checksum sum) { -+ this.out = out; -+ this.sum = sum; -+ } -+ -+ @Override -+ public void close() throws IOException { -+ out.close(); -+ } -+ -+ @Override -+ public void flush() throws IOException { -+ out.flush(); -+ } -+ -+ @Override -+ public void write(int b) throws IOException { -+ sum.update(b); -+ out.write(b); -+ } -+ -+ @Override -+ public void write(byte @NotNull [] b) throws IOException { -+ sum.update(b); -+ out.write(b); -+ } -+ -+ @Override -+ public void write(byte @NotNull [] b, int off, int len) throws IOException { -+ sum.update(b, off, len); -+ out.write(b, off, len); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java b/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5a3ea3e1e8df362262e1beaac167d667bd10adfa ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/RecordMetaData.java -@@ -0,0 +1,23 @@ -+package org.leavesmc.leaves.replay; -+ -+import java.util.HashSet; -+import java.util.Set; -+import java.util.UUID; -+ -+public class RecordMetaData { -+ -+ public static final int CURRENT_FILE_FORMAT_VERSION = 14; -+ -+ public boolean singleplayer = false; -+ public String serverName = "Leaf"; -+ public int duration = 0; -+ public long date; -+ public String mcversion; -+ public String fileFormat = "MCPR"; -+ public int fileFormatVersion; -+ public int protocol; -+ public String generator; -+ public int selfId = -1; -+ -+ public Set players = new HashSet<>(); -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/Recorder.java b/src/main/java/org/leavesmc/leaves/replay/Recorder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d1fb2f08f2d357c6551de7832eb3cf6980d44fb5 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/Recorder.java -@@ -0,0 +1,285 @@ -+package org.leavesmc.leaves.replay; -+ -+import com.mojang.serialization.DynamicOps; -+import io.netty.channel.local.LocalChannel; -+import net.minecraft.SharedConstants; -+import net.minecraft.core.LayeredRegistryAccess; -+import net.minecraft.core.RegistrySynchronization; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.Tag; -+import net.minecraft.network.Connection; -+import net.minecraft.network.ConnectionProtocol; -+import net.minecraft.network.PacketSendListener; -+import net.minecraft.network.protocol.BundlePacket; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.PacketFlow; -+import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -+import net.minecraft.network.protocol.common.ClientboundDisconnectPacket; -+import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket; -+import net.minecraft.network.protocol.common.ClientboundServerLinksPacket; -+import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; -+import net.minecraft.network.protocol.common.custom.BrandPayload; -+import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket; -+import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket; -+import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks; -+import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket; -+import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; -+import net.minecraft.network.protocol.game.ClientboundGameEventPacket; -+import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; -+import net.minecraft.network.protocol.game.ClientboundSetTimePacket; -+import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; -+import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.RegistryLayer; -+import net.minecraft.server.packs.repository.KnownPack; -+import net.minecraft.tags.TagNetworkSerialization; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.flag.FeatureFlags; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.leavesmc.leaves.LeavesLogger; -+ -+import java.io.File; -+import java.io.IOException; -+import java.util.List; -+import java.util.Optional; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.CompletionException; -+import java.util.concurrent.ExecutorService; -+import java.util.concurrent.Executors; -+import java.util.concurrent.TimeUnit; -+ -+public class Recorder extends Connection { -+ -+ private static final LeavesLogger LOGGER = LeavesLogger.LOGGER; -+ -+ private final ReplayFile replayFile; -+ private final ServerPhotographer photographer; -+ private final RecorderOption recorderOption; -+ private final RecordMetaData metaData; -+ -+ private final ExecutorService saveService = Executors.newSingleThreadExecutor(); -+ -+ private boolean stopped = false; -+ private boolean paused = false; -+ private boolean resumeOnNextPacket = true; -+ -+ private long startTime; -+ private long lastPacket; -+ private long timeShift = 0; -+ -+ private boolean isSaved; -+ private boolean isSaving; -+ private ConnectionProtocol state = ConnectionProtocol.LOGIN; -+ -+ public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException { -+ super(PacketFlow.CLIENTBOUND); -+ -+ this.photographer = photographer; -+ this.recorderOption = recorderOption; -+ this.metaData = new RecordMetaData(); -+ this.replayFile = new ReplayFile(replayFile); -+ this.channel = new LocalChannel(); -+ } -+ -+ public void start() { -+ startTime = System.currentTimeMillis(); -+ -+ metaData.singleplayer = false; -+ metaData.serverName = recorderOption.serverName; -+ metaData.date = startTime; -+ metaData.mcversion = SharedConstants.getCurrentVersion().getName(); -+ -+ // TODO start event -+ this.savePacket(new ClientboundLoginFinishedPacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN); -+ this.startConfiguration(); -+ -+ if (recorderOption.forceWeather != null) { -+ setWeather(recorderOption.forceWeather); -+ } -+ } -+ -+ public void startConfiguration() { -+ this.state = ConnectionProtocol.CONFIGURATION; -+ MinecraftServer server = MinecraftServer.getServer(); -+ -+ this.savePacket(new ClientboundCustomPayloadPacket(new BrandPayload(server.getServerModName())), ConnectionProtocol.CONFIGURATION); -+ this.savePacket(new ClientboundServerLinksPacket(server.serverLinks().untrust()), ConnectionProtocol.CONFIGURATION); -+ this.savePacket(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(server.getWorldData().enabledFeatures())), ConnectionProtocol.CONFIGURATION); -+ -+ List knownPackslist = server.getResourceManager().listPacks().flatMap((iresourcepack) -> iresourcepack.location().knownPackInfo().stream()).toList(); -+ this.savePacket(new ClientboundSelectKnownPacks(knownPackslist), ConnectionProtocol.CONFIGURATION); -+ -+ server.getServerResourcePack().ifPresent((info) -> this.savePacket(new ClientboundResourcePackPushPacket( -+ info.id(), info.url(), info.hash(), info.isRequired(), Optional.ofNullable(info.prompt()) -+ ))); -+ -+ LayeredRegistryAccess layeredregistryaccess = server.registries(); -+ DynamicOps dynamicOps = layeredregistryaccess.compositeAccess().createSerializationContext(NbtOps.INSTANCE); -+ RegistrySynchronization.packRegistries(dynamicOps, layeredregistryaccess.getAccessFrom(RegistryLayer.WORLDGEN), Set.copyOf(knownPackslist), -+ (key, entries) -> -+ this.savePacket(new ClientboundRegistryDataPacket(key, entries), ConnectionProtocol.CONFIGURATION) -+ ); -+ this.savePacket(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(layeredregistryaccess)), ConnectionProtocol.CONFIGURATION); -+ -+ this.savePacket(ClientboundFinishConfigurationPacket.INSTANCE, ConnectionProtocol.CONFIGURATION); -+ state = ConnectionProtocol.PLAY; -+ } -+ -+ @Override -+ public void flushChannel() { -+ } -+ -+ public void stop() { -+ stopped = true; -+ } -+ -+ public void pauseRecording() { -+ resumeOnNextPacket = false; -+ paused = true; -+ } -+ -+ public void resumeRecording() { -+ resumeOnNextPacket = true; -+ } -+ -+ public void setWeather(RecorderOption.RecordWeather weather) { -+ weather.getPackets().forEach(this::savePacket); -+ } -+ -+ public long getRecordedTime() { -+ final long base = System.currentTimeMillis() - startTime; -+ return base - timeShift; -+ } -+ -+ private synchronized long getCurrentTimeAndUpdate() { -+ long now = getRecordedTime(); -+ if (paused) { -+ if (resumeOnNextPacket) { -+ paused = false; -+ } -+ timeShift += now - lastPacket; -+ return lastPacket; -+ } -+ return lastPacket = now; -+ } -+ -+ @Override -+ public boolean isConnected() { -+ return true; -+ } -+ -+ @Override -+ public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { -+ if (!stopped) { -+ if (packet instanceof BundlePacket packet1) { -+ packet1.subPackets().forEach(subPacket -> send(subPacket, null)); -+ return; -+ } -+ -+ if (packet instanceof ClientboundAddEntityPacket packet1) { -+ if (packet1.getType() == EntityType.PLAYER) { -+ metaData.players.add(packet1.getUUID()); -+ saveMetadata(); -+ } -+ } -+ -+ if (packet instanceof ClientboundDisconnectPacket) { -+ return; -+ } -+ -+ if (recorderOption.forceDayTime != -1 && packet instanceof ClientboundSetTimePacket packet1) { -+ packet = new ClientboundSetTimePacket(packet1.dayTime(), recorderOption.forceDayTime, false); -+ } -+ -+ if (recorderOption.forceWeather != null && packet instanceof ClientboundGameEventPacket packet1) { -+ ClientboundGameEventPacket.Type type = packet1.getEvent(); -+ if (type == ClientboundGameEventPacket.START_RAINING || type == ClientboundGameEventPacket.STOP_RAINING || type == ClientboundGameEventPacket.RAIN_LEVEL_CHANGE || type == ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE) { -+ return; -+ } -+ } -+ -+ if (recorderOption.ignoreChat && (packet instanceof ClientboundSystemChatPacket || packet instanceof ClientboundPlayerChatPacket)) { -+ return; -+ } -+ -+ savePacket(packet); -+ } -+ } -+ -+ private void saveMetadata() { -+ saveService.submit(() -> { -+ try { -+ replayFile.saveMetaData(metaData); -+ } catch (IOException e) { -+ e.printStackTrace(); -+ } -+ }); -+ } -+ -+ private void savePacket(Packet packet) { -+ this.savePacket(packet, state); -+ } -+ -+ private void savePacket(Packet packet, final ConnectionProtocol protocol) { -+ try { -+ final long timestamp = getCurrentTimeAndUpdate(); -+ saveService.submit(() -> { -+ try { -+ replayFile.savePacket(timestamp, packet, protocol); -+ } catch (Exception e) { -+ LOGGER.severe("Error saving packet"); -+ e.printStackTrace(); -+ } -+ }); -+ } catch (Exception e) { -+ LOGGER.severe("Error saving packet"); -+ e.printStackTrace(); -+ } -+ } -+ -+ public boolean isSaved() { -+ return isSaved; -+ } -+ -+ public CompletableFuture saveRecording(File dest, boolean save) { -+ isSaved = true; -+ if (!isSaving) { -+ isSaving = true; -+ metaData.duration = (int) lastPacket; -+ return CompletableFuture.runAsync(() -> { -+ saveMetadata(); -+ saveService.shutdown(); -+ boolean interrupted = false; -+ try { -+ saveService.awaitTermination(10, TimeUnit.SECONDS); -+ } catch (InterruptedException e) { -+ interrupted = true; -+ } -+ try { -+ if (save) { -+ replayFile.closeAndSave(dest); -+ } else { -+ replayFile.closeNotSave(); -+ } -+ } catch (IOException e) { -+ e.printStackTrace(); -+ throw new CompletionException(e); -+ } finally { -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ } -+ }, runnable -> { -+ final Thread thread = new Thread(runnable, "Recording file save thread"); -+ thread.start(); -+ }); -+ } else { -+ LOGGER.warning("saveRecording() called twice"); -+ return CompletableFuture.supplyAsync(() -> { -+ throw new IllegalStateException("saveRecording() called twice"); -+ }); -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java b/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e1c32a60fa60054b351b0f4267d2e3e20c129e9b ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/RecorderOption.java -@@ -0,0 +1,57 @@ -+package org.leavesmc.leaves.replay; -+ -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.game.ClientboundGameEventPacket; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.List; -+ -+public class RecorderOption { -+ -+ public int recordDistance = -1; -+ public String serverName = "Leaf"; -+ public RecordWeather forceWeather = null; -+ public int forceDayTime = -1; -+ public boolean ignoreChat = false; -+ public boolean ignoreItem = false; -+ -+ @NotNull -+ @Contract(" -> new") -+ public static RecorderOption createDefaultOption() { -+ return new RecorderOption(); -+ } -+ -+ @NotNull -+ public static RecorderOption createFromBukkit(@NotNull BukkitRecorderOption bukkitRecorderOption) { -+ RecorderOption recorderOption = new RecorderOption(); -+ // recorderOption.recordDistance = bukkitRecorderOption.recordDistance; -+ // recorderOption.ignoreItem = bukkitRecorderOption.ignoreItem; -+ recorderOption.serverName = bukkitRecorderOption.serverName; -+ recorderOption.ignoreChat = bukkitRecorderOption.ignoreChat; -+ recorderOption.forceDayTime = bukkitRecorderOption.forceDayTime; -+ recorderOption.forceWeather = switch (bukkitRecorderOption.forceWeather) { -+ case RAIN -> RecordWeather.RAIN; -+ case CLEAR -> RecordWeather.CLEAR; -+ case THUNDER -> RecordWeather.THUNDER; -+ case NULL -> null; -+ }; -+ return recorderOption; -+ } -+ -+ public enum RecordWeather { -+ CLEAR(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)), -+ RAIN(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)), -+ THUNDER(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 1)); -+ -+ private final List> packets; -+ -+ private RecordWeather(Packet... packets) { -+ this.packets = List.of(packets); -+ } -+ -+ public List> getPackets() { -+ return packets; -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java b/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c6bb5431c3fe14935b0f0a871308830e476271d8 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/ReplayFile.java -@@ -0,0 +1,198 @@ -+package org.leavesmc.leaves.replay; -+ -+import com.google.gson.Gson; -+import com.google.gson.GsonBuilder; -+import io.netty.buffer.ByteBuf; -+import io.netty.buffer.Unpooled; -+import net.minecraft.SharedConstants; -+import net.minecraft.network.ConnectionProtocol; -+import net.minecraft.network.ProtocolInfo; -+import net.minecraft.network.RegistryFriendlyByteBuf; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.configuration.ConfigurationProtocols; -+import net.minecraft.network.protocol.game.GameProtocols; -+import net.minecraft.network.protocol.login.LoginProtocols; -+import net.minecraft.network.protocol.status.StatusProtocols; -+import net.minecraft.server.MinecraftServer; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.protocol.core.ProtocolUtils; -+import org.leavesmc.leaves.util.UUIDSerializer; -+ -+import java.io.BufferedOutputStream; -+import java.io.DataOutputStream; -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.FileOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.OutputStream; -+import java.io.OutputStreamWriter; -+import java.io.Writer; -+import java.nio.charset.StandardCharsets; -+import java.nio.file.Files; -+import java.util.List; -+import java.util.Map; -+import java.util.UUID; -+import java.util.zip.CRC32; -+import java.util.zip.ZipEntry; -+import java.util.zip.ZipOutputStream; -+ -+public class ReplayFile { -+ -+ private static final String RECORDING_FILE = "recording.tmcpr"; -+ private static final String RECORDING_FILE_CRC32 = "recording.tmcpr.crc32"; -+ private static final String MARKER_FILE = "markers.json"; -+ private static final String META_FILE = "metaData.json"; -+ -+ private static final Gson MARKER_GSON = new GsonBuilder().registerTypeAdapter(ReplayMarker.class, new ReplayMarker.Serializer()).create(); -+ private static final Gson META_GSON = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDSerializer()).create(); -+ -+ private final File tmpDir; -+ private final DataOutputStream packetStream; -+ private final CRC32 crc32 = new CRC32(); -+ -+ private final File markerFile; -+ private final File metaFile; -+ -+ private final Map> protocols; -+ -+ public ReplayFile(@NotNull File name) throws IOException { -+ this.tmpDir = new File(name.getParentFile(), name.getName() + ".tmp"); -+ if (tmpDir.exists()) { -+ if (!ReplayFile.deleteDir(tmpDir)) { -+ throw new IOException("Recording file " + name + " already exists!"); -+ } -+ } -+ -+ if (!tmpDir.mkdirs()) { -+ throw new IOException("Failed to create temp directory for recording " + tmpDir); -+ } -+ -+ File packetFile = new File(tmpDir, RECORDING_FILE); -+ this.metaFile = new File(tmpDir, META_FILE); -+ this.markerFile = new File(tmpDir, MARKER_FILE); -+ -+ this.packetStream = new DataOutputStream(new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(packetFile)), crc32)); -+ -+ this.protocols = Map.of( -+ ConnectionProtocol.STATUS, StatusProtocols.CLIENTBOUND, -+ ConnectionProtocol.LOGIN, LoginProtocols.CLIENTBOUND, -+ ConnectionProtocol.CONFIGURATION, ConfigurationProtocols.CLIENTBOUND, -+ ConnectionProtocol.PLAY, GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess())) -+ ); -+ } -+ -+ @SuppressWarnings({"rawtypes", "unchecked"}) -+ private byte @NotNull [] getPacketBytes(Packet packet, ConnectionProtocol state) { -+ ProtocolInfo protocol = this.protocols.get(state); -+ if (protocol == null) { -+ throw new IllegalArgumentException("Unknown protocol state " + state); -+ } -+ -+ ByteBuf buf = Unpooled.buffer(); -+ protocol.codec().encode(buf, packet); -+ -+ buf.readerIndex(0); -+ byte[] ret = new byte[buf.readableBytes()]; -+ buf.readBytes(ret); -+ buf.release(); -+ return ret; -+ } -+ -+ public void saveMarkers(List markers) throws IOException { -+ try (Writer writer = new OutputStreamWriter(new FileOutputStream(markerFile), StandardCharsets.UTF_8)) { -+ writer.write(MARKER_GSON.toJson(markers)); -+ } -+ } -+ -+ public void saveMetaData(@NotNull RecordMetaData data) throws IOException { -+ data.fileFormat = "MCPR"; -+ data.fileFormatVersion = RecordMetaData.CURRENT_FILE_FORMAT_VERSION; -+ data.protocol = SharedConstants.getCurrentVersion().getProtocolVersion(); -+ data.generator = ProtocolUtils.buildProtocolVersion("replay"); -+ -+ try (Writer writer = new OutputStreamWriter(new FileOutputStream(metaFile), StandardCharsets.UTF_8)) { -+ writer.write(META_GSON.toJson(data)); -+ } -+ } -+ -+ public void savePacket(long timestamp, Packet packet, ConnectionProtocol protocol) throws Exception { -+ byte[] data = getPacketBytes(packet, protocol); -+ packetStream.writeInt((int) timestamp); -+ packetStream.writeInt(data.length); -+ packetStream.write(data); -+ } -+ -+ public synchronized void closeAndSave(File file) throws IOException { -+ packetStream.close(); -+ -+ String[] files = tmpDir.list(); -+ if (files == null) { -+ return; -+ } -+ -+ try (ZipOutputStream os = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { -+ for (String fileName : files) { -+ os.putNextEntry(new ZipEntry(fileName)); -+ File f = new File(tmpDir, fileName); -+ copy(new FileInputStream(f), os); -+ } -+ -+ os.putNextEntry(new ZipEntry(RECORDING_FILE_CRC32)); -+ Writer writer = new OutputStreamWriter(os); -+ writer.write(Long.toString(crc32.getValue())); -+ writer.flush(); -+ } -+ -+ for (String fileName : files) { -+ File f = new File(tmpDir, fileName); -+ Files.delete(f.toPath()); -+ } -+ Files.delete(tmpDir.toPath()); -+ } -+ -+ public synchronized void closeNotSave() throws IOException { -+ packetStream.close(); -+ -+ String[] files = tmpDir.list(); -+ if (files == null) { -+ return; -+ } -+ -+ for (String fileName : files) { -+ File f = new File(tmpDir, fileName); -+ Files.delete(f.toPath()); -+ } -+ Files.delete(tmpDir.toPath()); -+ } -+ -+ private void copy(@NotNull InputStream in, OutputStream out) throws IOException { -+ byte[] buffer = new byte[8192]; -+ int len; -+ while ((len = in.read(buffer)) > -1) { -+ out.write(buffer, 0, len); -+ } -+ in.close(); -+ } -+ -+ private static boolean deleteDir(File dir) { -+ if (dir == null || !dir.exists()) { -+ return false; -+ } -+ -+ File[] files = dir.listFiles(); -+ if (files != null) { -+ for (File file : files) { -+ if (file.isDirectory()) { -+ deleteDir(file); -+ } else { -+ if (!file.delete()) { -+ return false; -+ } -+ } -+ } -+ } -+ -+ return dir.delete(); -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java b/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1568f6928d5d4f38ca1919c6de6ec9bb9deb20b2 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/ReplayMarker.java -@@ -0,0 +1,43 @@ -+package org.leavesmc.leaves.replay; -+ -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import com.google.gson.JsonSerializationContext; -+import com.google.gson.JsonSerializer; -+ -+import java.lang.reflect.Type; -+ -+public class ReplayMarker { -+ -+ public int time; -+ public String name; -+ public double x = 0; -+ public double y = 0; -+ public double z = 0; -+ public float phi = 0; -+ public float theta = 0; -+ public float varphi = 0; -+ -+ public static class Serializer implements JsonSerializer { -+ @Override -+ public JsonElement serialize(ReplayMarker src, Type typeOfSrc, JsonSerializationContext context) { -+ JsonObject ret = new JsonObject(); -+ JsonObject value = new JsonObject(); -+ JsonObject position = new JsonObject(); -+ ret.add("realTimestamp", new JsonPrimitive(src.time)); -+ ret.add("value", value); -+ -+ value.add("name", new JsonPrimitive(src.name)); -+ value.add("position", position); -+ -+ position.add("x", new JsonPrimitive(src.x)); -+ position.add("y", new JsonPrimitive(src.y)); -+ position.add("z", new JsonPrimitive(src.z)); -+ position.add("yaw", new JsonPrimitive(src.phi)); -+ position.add("pitch", new JsonPrimitive(src.theta)); -+ position.add("roll", new JsonPrimitive(src.varphi)); -+ return ret; -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java b/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d8f8f071c67cd0e29411d3f0f3b5e931abe86f65 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/ServerPhotographer.java -@@ -0,0 +1,222 @@ -+package org.leavesmc.leaves.replay; -+ -+import com.mojang.authlib.GameProfile; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ClientInformation; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.stats.ServerStatsCounter; -+import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.phys.Vec3; -+import org.bukkit.Bukkit; -+import org.bukkit.Location; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.bot.BotStatsCounter; -+import org.leavesmc.leaves.entity.CraftPhotographer; -+import org.leavesmc.leaves.entity.Photographer; -+ -+import java.io.File; -+import java.io.IOException; -+import java.util.List; -+import java.util.UUID; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.CopyOnWriteArrayList; -+ -+public class ServerPhotographer extends ServerPlayer { -+ -+ private static final List photographers = new CopyOnWriteArrayList<>(); -+ -+ public PhotographerCreateState createState; -+ private ServerPlayer followPlayer; -+ private Recorder recorder; -+ private File saveFile; -+ private Vec3 lastPos; -+ -+ private final ServerStatsCounter stats; -+ -+ private ServerPhotographer(MinecraftServer server, ServerLevel world, GameProfile profile) { -+ super(server, world, profile, ClientInformation.createDefault()); -+ this.gameMode = new ServerPhotographerGameMode(this); -+ this.followPlayer = null; -+ this.stats = new BotStatsCounter(server); -+ this.lastPos = this.position(); -+ } -+ -+ public static ServerPhotographer createPhotographer(@NotNull PhotographerCreateState state) throws IOException { -+ if (!isCreateLegal(state.id)) { -+ throw new IllegalArgumentException(state.id + " is a invalid photographer id"); -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ -+ ServerLevel world = ((CraftWorld) state.loc.getWorld()).getHandle(); -+ GameProfile profile = new GameProfile(UUID.randomUUID(), state.id); -+ -+ ServerPhotographer photographer = new ServerPhotographer(server, world, profile); -+ photographer.recorder = new Recorder(photographer, state.option, new File("replay", state.id)); -+ photographer.saveFile = new File("replay", state.id + ".mcpr"); -+ photographer.createState = state; -+ -+ photographer.recorder.start(); -+ MinecraftServer.getServer().getPlayerList().placeNewPhotographer(photographer.recorder, photographer, world, state.loc); -+ photographer.serverLevel().chunkSource.move(photographer); -+ photographer.setInvisible(true); -+ photographers.add(photographer); -+ -+ LeavesLogger.LOGGER.info("Photographer " + state.id + " created"); -+ -+ // TODO record distance -+ -+ return photographer; -+ } -+ -+ @Override -+ public void tick() { -+ super.tick(); -+ super.doTick(); -+ -+ if (this.server.getTickCount() % 10 == 0) { -+ connection.resetPosition(); -+ this.serverLevel().chunkSource.move(this); -+ } -+ -+ if (this.followPlayer != null) { -+ if (this.getCamera() == this || this.getCamera().level() != this.level()) { -+ this.getBukkitPlayer().teleport(this.getCamera().getBukkitEntity().getLocation()); -+ this.setCamera(followPlayer); -+ } -+ if (lastPos.distanceToSqr(this.position()) > 1024D) { -+ this.getBukkitPlayer().teleport(this.getCamera().getBukkitEntity().getLocation()); -+ } -+ } -+ -+ lastPos = this.position(); -+ } -+ -+ @Override -+ public void die(@NotNull DamageSource damageSource) { -+ super.die(damageSource); -+ remove(true); -+ } -+ -+ @Override -+ public boolean isInvulnerableTo(@NotNull ServerLevel world, @NotNull DamageSource damageSource) { -+ return true; -+ } -+ -+ @Override -+ public boolean hurtServer(@NotNull ServerLevel world, @NotNull DamageSource source, float amount) { -+ return false; -+ } -+ -+ @Override -+ public void setHealth(float health) { -+ } -+ -+ @NotNull -+ @Override -+ public ServerStatsCounter getStats() { -+ return stats; -+ } -+ -+ public void remove(boolean async) { -+ this.remove(async, true); -+ } -+ -+ public void remove(boolean async, boolean save) { -+ super.remove(RemovalReason.KILLED); -+ photographers.remove(this); -+ this.recorder.stop(); -+ this.server.getPlayerList().removePhotographer(this); -+ -+ LeavesLogger.LOGGER.info("Photographer " + createState.id + " removed"); -+ -+ if (!recorder.isSaved()) { -+ CompletableFuture future = recorder.saveRecording(saveFile, save); -+ if (!async) { -+ future.join(); -+ } -+ } -+ } -+ -+ public void setFollowPlayer(ServerPlayer followPlayer) { -+ this.setCamera(followPlayer); -+ this.followPlayer = followPlayer; -+ } -+ -+ public void setSaveFile(File saveFile) { -+ this.saveFile = saveFile; -+ } -+ -+ public void pauseRecording() { -+ this.recorder.pauseRecording(); -+ } -+ -+ public void resumeRecording() { -+ this.recorder.resumeRecording(); -+ } -+ -+ public static ServerPhotographer getPhotographer(String id) { -+ for (ServerPhotographer photographer : photographers) { -+ if (photographer.createState.id.equals(id)) { -+ return photographer; -+ } -+ } -+ return null; -+ } -+ -+ public static ServerPhotographer getPhotographer(UUID uuid) { -+ for (ServerPhotographer photographer : photographers) { -+ if (photographer.getUUID().equals(uuid)) { -+ return photographer; -+ } -+ } -+ return null; -+ } -+ -+ public static List getPhotographers() { -+ return photographers; -+ } -+ -+ public Photographer getBukkitPlayer() { -+ return getBukkitEntity(); -+ } -+ -+ @Override -+ @NotNull -+ public CraftPhotographer getBukkitEntity() { -+ return (CraftPhotographer) super.getBukkitEntity(); -+ } -+ -+ public static boolean isCreateLegal(@NotNull String name) { -+ if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { -+ return false; -+ } -+ -+ return Bukkit.getPlayerExact(name) == null && ServerPhotographer.getPhotographer(name) == null; -+ } -+ -+ public static class PhotographerCreateState { -+ -+ public RecorderOption option; -+ public Location loc; -+ public final String id; -+ -+ public PhotographerCreateState(Location loc, String id, RecorderOption option) { -+ this.loc = loc; -+ this.id = id; -+ this.option = option; -+ } -+ -+ public ServerPhotographer createSync() { -+ try { -+ return createPhotographer(this); -+ } catch (IOException e) { -+ e.printStackTrace(); -+ } -+ return null; -+ } -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java b/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c612215b0f1e8c3fae641e7a23c7cf7d165eca87 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/replay/ServerPhotographerGameMode.java -@@ -0,0 +1,35 @@ -+package org.leavesmc.leaves.replay; -+ -+import net.kyori.adventure.text.Component; -+import net.minecraft.server.level.ServerPlayerGameMode; -+import net.minecraft.world.level.GameType; -+import org.bukkit.event.player.PlayerGameModeChangeEvent; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public class ServerPhotographerGameMode extends ServerPlayerGameMode { -+ -+ public ServerPhotographerGameMode(ServerPhotographer photographer) { -+ super(photographer); -+ super.setGameModeForPlayer(GameType.SPECTATOR, null); -+ } -+ -+ @Override -+ public boolean changeGameModeForPlayer(@NotNull GameType gameMode) { -+ return false; -+ } -+ -+ @Nullable -+ @Override -+ public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) { -+ return null; -+ } -+ -+ @Override -+ protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) { -+ } -+ -+ @Override -+ public void tick() { -+ } -+} -diff --git a/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java b/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b0834f4b569b3e28ec7e026b3ff4236219498011 ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/util/UUIDSerializer.java -@@ -0,0 +1,17 @@ -+package org.leavesmc.leaves.util; -+ -+import com.google.gson.JsonElement; -+import com.google.gson.JsonPrimitive; -+import com.google.gson.JsonSerializationContext; -+import com.google.gson.JsonSerializer; -+import org.jetbrains.annotations.NotNull; -+ -+import java.lang.reflect.Type; -+import java.util.UUID; -+ -+public class UUIDSerializer implements JsonSerializer { -+ @Override -+ public JsonElement serialize(@NotNull UUID src, Type typeOfSrc, JsonSerializationContext context) { -+ return new JsonPrimitive(src.toString()); -+ } -+} diff --git a/patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch b/patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch deleted file mode 100644 index 9e6c3bee..00000000 --- a/patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Thu, 28 Sep 2023 20:30:46 +0800 -Subject: [PATCH] Leaves: Disable moved wrongly threshold - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6f00bc85205a4c481d85f95e30aeb61ab3db1e8a..68cf6f0f0d2d1eb23de070ea8471111a41a9f9bf 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -593,7 +593,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // Paper end - Prevent moving into unloaded chunks - -- if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { -+ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled && d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { // Leaves - disable can - // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); - this.send(new ClientboundMoveVehiclePacket(entity)); -@@ -629,7 +629,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - d10 = d6 * d6 + d7 * d7 + d8 * d8; - boolean flag2 = false; - -- if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot -+ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot // Leaves - disable can - 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)}); - } -@@ -1490,7 +1490,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, - toX, toY, toZ, toYaw, toPitch, true); - if (!event.isAllowed()) { -- if (event.getLogWarning()) -+ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled && event.getLogWarning()) // Leaves - disable can - ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); - this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); - return; -@@ -1560,7 +1560,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - d10 = d6 * d6 + d7 * d7 + d8 * d8; - boolean movedWrongly = false; // Paper - Add fail move event; rename - -- if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot -+ if (!org.dreeam.leaf.config.modules.gameplay.DisableMovedWronglyThreshold.enabled && !this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot // Leaves - disable can - // Paper start - Add fail move event - io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, - toX, toY, toZ, toYaw, toPitch, true); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java b/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java -new file mode 100644 -index 0000000000000000000000000000000000000000..14ed289c91a99d6fd7d12462a7bde506e2ecbedc ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/gameplay/DisableMovedWronglyThreshold.java -@@ -0,0 +1,22 @@ -+package org.dreeam.leaf.config.modules.gameplay; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class DisableMovedWronglyThreshold extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".player"; -+ } -+ -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".disable-moved-wrongly-threshold", enabled, -+ config.pickStringRegionBased( -+ "Disable moved quickly/wrongly checks.", -+ "关闭 moved wrongly/too quickly! 警告." -+ )); -+ } -+} diff --git a/patches/server/0037-Faster-Random-for-xaeroMapServerID-generation.patch b/patches/server/0037-Faster-Random-for-xaeroMapServerID-generation.patch deleted file mode 100644 index f0629547..00000000 --- a/patches/server/0037-Faster-Random-for-xaeroMapServerID-generation.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Mon, 9 Oct 2023 21:33:08 -0400 -Subject: [PATCH] Faster Random for xaeroMapServerID generation - - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index a81c0a7bc71b86eb652abcd06099d36f4de71ae9..c17e3484ced6d1fb6083dde7c68f8a5a03de0c4e 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -3,7 +3,7 @@ package org.dreeam.leaf.config.modules.network; - import org.dreeam.leaf.config.ConfigModules; - import org.dreeam.leaf.config.EnumConfigCategory; - --import java.util.Random; -+import java.util.concurrent.ThreadLocalRandom; - - public class ProtocolSupport extends ConfigModules { - -@@ -17,7 +17,7 @@ public class ProtocolSupport extends ConfigModules { - public static boolean asteorBarProtocol = false; - public static boolean chatImageProtocol = false; - public static boolean xaeroMapProtocol = false; -- public static int xaeroMapServerID = new Random().nextInt(); -+ public static int xaeroMapServerID = ThreadLocalRandom.current().nextInt(); // Leaf - Faster Random - public static boolean syncmaticaProtocol = false; - public static boolean syncmaticaQuota = false; - public static int syncmaticaQuotaLimit = 40000000; diff --git a/patches/server/0038-Petal-Async-Pathfinding.patch b/patches/server/0038-Petal-Async-Pathfinding.patch deleted file mode 100644 index f821bc9b..00000000 --- a/patches/server/0038-Petal-Async-Pathfinding.patch +++ /dev/null @@ -1,1335 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: peaches94 -Date: Sun, 26 Jun 2022 16:51:37 -0500 -Subject: [PATCH] Petal: Async Pathfinding - -Fixed & Updated by KaiijuMC -Original license: GPLv3 -Original project: https://github.com/KaiijuMC/Kaiiju - -Original license: GPLv3 -Original project: https://github.com/Bloom-host/Petal - -This patch was ported downstream from the Petal fork. - -Makes most pathfinding-related work happen asynchronously - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 6c603c83d9caeed480f5d67854e40c3fa8ff20dd..eb547af300d8ecea19b3e02e5ebe6139330c9d62 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -297,6 +297,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - @Nullable - @Override - public LivingEntity getTarget() { -+ //if (Thread.currentThread().getName().contains("petal-async-pathfinding-thread")) return this.target; // Kaiiju - Don't reset target when async pathfinding! // Leaf - Don't need this - return this.target; - } - -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 93d17d93922841354fb0bfb15ce43776fafb19d2..5d18afe6db88393061656b18e2485e343d7769d6 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 -@@ -84,6 +84,38 @@ public class AcquirePoi { - io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), world.purpurConfig.villagerAcquirePoiSearchRadius, world.purpurConfig.villagerAcquirePoiSearchRadius*world.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); - Set, BlockPos>> set = new java.util.HashSet<>(poiposes); - // Paper end - optimise POI access -+ // Kaiiju start - petal - Async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ // await on path async -+ Path possiblePath = findPathToPois(entity, set); -+ -+ // wait on the path to be processed -+ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { -+ // read canReach check -+ if (path == null || !path.canReach()) { -+ for (Pair, BlockPos> pair : set) { -+ long2ObjectMap.computeIfAbsent( -+ pair.getSecond().asLong(), -+ m -> new JitteredLinearRetry(entity.level().random, time) -+ ); -+ } -+ return; -+ } -+ BlockPos blockPos = path.getTarget(); -+ poiManager.getType(blockPos).ifPresent(poiType -> { -+ poiManager.take(poiPredicate, -+ (holder, blockPos2) -> blockPos2.equals(blockPos), -+ blockPos, -+ 1 -+ ); -+ queryResult.set(GlobalPos.of(world.dimension(), blockPos)); -+ entityStatus.ifPresent(status -> world.broadcastEntityEvent(entity, status)); -+ long2ObjectMap.clear(); -+ DebugPackets.sendPoiTicketCountPacket(world, blockPos); -+ }); -+ }); -+ } else { -+ // Kaiiju end - Path path = findPathToPois(entity, set); - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); -@@ -101,6 +133,7 @@ public class AcquirePoi { - ); - } - } -+ } // Kaiiju - Async path processing - - return true; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java -index 2a7a26ca447cc78f24e61a2bf557411c31eb16b2..4010cb7ad8897995c8b850f9279aad2a2b58d4eb 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java -@@ -21,6 +21,7 @@ public class MoveToTargetSink extends Behavior { - private int remainingCooldown; - @Nullable - private Path path; -+ private boolean finishedProcessing; // Kaiiju - petal - track when path is processed - @Nullable - private BlockPos lastTargetPos; - private float speedModifier; -@@ -53,9 +54,10 @@ public class MoveToTargetSink extends Behavior { - Brain brain = entity.getBrain(); - WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); - boolean bl = this.reachedTarget(entity, walkTarget); -- if (!bl && this.tryComputePath(entity, walkTarget, world.getGameTime())) { -+ if (!org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled && !bl && this.tryComputePath(entity, walkTarget, world.getGameTime())) { // Kaiiju - petal - async path processing means we can't know if the path is reachable here - this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); - return true; -+ } else if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled && !bl) { return true; // Kaiiju - async pathfinding - } else { - brain.eraseMemory(MemoryModuleType.WALK_TARGET); - if (bl) { -@@ -69,6 +71,7 @@ public class MoveToTargetSink extends Behavior { - - @Override - protected boolean canStillUse(ServerLevel world, Mob entity, long time) { -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled && !this.finishedProcessing) return true; // Kaiiju - petal - wait for processing - if (this.path != null && this.lastTargetPos != null) { - Optional optional = entity.getBrain().getMemory(MemoryModuleType.WALK_TARGET); - boolean bl = optional.map(MoveToTargetSink::isWalkTargetSpectator).orElse(false); -@@ -95,12 +98,68 @@ public class MoveToTargetSink extends Behavior { - - @Override - protected void start(ServerLevel serverLevel, Mob mob, long l) { -+ // Kaiiju start - petal - start processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ Brain brain = mob.getBrain(); -+ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); -+ -+ this.finishedProcessing = false; -+ this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); -+ this.path = this.computePath(mob, walkTarget); -+ return; -+ } -+ // Kaiiju end - mob.getBrain().setMemory(MemoryModuleType.PATH, this.path); - mob.getNavigation().moveTo(this.path, (double)this.speedModifier); - } - - @Override - protected void tick(ServerLevel serverLevel, Mob mob, long l) { -+ // Kaiiju start - petal - Async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ if (this.path != null && !this.path.isProcessed()) return; // wait for processing -+ -+ if (!this.finishedProcessing) { -+ this.finishedProcessing = true; -+ -+ Brain brain = mob.getBrain(); -+ boolean canReach = this.path != null && this.path.canReach(); -+ if (canReach) { -+ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); -+ } else if (!brain.hasMemoryValue(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)) { -+ brain.setMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, l); -+ } -+ -+ if (!canReach) { -+ Optional walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET); -+ -+ if (!walkTarget.isPresent()) return; -+ -+ BlockPos blockPos = walkTarget.get().getTarget().currentBlockPosition(); -+ Vec3 vec3 = DefaultRandomPos.getPosTowards((PathfinderMob) mob, 10, 7, Vec3.atBottomCenterOf(blockPos), (float) Math.PI / 2F); -+ if (vec3 != null) { -+ // try recalculating the path using a random position -+ this.path = mob.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); -+ this.finishedProcessing = false; -+ return; -+ } -+ } -+ -+ mob.getBrain().setMemory(MemoryModuleType.PATH, this.path); -+ mob.getNavigation().moveTo(this.path, this.speedModifier); -+ } -+ -+ Path path = mob.getNavigation().getPath(); -+ Brain brain = mob.getBrain(); -+ -+ if (path != null && this.lastTargetPos != null && brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) { -+ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); // we know isPresent = true -+ if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0D) { -+ this.start(serverLevel, mob, l); -+ } -+ } -+ } else { -+ // Kaiiju end - Path path = mob.getNavigation().getPath(); - Brain brain = mob.getBrain(); - if (this.path != path) { -@@ -116,7 +175,23 @@ public class MoveToTargetSink extends Behavior { - this.start(serverLevel, mob, l); - } - } -+ } // Kaiiju - async path processing -+ } -+ -+ // Kaiiju start - petal - Async path processing -+ @Nullable -+ private Path computePath(Mob entity, WalkTarget walkTarget) { -+ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); -+ // don't pathfind outside region -+ //if (!io.papermc.paper.util.TickThread.isTickThreadFor((ServerLevel) entity.level(), blockPos)) return null; // Leaf - Don't need this -+ this.speedModifier = walkTarget.getSpeedModifier(); -+ Brain brain = entity.getBrain(); -+ if (this.reachedTarget(entity, walkTarget)) { -+ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); -+ } -+ return entity.getNavigation().createPath(blockPos, 0); - } -+ // Kaiiju end - - private boolean tryComputePath(Mob entity, WalkTarget walkTarget, long time) { - BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java -index 6802e0c4d331c7125114dd86409f6a110465ab82..601f43615cb55142125e21411f651318ee760e9f 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java -@@ -60,6 +60,26 @@ public class SetClosestHomeAsWalkTarget { - poiType -> poiType.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY - ) - .collect(Collectors.toSet()); -+ // Kaiiju start - petal - Async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ // await on path async -+ Path possiblePath = AcquirePoi.findPathToPois(entity, set); -+ -+ // wait on the path to be processed -+ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { -+ if (path == null || !path.canReach() || mutableInt.getValue() < 5) { // read canReach check -+ long2LongMap.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); -+ return; -+ } -+ BlockPos blockPos = path.getTarget(); -+ Optional> optional2 = poiManager.getType(blockPos); -+ if (optional2.isPresent()) { -+ walkTarget.set(new WalkTarget(blockPos, speed, 1)); -+ DebugPackets.sendPoiTicketCountPacket(world, blockPos); -+ } -+ }); -+ } else { -+ // Kaiiju end - Path path = AcquirePoi.findPathToPois(entity, set); - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); -@@ -71,6 +91,7 @@ public class SetClosestHomeAsWalkTarget { - } else if (mutableInt.getValue() < 5) { - long2LongMap.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); - } -+ } // Kaiiju - async path processing - - return true; - } else { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java -index 74aca307b4ebffe4e33c4fca3e07c23ca87622ac..37e61aa259f573cc5b5844026af99f3c72945f34 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java -@@ -56,7 +56,7 @@ public abstract class DoorInteractGoal extends Goal { - } else { - GroundPathNavigation groundPathNavigation = (GroundPathNavigation)this.mob.getNavigation(); - Path path = groundPathNavigation.getPath(); -- if (path != null && !path.isDone() && groundPathNavigation.canOpenDoors()) { -+ if (path != null && path.isProcessed() && !path.isDone() && groundPathNavigation.canOpenDoors()) { // Kaiiju - async pathfinding - ensure path is processed - for (int i = 0; i < Math.min(path.getNextNodeIndex() + 2, path.getNodeCount()); i++) { - Node node = path.getNode(i); - this.doorPos = new BlockPos(node.x, node.y + 1, node.z); -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java -index ee38e447a810094d2253b85714b74282a4b4c2bc..e4699174bd7dbd2e24d5d11c385fb40d4aeb79c6 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java -@@ -12,10 +12,26 @@ public class AmphibiousPathNavigation extends PathNavigation { - super(mob, world); - } - -+ // Kaiiju start - petal - async path processing -+ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { -+ AmphibiousNodeEvaluator nodeEvaluator = new AmphibiousNodeEvaluator(false); -+ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); -+ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); -+ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); -+ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); -+ return nodeEvaluator; -+ }; -+ // Kaiiju end -+ - @Override - protected PathFinder createPathFinder(int range) { - this.nodeEvaluator = new AmphibiousNodeEvaluator(false); - this.nodeEvaluator.setCanPassDoors(true); -+ // Kaiiju start - petal - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range); - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java -index a3e0c5af4cc9323c02e88e768cbda9e46854aea1..231664c516ded9bb4619d8afb41e5cb2806209be 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java -@@ -16,10 +16,26 @@ public class FlyingPathNavigation extends PathNavigation { - super(entity, world); - } - -+ // Kaiiju start - petal - async path processing -+ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { -+ FlyNodeEvaluator nodeEvaluator = new FlyNodeEvaluator(); -+ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); -+ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); -+ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); -+ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); -+ return nodeEvaluator; -+ }; -+ // Kaiiju end -+ - @Override - protected PathFinder createPathFinder(int range) { - this.nodeEvaluator = new FlyNodeEvaluator(); - this.nodeEvaluator.setCanPassDoors(true); -+ // Kaiiju start - petal - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range); - } - -@@ -49,6 +65,7 @@ public class FlyingPathNavigation extends PathNavigation { - if (this.hasDelayedRecomputation) { - this.recomputePath(); - } -+ if (this.path != null && !this.path.isProcessed()) return; // Kaiiju - petal - async path processing - - if (!this.isDone()) { - if (this.canUpdatePath()) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -index f73b559b8e60859020f762dab88b67b8c912bf8f..ce7dbad7bad5379679a757826a2eaac0ac98f718 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -@@ -24,10 +24,26 @@ public class GroundPathNavigation extends PathNavigation { - super(entity, world); - } - -+ // Kaiiju start - petal - async path processing -+ protected static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { -+ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); -+ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); -+ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); -+ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); -+ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); -+ return nodeEvaluator; -+ }; -+ // Kaiiju end -+ - @Override - protected PathFinder createPathFinder(int range) { - this.nodeEvaluator = new WalkNodeEvaluator(); - this.nodeEvaluator.setCanPassDoors(true); -+ // Kaiiju start - petal - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range); - } - -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 79342566ae54b5a8ccd8cab01b8282fada5b7bab..7ea1d007f9aeabb26f8e8c42a0d5bbc883fccb3e 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 -@@ -166,6 +166,10 @@ public abstract class PathNavigation { - return null; - } else if (!this.canUpdatePath()) { - return null; -+ // Kaiiju start - petal - catch early if it's still processing these positions let it keep processing -+ } else if (this.path instanceof org.dreeam.leaf.async.path.AsyncPath asyncPath && !asyncPath.isProcessed() && asyncPath.hasSameProcessingPositions(positions)) { -+ return this.path; -+ // Kaiiju end - } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) { - return this.path; - } else { -@@ -190,11 +194,29 @@ public abstract class PathNavigation { - int i = (int)(followRange + (float)range); - PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i)); - Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, positions, followRange, distance, this.maxVisitedNodesMultiplier); -+ // Kaiiju start - petal - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ // assign early a target position. most calls will only have 1 position -+ if (!positions.isEmpty()) this.targetPos = positions.iterator().next(); -+ -+ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(path, processedPath -> { -+ // check that processing didn't take so long that we calculated a new path -+ if (processedPath != this.path) return; -+ -+ if (processedPath != null && processedPath.getTarget() != null) { -+ this.targetPos = processedPath.getTarget(); -+ this.reachRange = distance; -+ this.resetStuckTimeout(); -+ } -+ }); -+ } else { -+ // Kaiiju end - if (path != null && path.getTarget() != null) { - this.targetPos = path.getTarget(); - this.reachRange = distance; - this.resetStuckTimeout(); - } -+ } // Kaiiju - async path processing - - return path; - } -@@ -245,8 +267,8 @@ public abstract class PathNavigation { - if (this.isDone()) { - return false; - } else { -- this.trimPath(); -- if (this.path.getNodeCount() <= 0) { -+ if (path.isProcessed()) this.trimPath(); // Kaiiju - petal - only trim if processed -+ if (path.isProcessed() && this.path.getNodeCount() <= 0) { // Kaiiju - petal - only check node count if processed - return false; - } else { - this.speedModifier = speed; -@@ -269,6 +291,7 @@ public abstract class PathNavigation { - if (this.hasDelayedRecomputation) { - this.recomputePath(); - } -+ if (this.path != null && !this.path.isProcessed()) return; // Kaiiju - petal - skip pathfinding if we're still processing - - if (!this.isDone()) { - if (this.canUpdatePath()) { -@@ -295,6 +318,7 @@ public abstract class PathNavigation { - } - - protected void followThePath() { -+ if (!this.path.isProcessed()) return; // Kaiiju - petal - skip if not processed - Vec3 vec3 = this.getTempMobPos(); - this.maxDistanceToWaypoint = this.mob.getBbWidth() > 0.75F ? this.mob.getBbWidth() / 2.0F : 0.75F - this.mob.getBbWidth() / 2.0F; - Vec3i vec3i = this.path.getNextNodePos(); -@@ -451,7 +475,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.isProcessed() && !this.path.isDone() && this.path.getNodeCount() != 0) { // Kaiiju - petal - Skip if not processed - Node node = this.path.getEndNode(); - Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0, ((double)node.y + this.mob.getY()) / 2.0, ((double)node.z + this.mob.getZ()) / 2.0); - return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex())); -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java -index 0446ac540d8509a653abe1a8bc10f52fb43d6ae1..faa9a67bb0412caca005337d8c14b2e3098c2c45 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java -@@ -15,10 +15,26 @@ public class WaterBoundPathNavigation extends PathNavigation { - super(entity, world); - } - -+ // Kaiiju start - petal - async path processing -+ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { -+ SwimNodeEvaluator nodeEvaluator = new SwimNodeEvaluator(nodeEvaluatorFeatures.allowBreaching()); -+ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); -+ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); -+ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); -+ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); -+ return nodeEvaluator; -+ }; -+ // Kaiiju end -+ - @Override - protected PathFinder createPathFinder(int range) { - this.allowBreaching = this.mob.getType() == EntityType.DOLPHIN; - this.nodeEvaluator = new SwimNodeEvaluator(this.allowBreaching); -+ // Kaiiju start - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range); - } - -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 9104d7010bda6f9f73b478c11490ef9c53f76da2..fb12b8581ebaccc12dc336cc73a847d75b06c421 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 -@@ -57,6 +57,26 @@ public class NearestBedSensor extends Sensor { - 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(), world.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur -+ // Kaiiju start - await on async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); -+ org.dreeam.leaf.async.path.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { -+ // read canReach check -+ if ((path == null || !path.canReach()) && this.triedCount < 5) { -+ this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); -+ return; -+ } -+ if (path == null) return; -+ -+ BlockPos blockPos = path.getTarget(); -+ Optional> optional = poiManager.getType(blockPos); -+ if (optional.isPresent()) { -+ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, blockPos); -+ } -+ }); -+ } else { -+ // Kaiiju end -+ - Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); - // Paper end - optimise POI access - if (path != null && path.canReach()) { -@@ -68,6 +88,7 @@ public class NearestBedSensor extends Sensor { - } else if (this.triedCount < 5) { - this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); - } -+ } // Kaiiju - async path processing - } - } - } -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 dc8df0912c1d18176e18a8f4dc43c4f60f81b659..a0e3ec138a1b9750afcc0e2712be377b26f58dcd 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -1206,7 +1206,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } else { - Bee.this.pathfindRandomlyTowards(Bee.this.hivePos); - } -- } else { -+ } else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Kaiiju - petal - check processing - boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos); - - if (!flag) { -@@ -1265,7 +1265,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } else { - Path pathentity = Bee.this.navigation.getPath(); - -- return pathentity != null && pathentity.getTarget().equals(pos) && pathentity.canReach() && pathentity.isDone(); -+ return pathentity != null && pathentity.isProcessed() && pathentity.getTarget().equals(pos) && pathentity.canReach() && pathentity.isDone(); // Kaiiju - petal - ensure path is processed - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index 0f077f9be003a17b77e9b29fa2f398a4de1fa3c5..d8fe42996c8ba3d44e31197b24294b86b845e2a8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -@@ -475,6 +475,17 @@ public class Frog extends Animal implements VariantHolder> { - super(frog, world); - } - -+ // Kaiiju start - petal - async path processing -+ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { -+ Frog.FrogNodeEvaluator nodeEvaluator = new Frog.FrogNodeEvaluator(true); -+ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); -+ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); -+ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); -+ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); -+ return nodeEvaluator; -+ }; -+ // Kaiiju end -+ - @Override - public boolean canCutCorner(PathType nodeType) { - return nodeType != PathType.WATER_BORDER && super.canCutCorner(nodeType); -@@ -484,6 +495,11 @@ public class Frog extends Animal implements VariantHolder> { - protected PathFinder createPathFinder(int range) { - this.nodeEvaluator = new Frog.FrogNodeEvaluator(true); - this.nodeEvaluator.setCanPassDoors(true); -+ // Kaiiju start - petal - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -index 949207eda199c874f2f14074b5a4fff5462b86b9..53ebc584a7eb8b18e959aff75a11a37fd0b8ace2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -@@ -308,7 +308,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - - protected boolean closeToNextPos() { - Path path = this.getNavigation().getPath(); -- if (path != null) { -+ if (path != null && path.isProcessed()) { // Kaiiju - petal - ensure path is processed - BlockPos blockPos = path.getTarget(); - if (blockPos != null) { - double d = this.distanceToSqr((double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index c3b5b34a54de945071692293645b8a8865aed961..531a71c45a573b6832230c2b2aac51b4187c3981 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java -@@ -608,10 +608,26 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - super(entity, world); - } - -+ // Kaiiju start - petal - async path processing -+ private static final org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.dreeam.leaf.async.path.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { -+ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); -+ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); -+ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); -+ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); -+ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); -+ return nodeEvaluator; -+ }; -+ // Kaiiju end -+ - @Override - protected PathFinder createPathFinder(int range) { - this.nodeEvaluator = new WalkNodeEvaluator(); - this.nodeEvaluator.setCanPassDoors(true); -+ // Kaiiju start - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, nodeEvaluatorGenerator); -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range); - } - -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 d0f744597de323f6169e15cabe9b3a80dbdbf5bb..a429de1fdb4823b1f1df42dfe213853cc06ef1c2 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 -@@ -613,6 +613,16 @@ public class Warden extends Monster implements VibrationSystem { - protected PathFinder createPathFinder(int range) { - this.nodeEvaluator = new WalkNodeEvaluator(); - this.nodeEvaluator.setCanPassDoors(true); -+ // Kaiiju start - petal - async path processing -+ if (org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) { -+ return new PathFinder(this.nodeEvaluator, range, GroundPathNavigation.nodeEvaluatorGenerator) { -+ @Override -+ protected float distance(Node a, Node b) { -+ return a.distanceToXZ(b); -+ } -+ }; -+ } -+ // Kaiiju end - return new PathFinder(this.nodeEvaluator, range) { // CraftBukkit - decompile error - @Override - protected float distance(Node a, Node b) { -diff --git a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -index 155c7240b1112729333e6968122568c707d8f66b..57a0170439ac8dff818c89443bca62e5ceeb3003 100644 ---- a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -@@ -223,8 +223,15 @@ public class ShulkerBoxBlock extends BaseEntityBlock { - - @Override - protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { -+ //if (Thread.currentThread().getName().contains("petal-async-pathfinding-thread")) return Shapes.block(); // Kaiiju - async pathfinding - we cannot get block entities // Leaf - Don't need this - BlockEntity blockEntity = world.getBlockEntity(pos); - return blockEntity instanceof ShulkerBoxBlockEntity ? Shapes.create(((ShulkerBoxBlockEntity)blockEntity).getBoundingBox(state)) : Shapes.block(); -+ // Kaiiju start - async pathfinding - workaround // Leaf - Don't need this -+ /* -+ } catch (NullPointerException e) { -+ return Shapes.block(); -+ } -+ */ - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Path.java b/src/main/java/net/minecraft/world/level/pathfinder/Path.java -index d9d0fff9962131808d54cca20f209df50b8e4af1..f2dcbb40ed43e6ddf6f3dc27fcff04f7962312aa 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/Path.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/Path.java -@@ -27,6 +27,17 @@ public class Path { - this.reached = reachesTarget; - } - -+ // Kaiiju start - petal - async path processing -+ /** -+ * checks if the path is completely processed in the case of it being computed async -+ * -+ * @return true if the path is processed -+ */ -+ public boolean isProcessed() { -+ return true; -+ } -+ // Kaiiju end -+ - public void advance() { - this.nextNodeIndex++; - } -@@ -100,6 +111,7 @@ public class Path { - } - - public boolean sameAs(@Nullable Path o) { -+ if (o == this) return true; // Kaiiju - petal - short circuit - if (o == null) { - return false; - } else if (o.nodes.size() != this.nodes.size()) { -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 d7ac3a6a1d7d5561aae153ecb1455b8972d9a61d..3cedcf0ae24a67d5bb1ac6b06bb5c0346d82a84a 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java -@@ -22,10 +22,18 @@ public class PathFinder { - public final NodeEvaluator nodeEvaluator; - private static final boolean DEBUG = false; - private final BinaryHeap openSet = new BinaryHeap(); -+ private final @Nullable org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator; // Kaiiju - petal - we use this later to generate an evaluator - -- public PathFinder(NodeEvaluator pathNodeMaker, int range) { -+ public PathFinder(NodeEvaluator pathNodeMaker, int range, @Nullable org.dreeam.leaf.async.path.NodeEvaluatorGenerator nodeEvaluatorGenerator) { // Kaiiju - petal - add nodeEvaluatorGenerator - this.nodeEvaluator = pathNodeMaker; - this.maxVisitedNodes = range; -+ // Kaiiju start - petal - support nodeEvaluatorgenerators -+ this.nodeEvaluatorGenerator = nodeEvaluatorGenerator; -+ } -+ -+ public PathFinder(NodeEvaluator pathNodeMaker, int range) { -+ this(pathNodeMaker, range, null); -+ // Kaiiju end - } - - public void setMaxVisitedNodes(int range) { -@@ -34,27 +42,64 @@ public class PathFinder { - - @Nullable - public Path findPath(PathNavigationRegion world, Mob mob, Set positions, float followRange, int distance, float rangeMultiplier) { -- this.openSet.clear(); -- this.nodeEvaluator.prepare(world, mob); -- Node node = this.nodeEvaluator.getStart(); -+ if (!org.dreeam.leaf.config.modules.async.AsyncPathfinding.enabled) -+ this.openSet.clear(); // Kaiiju - petal - it's always cleared in processPath -+ // Kaiiju start - petal - use a generated evaluator if we have one otherwise run sync -+ NodeEvaluator nodeEvaluator = this.nodeEvaluatorGenerator == null -+ ? this.nodeEvaluator -+ : org.dreeam.leaf.async.path.NodeEvaluatorCache.takeNodeEvaluator(this.nodeEvaluatorGenerator, this.nodeEvaluator); -+ nodeEvaluator.prepare(world, mob); -+ Node node = nodeEvaluator.getStart(); -+ // Kaiiju end - if (node == null) { -+ org.dreeam.leaf.async.path.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); // Kaiiju - petal - handle nodeEvaluatorGenerator - return null; - } else { - // Paper start - Perf: remove streams and optimize collection - List> map = Lists.newArrayList(); - for (final BlockPos pos : positions) { -- map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos)); -+ map.add(new java.util.AbstractMap.SimpleEntry<>(nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos)); // Kaiiju - petal - handle nodeEvaluatorGenerator - } - // Paper end - Perf: remove streams and optimize collection -- Path path = this.findPath(node, map, followRange, distance, rangeMultiplier); -- this.nodeEvaluator.done(); -- return path; -+ // Kaiiju start - petal - async path processing -+ if (this.nodeEvaluatorGenerator == null) { -+ // run sync :( -+ org.dreeam.leaf.async.path.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); -+ return this.findPath(node, map, followRange, distance, rangeMultiplier); // Gale - Purpur - remove vanilla profiler -+ } -+ -+ return new org.dreeam.leaf.async.path.AsyncPath(Lists.newArrayList(), positions, () -> { -+ try { -+ return this.processPath(nodeEvaluator, node, map, followRange, distance, rangeMultiplier); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ return null; -+ } finally { -+ nodeEvaluator.done(); -+ org.dreeam.leaf.async.path.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator); -+ } -+ }); -+ // Kaiiju end - } - } - - @Nullable - // Paper start - Perf: remove streams and optimize collection - private Path findPath(Node startNode, List> positions, float followRange, int distance, float rangeMultiplier) { -+ // Kaiiju start - petal - split pathfinding into the original sync method for compat and processing for delaying -+ try { -+ return this.processPath(this.nodeEvaluator, startNode, positions, followRange, distance, rangeMultiplier); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ return null; -+ } finally { -+ this.nodeEvaluator.done(); -+ } -+ } -+ -+ private synchronized @org.jetbrains.annotations.NotNull Path processPath(NodeEvaluator nodeEvaluator, Node startNode, List> positions, float followRange, int distance, float rangeMultiplier) { // sync to only use the caching functions in this class on a single thread -+ org.apache.commons.lang3.Validate.isTrue(!positions.isEmpty()); // ensure that we have at least one position, which means we'll always return a path -+ // Kaiiju end - // Set set = positions.keySet(); - startNode.g = 0.0F; - startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection -@@ -90,7 +135,7 @@ public class PathFinder { - } - - if (!(node.distanceTo(startNode) >= followRange)) { -- int k = this.nodeEvaluator.getNeighbors(this.neighbors, node); -+ int k = nodeEvaluator.getNeighbors(this.neighbors, node); // Kaiiju - petal - use provided nodeEvaluator - - for (int l = 0; l < k; l++) { - Node node2 = this.neighbors[l]; -@@ -122,6 +167,7 @@ public class PathFinder { - if (best == null || comparator.compare(path, best) < 0) - best = path; - } -+ //noinspection ConstantConditions // Kaiiju - petal - ignore this warning, we know that the above loop always runs at least once since positions is not empty - return best; - // Paper end - Perf: remove streams and optimize collection - } -diff --git a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java -index 6308822f819d7cb84c8070c8a7eec1a3f822114b..e49851557f991ca1fc2f78abfb819609e672a526 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java -@@ -15,7 +15,7 @@ import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.material.FluidState; - - public class SwimNodeEvaluator extends NodeEvaluator { -- private final boolean allowBreaching; -+ public final boolean allowBreaching; // Kaiiju - make this public - private final Long2ObjectMap pathTypesByPosCache = new Long2ObjectOpenHashMap<>(); - - public SwimNodeEvaluator(boolean canJumpOutOfWater) { -diff --git a/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java b/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ba44944fd043e3982477bfee2c48a0e765d62db0 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java -@@ -0,0 +1,295 @@ -+package org.dreeam.leaf.async.path; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.pathfinder.Node; -+import net.minecraft.world.level.pathfinder.Path; -+import net.minecraft.world.phys.Vec3; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Set; -+import java.util.function.Supplier; -+ -+/** -+ * i'll be using this to represent a path that not be processed yet! -+ */ -+public class AsyncPath extends Path { -+ -+ /** -+ * marks whether this async path has been processed -+ */ -+ private volatile PathProcessState processState = PathProcessState.WAITING; -+ -+ /** -+ * runnables waiting for this to be processed -+ */ -+ private final List postProcessing = new ArrayList<>(0); -+ -+ /** -+ * a list of positions that this path could path towards -+ */ -+ private final Set positions; -+ -+ /** -+ * the supplier of the real processed path -+ */ -+ private final Supplier pathSupplier; -+ -+ /* -+ * Processed values -+ */ -+ -+ /** -+ * this is a reference to the nodes list in the parent `Path` object -+ */ -+ private final List nodes; -+ /** -+ * the block we're trying to path to -+ *

-+ * while processing, we have no idea where this is so consumers of `Path` should check that the path is processed before checking the target block -+ */ -+ private @Nullable BlockPos target; -+ /** -+ * how far we are to the target -+ *

-+ * while processing, the target could be anywhere but theoretically we're always "close" to a theoretical target so default is 0 -+ */ -+ private float distToTarget = 0; -+ /** -+ * whether we can reach the target -+ *

-+ * while processing, we can always theoretically reach the target so default is true -+ */ -+ private boolean canReach = true; -+ -+ public AsyncPath(@NotNull List emptyNodeList, @NotNull Set positions, @NotNull Supplier pathSupplier) { -+ //noinspection ConstantConditions -+ super(emptyNodeList, null, false); -+ -+ this.nodes = emptyNodeList; -+ this.positions = positions; -+ this.pathSupplier = pathSupplier; -+ -+ AsyncPathProcessor.queue(this); -+ } -+ -+ @Override -+ public boolean isProcessed() { -+ return this.processState == PathProcessState.COMPLETED; -+ } -+ -+ /** -+ * returns the future representing the processing state of this path -+ */ -+ public synchronized void postProcessing(@NotNull Runnable runnable) { -+ if (isProcessed()) { -+ runnable.run(); -+ } else { -+ this.postProcessing.add(runnable); -+ } -+ } -+ -+ /** -+ * an easy way to check if this processing path is the same as an attempted new path -+ * -+ * @param positions - the positions to compare against -+ * @return true if we are processing the same positions -+ */ -+ public boolean hasSameProcessingPositions(final Set positions) { -+ if (this.positions.size() != positions.size()) { -+ return false; -+ } -+ -+ return this.positions.containsAll(positions); -+ } -+ -+ /** -+ * starts processing this path -+ */ -+ public synchronized void process() { -+ if (this.processState == PathProcessState.COMPLETED || -+ this.processState == PathProcessState.PROCESSING) { -+ return; -+ } -+ -+ processState = PathProcessState.PROCESSING; -+ -+ final Path bestPath = this.pathSupplier.get(); -+ -+ this.nodes.addAll(bestPath.nodes); // we mutate this list to reuse the logic in Path -+ this.target = bestPath.getTarget(); -+ this.distToTarget = bestPath.getDistToTarget(); -+ this.canReach = bestPath.canReach(); -+ -+ processState = PathProcessState.COMPLETED; -+ -+ for (Runnable runnable : this.postProcessing) { -+ runnable.run(); -+ } // Run tasks after processing -+ } -+ -+ /** -+ * if this path is accessed while it hasn't processed, just process it in-place -+ */ -+ private void checkProcessed() { -+ if (this.processState == PathProcessState.WAITING || -+ this.processState == PathProcessState.PROCESSING) { // Block if we are on processing -+ this.process(); -+ } -+ } -+ -+ /* -+ * overrides we need for final fields that we cannot modify after processing -+ */ -+ -+ @Override -+ public @NotNull BlockPos getTarget() { -+ this.checkProcessed(); -+ -+ return this.target; -+ } -+ -+ @Override -+ public float getDistToTarget() { -+ this.checkProcessed(); -+ -+ return this.distToTarget; -+ } -+ -+ @Override -+ public boolean canReach() { -+ this.checkProcessed(); -+ -+ return this.canReach; -+ } -+ -+ /* -+ * overrides to ensure we're processed first -+ */ -+ -+ @Override -+ public boolean isDone() { -+ return this.processState == PathProcessState.COMPLETED && super.isDone(); -+ } -+ -+ @Override -+ public void advance() { -+ this.checkProcessed(); -+ -+ super.advance(); -+ } -+ -+ @Override -+ public boolean notStarted() { -+ this.checkProcessed(); -+ -+ return super.notStarted(); -+ } -+ -+ @Nullable -+ @Override -+ public Node getEndNode() { -+ this.checkProcessed(); -+ -+ return super.getEndNode(); -+ } -+ -+ @Override -+ public Node getNode(int index) { -+ this.checkProcessed(); -+ -+ return super.getNode(index); -+ } -+ -+ @Override -+ public void truncateNodes(int length) { -+ this.checkProcessed(); -+ -+ super.truncateNodes(length); -+ } -+ -+ @Override -+ public void replaceNode(int index, Node node) { -+ this.checkProcessed(); -+ -+ super.replaceNode(index, node); -+ } -+ -+ @Override -+ public int getNodeCount() { -+ this.checkProcessed(); -+ -+ return super.getNodeCount(); -+ } -+ -+ @Override -+ public int getNextNodeIndex() { -+ this.checkProcessed(); -+ -+ return super.getNextNodeIndex(); -+ } -+ -+ @Override -+ public void setNextNodeIndex(int nodeIndex) { -+ this.checkProcessed(); -+ -+ super.setNextNodeIndex(nodeIndex); -+ } -+ -+ @Override -+ public Vec3 getEntityPosAtNode(Entity entity, int index) { -+ this.checkProcessed(); -+ -+ return super.getEntityPosAtNode(entity, index); -+ } -+ -+ @Override -+ public BlockPos getNodePos(int index) { -+ this.checkProcessed(); -+ -+ return super.getNodePos(index); -+ } -+ -+ @Override -+ public Vec3 getNextEntityPos(Entity entity) { -+ this.checkProcessed(); -+ -+ return super.getNextEntityPos(entity); -+ } -+ -+ @Override -+ public BlockPos getNextNodePos() { -+ this.checkProcessed(); -+ -+ return super.getNextNodePos(); -+ } -+ -+ @Override -+ public Node getNextNode() { -+ this.checkProcessed(); -+ -+ return super.getNextNode(); -+ } -+ -+ @Nullable -+ @Override -+ public Node getPreviousNode() { -+ this.checkProcessed(); -+ -+ return super.getPreviousNode(); -+ } -+ -+ @Override -+ public boolean hasNext() { -+ this.checkProcessed(); -+ -+ return super.hasNext(); -+ } -+ -+ public PathProcessState getProcessState() { -+ return processState; -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..192edd0fdc8e2fd7fa11bef416544810c94f292b ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java -@@ -0,0 +1,51 @@ -+package org.dreeam.leaf.async.path; -+ -+import com.google.common.util.concurrent.ThreadFactoryBuilder; -+ -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.level.pathfinder.Path; -+ -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.concurrent.*; -+import java.util.function.Consumer; -+ -+/** -+ * used to handle the scheduling of async path processing -+ */ -+public class AsyncPathProcessor { -+ -+ private static final Executor pathProcessingExecutor = new ThreadPoolExecutor( -+ 1, -+ org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads, -+ org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, -+ new LinkedBlockingQueue<>(), -+ new ThreadFactoryBuilder() -+ .setNameFormat("Leaf Async Pathfinding Thread - %d") -+ .setPriority(Thread.NORM_PRIORITY - 2) -+ .build() -+ ); -+ -+ protected static CompletableFuture queue(@NotNull AsyncPath path) { -+ return CompletableFuture.runAsync(path::process, pathProcessingExecutor); -+ } -+ -+ /** -+ * takes a possibly unprocessed path, and waits until it is completed -+ * the consumer will be immediately invoked if the path is already processed -+ * the consumer will always be called on the main thread -+ * -+ * @param path a path to wait on -+ * @param afterProcessing a consumer to be called -+ */ -+ public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) { -+ if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) { -+ asyncPath.postProcessing(() -> -+ MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path)) -+ ); -+ } else { -+ afterProcessing.accept(path); -+ } -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b147a9675a45bd1306e4cf2a4f155025ce1ae1bf ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java -@@ -0,0 +1,45 @@ -+package org.dreeam.leaf.async.path; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import net.minecraft.world.level.pathfinder.NodeEvaluator; -+ -+import org.apache.commons.lang.Validate; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Map; -+import java.util.Queue; -+import java.util.concurrent.ConcurrentHashMap; -+ -+public class NodeEvaluatorCache { -+ private static final Map> threadLocalNodeEvaluators = new ConcurrentHashMap<>(); -+ private static final Map nodeEvaluatorToGenerator = new ConcurrentHashMap<>(); -+ -+ private static @NotNull Queue getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) { -+ return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, key -> new MultiThreadedQueue<>()); -+ } -+ -+ public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) { -+ final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator); -+ NodeEvaluator nodeEvaluator = getQueueForFeatures(nodeEvaluatorFeatures).poll(); -+ -+ if (nodeEvaluator == null) { -+ nodeEvaluator = generator.generate(nodeEvaluatorFeatures); -+ } -+ -+ nodeEvaluatorToGenerator.put(nodeEvaluator, generator); -+ -+ return nodeEvaluator; -+ } -+ -+ public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { -+ final NodeEvaluatorGenerator generator = nodeEvaluatorToGenerator.remove(nodeEvaluator); -+ Validate.notNull(generator, "NodeEvaluator already returned"); -+ -+ final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator); -+ getQueueForFeatures(nodeEvaluatorFeatures).offer(nodeEvaluator); -+ } -+ -+ public static void removeNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { -+ nodeEvaluatorToGenerator.remove(nodeEvaluator); -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2c4876bc001463eea818e429e652f164eca0187c ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorFeatures.java -@@ -0,0 +1,23 @@ -+package org.dreeam.leaf.async.path; -+ -+import net.minecraft.world.level.pathfinder.NodeEvaluator; -+import net.minecraft.world.level.pathfinder.SwimNodeEvaluator; -+ -+public record NodeEvaluatorFeatures( -+ NodeEvaluatorType type, -+ boolean canPassDoors, -+ boolean canFloat, -+ boolean canWalkOverFences, -+ boolean canOpenDoors, -+ boolean allowBreaching -+) { -+ public static NodeEvaluatorFeatures fromNodeEvaluator(NodeEvaluator nodeEvaluator) { -+ NodeEvaluatorType type = NodeEvaluatorType.fromNodeEvaluator(nodeEvaluator); -+ boolean canPassDoors = nodeEvaluator.canPassDoors(); -+ boolean canFloat = nodeEvaluator.canFloat(); -+ boolean canWalkOverFences = nodeEvaluator.canWalkOverFences(); -+ boolean canOpenDoors = nodeEvaluator.canOpenDoors(); -+ boolean allowBreaching = nodeEvaluator instanceof SwimNodeEvaluator swimNodeEvaluator && swimNodeEvaluator.allowBreaching; -+ return new NodeEvaluatorFeatures(type, canPassDoors, canFloat, canWalkOverFences, canOpenDoors, allowBreaching); -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..062ddc246ed5f8f8f0599a8e86c9b85c4420daf7 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorGenerator.java -@@ -0,0 +1,11 @@ -+package org.dreeam.leaf.async.path; -+ -+import net.minecraft.world.level.pathfinder.NodeEvaluator; -+import org.jetbrains.annotations.NotNull; -+ -+public interface NodeEvaluatorGenerator { -+ -+ @NotNull -+ NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures); -+ -+} -\ No newline at end of file -diff --git a/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c0527323c42acf7e4728237e268f075e85d71a15 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java -@@ -0,0 +1,17 @@ -+package org.dreeam.leaf.async.path; -+ -+import net.minecraft.world.level.pathfinder.*; -+ -+public enum NodeEvaluatorType { -+ WALK, -+ SWIM, -+ AMPHIBIOUS, -+ FLY; -+ -+ public static NodeEvaluatorType fromNodeEvaluator(NodeEvaluator nodeEvaluator) { -+ if (nodeEvaluator instanceof SwimNodeEvaluator) return SWIM; -+ if (nodeEvaluator instanceof FlyNodeEvaluator) return FLY; -+ if (nodeEvaluator instanceof AmphibiousNodeEvaluator) return AMPHIBIOUS; -+ return WALK; -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java b/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java -new file mode 100644 -index 0000000000000000000000000000000000000000..73f30b733fb93f5cfbf9e14800b055b9053f6383 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/path/PathProcessState.java -@@ -0,0 +1,7 @@ -+package org.dreeam.leaf.async.path; -+ -+public enum PathProcessState { -+ WAITING, -+ PROCESSING, -+ COMPLETED, -+} -diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b45d738fb982b0a245ee5fda8c4abd0df0441f74 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java -@@ -0,0 +1,32 @@ -+package org.dreeam.leaf.config.modules.async; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.LeafConfig; -+ -+public class AsyncPathfinding extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-pathfinding"; -+ } -+ -+ public static boolean enabled = false; -+ public static int asyncPathfindingMaxThreads = 0; -+ public static int asyncPathfindingKeepalive = 60; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled); -+ asyncPathfindingMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncPathfindingMaxThreads); -+ asyncPathfindingKeepalive = config.getInt(getBasePath() + ".keepalive", asyncPathfindingKeepalive); -+ -+ if (asyncPathfindingMaxThreads < 0) -+ asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathfindingMaxThreads, 1); -+ else if (asyncPathfindingMaxThreads == 0) -+ asyncPathfindingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); -+ if (!enabled) -+ asyncPathfindingMaxThreads = 0; -+ else -+ LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", asyncPathfindingMaxThreads); -+ } -+} diff --git a/patches/server/0043-Faster-sequencing-of-futures-for-chunk-structure-gen.patch b/patches/server/0043-Faster-sequencing-of-futures-for-chunk-structure-gen.patch deleted file mode 100644 index ea1a223f..00000000 --- a/patches/server/0043-Faster-sequencing-of-futures-for-chunk-structure-gen.patch +++ /dev/null @@ -1,110 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Tue, 2 Jan 2024 21:13:53 -0500 -Subject: [PATCH] Faster sequencing of futures for chunk structure gen - -Replace `thenApply` with `thenCompose`. Once one task is completed then the next task starts immediately, -to prevent blocking threads while waiting to complete all tasks. But may cause the sequence of future compose disorder. - -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 1360aa8202542d3d0f32247f1123575fc2c38ff1..8701e64aa7287093f8cac3921e0189f94c62cae9 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -586,17 +586,44 @@ public class Util { - return enumMap; - } - -+ // Leaf start - Faster sequencing of futures for chunk structure gen - public static CompletableFuture> sequence(List> futures) { -+ return sequence(futures, false); -+ } -+ public static CompletableFuture> sequence(List> futures, boolean useFaster) { -+ // Leaf end - Faster sequencing of futures for chunk structure gen - if (futures.isEmpty()) { - return CompletableFuture.completedFuture(List.of()); - } else if (futures.size() == 1) { - return futures.get(0).thenApply(List::of); - } else { - CompletableFuture completableFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); -+ // Leaf start - Faster sequencing of futures for chunk structure gen -+ if (org.dreeam.leaf.config.modules.opt.FasterStructureGenFutureSequencing.enabled && useFaster) { -+ return sequenceFaster(futures, completableFuture); -+ } -+ // Leaf end - Faster sequencing of futures for chunk structure gen -+ - return completableFuture.thenApply(void_ -> futures.stream().map(CompletableFuture::join).toList()); - } - } - -+ // Leaf start - Faster sequencing of futures for chunk structure gen -+ private static CompletableFuture> sequenceFaster(List> futures, CompletableFuture completableFuture) { -+ return completableFuture.thenCompose(void_ -> -+ CompletableFuture.supplyAsync(() -> { -+ List list = new java.util.ArrayList<>(); -+ -+ for (CompletableFuture future : futures) { -+ list.add(future.join()); -+ } -+ -+ return list; -+ } -+ )); -+ } -+ // Leaf end - Faster sequencing of futures for chunk structure gen -+ - public static CompletableFuture> sequenceFailFast(List> futures) { - CompletableFuture> completableFuture = new CompletableFuture<>(); - return fallibleSequence(futures, completableFuture::completeExceptionally).applyToEither(completableFuture, Function.identity()); -diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java -index ea1cbd7a3897ea4a86877a557da264387ef78a38..d551d9c00ef0eec405f78c9caa482d3bbaf23538 100644 ---- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java -+++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java -@@ -54,7 +54,7 @@ public class ReloadableServerRegistries { - List>> list2 = LootDataType.values() - .map(type -> scheduleRegistryLoad((LootDataType)type, registryOps, resourceManager, prepareExecutor, conversions)) // Paper - .toList(); -- CompletableFuture>> completableFuture = Util.sequence(list2); -+ CompletableFuture>> completableFuture = Util.sequence(list2, false); // Leaf - Faster sequencing of futures for chunk structure gen - return completableFuture.thenApplyAsync( - registries -> createAndValidateFullContext(dynamicRegistries, provider, (List>)registries), prepareExecutor - ); -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -index a20520a6bd28bae1cee82258ac49d9753faba2bd..b4d9e43b524b9cc6da4bb50d28ce1e854f5abcce 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -@@ -270,7 +270,7 @@ public class ChunkGeneratorStructureState { - } - } - -- return Util.sequence(list).thenApply((list1) -> { -+ return Util.sequence(list, true).thenApply((list1) -> { // Leaf - Faster sequencing of futures for chunk structure gen - double d2 = (double) stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) / 1000.0D; - - ChunkGeneratorStructureState.LOGGER.debug("Calculation for {} took {}s", structureSetEntry, d2); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java b/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java -new file mode 100644 -index 0000000000000000000000000000000000000000..97a730047491c4ada75963185bb8b2e6f56c56a2 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class FasterStructureGenFutureSequencing extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".faster-structure-gen-future-sequencing", enabled, -+ config.pickStringRegionBased( -+ "May cause the inconsistent order of future compose tasks.", -+ "更快的结构生成任务分段.")); -+ } -+} diff --git a/patches/server/0044-Reduce-items-finding-hopper-nearby-check.patch b/patches/server/0044-Reduce-items-finding-hopper-nearby-check.patch deleted file mode 100644 index e69451df..00000000 --- a/patches/server/0044-Reduce-items-finding-hopper-nearby-check.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Mon, 15 Jan 2024 10:53:10 -0500 -Subject: [PATCH] Reduce items finding hopper nearby check - -This patch add a toggle for items checking MinecraftHopper nearby, - -But still recommend to turn-off `checkForMinecartNearItemWhileActive` -Since `Reduce-hopper-item-checks.patch` will cause lag under massive dropped items - -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 186fb46e491bcf5d2a85e68ecf8e13b87381875c..8eed7d70d5716f6d58c46b31a526b5de2a891f16 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -242,7 +242,9 @@ public class ItemEntity extends Entity implements TraceableEntity { - this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause - return; // Gale - EMC - reduce hopper item checks - } -- this.markNearbyHopperCartsAsImmune(); // Gale - EMC - reduce hopper item checks -+ if (level().galeConfig().smallOptimizations.reducedIntervals.checkNearbyItem.hopper.minecart.temporaryImmunity.checkForMinecartNearItemWhileActive) { // Leaf - Reduce items finding hopper nearby check -+ this.markNearbyHopperCartsAsImmune(); // Gale - EMC - reduce hopper item checks -+ } - - } - } -diff --git a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -index 838d15a8c81f168b6d94adb602a996123313aaea..bbc47a0d6038c4339e1c29d58bd6a9f744e896a9 100644 ---- a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -+++ b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -@@ -75,10 +75,11 @@ public class GaleWorldConfiguration extends ConfigurationPart { - - public TemporaryImmunity temporaryImmunity; - public class TemporaryImmunity extends ConfigurationPart { -+ public boolean checkForMinecartNearItemWhileActive = false; // Leaf - Make it configurable and reorder code -+ public boolean checkForMinecartNearItemWhileInactive = true; - public int duration = 100; - public int nearbyItemMaxAge = 1200; - public int checkForMinecartNearItemInterval = 20; -- public boolean checkForMinecartNearItemWhileInactive = true; - public double maxItemHorizontalDistance = 24.0; - public double maxItemVerticalDistance = 4.0; - } diff --git a/patches/server/0045-Linear-region-file-format.patch b/patches/server/0045-Linear-region-file-format.patch deleted file mode 100644 index a622e267..00000000 --- a/patches/server/0045-Linear-region-file-format.patch +++ /dev/null @@ -1,923 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Sun, 30 Jun 2024 00:35:19 +0800 -Subject: [PATCH] Linear region file format - -Original license: MIT -Original project: https://github.com/LuminolMC/Luminol - -Linear is a region file format that uses ZSTD compression instead of -ZLIB. -This format saves about 50% of disk space. -Documentation: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools - -diff --git a/build.gradle.kts b/build.gradle.kts -index 635ec3ef8bd96f73527ee50ca87c8f2e6b8232b2..92fc2ab1fd20d31d1c5476f8f2349f0f64fbec9d 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -33,6 +33,11 @@ dependencies { - } - // Leaf end - Leaf Config - -+ // LinearPaper start -+ implementation("com.github.luben:zstd-jni:1.5.6-9") -+ implementation("org.lz4:lz4-java:1.8.0") -+ // LinearPaper end -+ - // Paper start - // Leaf start - Bump Dependencies - implementation("org.jline:jline-terminal-ffm:3.28.0") // use ffm on java 22+ // Leaf Bu -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -index a814512fcfb85312474ae2c2c21443843bf57831..f80c75c561313625b694b433692aa429b8f8fde9 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -@@ -8,9 +8,9 @@ public interface ChunkSystemRegionFileStorage { - - public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ); - -- public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); -+ public org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // LinearPaper - -- public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; -+ public org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // LinearPaper - - public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( - final int chunkX, final int chunkZ, final CompoundTag compound -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java -index 1acea58838f057ab87efd103cbecb6f5aeaef393..48a6d8b534943393c26180fbf341b77bd2d5bc48 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java -@@ -1462,7 +1462,7 @@ public final class MoonriseRegionFileIO { - - public static interface IORunnable { - -- public void run(final RegionFile regionFile) throws IOException; -+ public void run(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException; // LinearPaper - - } - } -diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java -index 51c126735ace8fdde89ad97b5cab62f244212db0..4046f0aaa153e00277bf14f009fbe14aa8859fec 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java -@@ -8,5 +8,5 @@ public interface ChunkSystemChunkBuffer { - - public void moonrise$setWriteOnClose(final boolean value); - -- public void moonrise$write(final RegionFile regionFile) throws IOException; -+ public void moonrise$write(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException; // LinearPaper - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2d231c01fe752d3a97965a0bef01e7c0f6cb1611..87e52d6dcd855c80b3e6b4f4010b596bf8649c80 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -985,10 +985,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> progressMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap()); - volatile Component status = Component.translatable("optimizeWorld.stage.counting"); -- static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); -+ static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.(linear | mca)$"); // LinearPaper - final DimensionDataStorage overworldDataStorage; - - public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, RegistryAccess dynamicRegistryManager, boolean eraseCache, boolean recreateRegionFiles) { -@@ -399,7 +399,7 @@ public class WorldUpgrader implements AutoCloseable { - - private static List getAllChunkPositions(RegionStorageInfo key, Path regionDirectory) { - File[] afile = regionDirectory.toFile().listFiles((file, s) -> { -- return s.endsWith(".mca"); -+ return s.endsWith(".linear") || s.endsWith(".mca"); // LinearPaper - }); - - if (afile == null) { -@@ -419,7 +419,7 @@ public class WorldUpgrader implements AutoCloseable { - List list1 = Lists.newArrayList(); - - try { -- RegionFile regionfile = new RegionFile(key, file.toPath(), regionDirectory, true); -+ org.stupidcraft.linearpaper.region.IRegionFile regionfile = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(key, file.toPath(), regionDirectory, true); // LinearPaper - - try { - for (int i1 = 0; i1 < 32; ++i1) { -@@ -482,7 +482,7 @@ public class WorldUpgrader implements AutoCloseable { - - protected abstract boolean tryProcessOnePosition(T storage, ChunkPos chunkPos, ResourceKey worldKey); - -- private void onFileFinished(RegionFile regionFile) { -+ private void onFileFinished(org.stupidcraft.linearpaper.region.IRegionFile regionFile) { // LinearPaper - if (WorldUpgrader.this.recreateRegionFiles) { - if (this.previousWriteFuture != null) { - this.previousWriteFuture.join(); -@@ -507,7 +507,7 @@ public class WorldUpgrader implements AutoCloseable { - } - } - -- static record FileToUpgrade(RegionFile file, List chunksToUpgrade) { -+ static record FileToUpgrade(org.stupidcraft.linearpaper.region.IRegionFile file, List chunksToUpgrade) { // LinearPaper - - } - -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 b26a5b76cd30c96ce15ced5ac51222a8333bde52..dd42303de58274c24394da26bda52bf0d09df7b2 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 -@@ -28,7 +28,7 @@ import net.minecraft.nbt.NbtIo; // Paper - import net.minecraft.world.level.ChunkPos; - import org.slf4j.Logger; - --public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system -+public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile, org.stupidcraft.linearpaper.region.IRegionFile { // Paper - rewrite chunk system // LinearPaper - - private static final Logger LOGGER = LogUtils.getLogger(); - private static final int SECTOR_BYTES = 4096; -@@ -129,7 +129,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - } - - // note: only call for CHUNK regionfiles -- boolean recalculateHeader() throws IOException { -+ public boolean recalculateHeader() throws IOException { // LinearPaper - make public - if (!this.canRecalcHeader) { - return false; - } -@@ -810,7 +810,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - } - } - -- protected synchronized void write(ChunkPos pos, ByteBuffer buf) throws IOException { -+ public synchronized void write(ChunkPos pos, ByteBuffer buf) throws IOException { // LinearPaper - protected -> public - int i = RegionFile.getOffsetIndex(pos); - int j = this.offsets.get(i); - int k = RegionFile.getSectorNumber(j); -@@ -952,10 +952,10 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - private static int getChunkIndex(int x, int z) { - return (x & 31) + (z & 31) * 32; - } -- synchronized boolean isOversized(int x, int z) { -+ public synchronized boolean isOversized(int x, int z) { // LinearPaper - make public - return this.oversized[getChunkIndex(x, z)] == 1; - } -- synchronized void setOversized(int x, int z, boolean oversized) throws IOException { -+ public synchronized void setOversized(int x, int z, boolean oversized) throws IOException { // LinearPaper - make public - final int offset = getChunkIndex(x, z); - boolean previous = this.oversized[offset] == 1; - this.oversized[offset] = (byte) (oversized ? 1 : 0); -@@ -994,7 +994,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt"); - } - -- synchronized CompoundTag getOversizedData(int x, int z) throws IOException { -+ public synchronized CompoundTag getOversizedData(int x, int z) throws IOException { // LinearPaper - make public - Path file = getOversizedFile(x, z); - try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) { - return NbtIo.read((java.io.DataInput) out); -@@ -1021,7 +1021,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - } - - @Override -- public final void moonrise$write(final RegionFile regionFile) throws IOException { -+ public final void moonrise$write(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException { // LinearPaper - regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count)); - } - // Paper end - rewrite chunk system -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 79aca7e7cd2f860464657e77e935391642981fad..1ae102bcf2dfd72ec36703aecd8f0deff132bcca 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 -@@ -23,7 +23,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - public static final String ANVIL_EXTENSION = ".mca"; - private static final int MAX_CACHE_SIZE = 256; -- public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); -+ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); // LinearPaper - private final RegionStorageInfo info; - private final Path folder; - private final boolean sync; -@@ -32,7 +32,11 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - private static final int REGION_SHIFT = 5; - private static final int MAX_NON_EXISTING_CACHE = 1024 * 4; - private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); -+ // Leaf start - Linear region format - private static String getRegionFileName(final int chunkX, final int chunkZ) { -+ if (org.dreeam.leaf.config.modules.misc.RegionFormatConfig.regionFormatType == org.stupidcraft.linearpaper.region.EnumRegionFileExtension.LINEAR) { -+ return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".linear"; -+ } - return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca"; - } - -@@ -68,15 +72,15 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - } - - @Override -- public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { -+ public synchronized final org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // LinearPaper - return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT)); - } - - @Override -- public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { -+ public synchronized final org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // LinearPaper - final long key = ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); - -- RegionFile ret = this.regionCache.getAndMoveToFirst(key); -+ org.stupidcraft.linearpaper.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // LinearPaper - if (ret != null) { - return ret; - } -@@ -100,7 +104,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - FileUtil.createDirectoriesSafe(this.folder); - -- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); -+ ret = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // LinearPaper - - this.regionCache.putAndMoveToFirst(key, ret); - -@@ -119,11 +123,11 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - } - - final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -- final RegionFile regionFile = this.getRegionFile(pos); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(pos); // LinearPaper - - // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input - // (and, the regionfile parameter is unused for writing until the write call) -- final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData = ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile)regionFile).moonrise$startWrite(compound, pos); -+ final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData = regionFile.moonrise$startWrite(compound, pos); // LinearPaper - - try { // Paper - implement RegionFileSizeException - try { -@@ -153,7 +157,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - ) throws IOException { - final ChunkPos pos = new ChunkPos(chunkX, chunkZ); - if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { -- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // LinearPaper - if (regionFile != null) { - regionFile.clear(pos); - } // else: didn't exist -@@ -168,7 +172,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( - final int chunkX, final int chunkZ - ) throws IOException { -- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // LinearPaper - - final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); - -@@ -221,7 +225,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - @Nullable - public static ChunkPos getRegionFileCoordinates(Path file) { - String fileName = file.getFileName().toString(); -- if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { -+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca") || !fileName.endsWith(".linear")) { // LinearPaper - return null; - } - -@@ -250,12 +254,12 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - } - - // Paper start - rewrite chunk system -- public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { -+ public org.stupidcraft.linearpaper.region.IRegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { // LinearPaper - return this.getRegionFile(chunkcoordintpair, false); - } - // Paper end - rewrite chunk system - -- public RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public -+ public org.stupidcraft.linearpaper.region.IRegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public // LinearPaper - // Paper start - rewrite chunk system - if (existingOnly) { - return this.moonrise$getRegionFileIfExists(chunkcoordintpair.x, chunkcoordintpair.z); -@@ -263,7 +267,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - synchronized (this) { - final long key = ChunkPos.asLong(chunkcoordintpair.x >> REGION_SHIFT, chunkcoordintpair.z >> REGION_SHIFT); - -- RegionFile ret = this.regionCache.getAndMoveToFirst(key); -+ org.stupidcraft.linearpaper.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // LinearPaper - if (ret != null) { - return ret; - } -@@ -272,13 +276,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - this.regionCache.removeLast().close(); - } - -- final Path regionPath = this.folder.resolve(getRegionFileName(chunkcoordintpair.x, chunkcoordintpair.z)); -+ final Path regionPath = this.folder.resolve(getRegionFileName(chunkcoordintpair.x, chunkcoordintpair.z)); // LinearPaper - - this.createRegionFile(key); - - FileUtil.createDirectoriesSafe(this.folder); - -- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); -+ ret = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // LinearPaper - - this.regionCache.putAndMoveToFirst(key, ret); - -@@ -298,7 +302,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - // Gale end - branding changes - } - -- private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { -+ private static CompoundTag readOversizedChunk(org.stupidcraft.linearpaper.region.IRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // LinearPaper - synchronized (regionfile) { - try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) { - CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); -@@ -333,7 +337,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - @Nullable - public CompoundTag read(ChunkPos pos) throws IOException { - // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing -- RegionFile regionfile = this.getRegionFile(pos, true); -+ org.stupidcraft.linearpaper.region.IRegionFile regionfile = this.getRegionFile(pos, true); // LinearPaper - if (regionfile == null) { - return null; - } -@@ -397,7 +401,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { - // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing -- RegionFile regionfile = this.getRegionFile(chunkPos, true); -+ org.stupidcraft.linearpaper.region.IRegionFile regionfile = this.getRegionFile(chunkPos, true); // LinearPaper - if (regionfile == null) { - return; - } -@@ -427,7 +431,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - } - - public void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { // Paper - rewrite chunk system - public -- RegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system -+ org.stupidcraft.linearpaper.region.IRegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system // LinearPaper - // Paper start - rewrite chunk system - if (regionfile == null) { - // if the RegionFile doesn't exist, no point in deleting from it -@@ -471,7 +475,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - // Paper start - rewrite chunk system - synchronized (this) { - final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); -- for (final RegionFile regionFile : this.regionCache.values()) { -+ for (final org.stupidcraft.linearpaper.region.IRegionFile regionFile : this.regionCache.values()) { // LinearPaper - try { - regionFile.close(); - } catch (final IOException ex) { -@@ -488,7 +492,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - // Paper start - rewrite chunk system - synchronized (this) { - final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); -- for (final RegionFile regionFile : this.regionCache.values()) { -+ for (final org.stupidcraft.linearpaper.region.IRegionFile regionFile : this.regionCache.values()) { // LinearPaper - try { - regionFile.flush(); - } catch (final IOException ex) { -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java b/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d94f8dd4a8682125ea356d36632e2b68e73d8af3 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java -@@ -0,0 +1,62 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import com.mojang.logging.LogUtils; -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.annotations.DoNotLoad; -+import org.slf4j.Logger; -+import org.stupidcraft.linearpaper.region.EnumRegionFileExtension; -+ -+public class RegionFormatConfig extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".region-format-settings"; -+ } -+ -+ @DoNotLoad -+ private static final Logger logger = LogUtils.getLogger(); -+ @DoNotLoad -+ public static EnumRegionFileExtension regionFormatType; -+ -+ public static String regionFormatTypeName = "MCA"; -+ public static int linearCompressionLevel = 1; -+ public static boolean throwOnUnknownExtension = false; -+ public static int linearFlushFrequency = 5; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Linear is a region file format that uses ZSTD compression instead of -+ ZLIB. -+ This format saves about 50% of disk space. -+ Read Documentation before using: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools -+ Disclaimer: This is an experimental feature, there is potential risk to lose chunk data. -+ So backup your server before switching to Linear.""", -+ """ -+ Linear 是一种使用 ZSTD 压缩而非 ZLIB 的区域文件格式. -+ 该格式可节省约 50% 的磁盘空间. -+ 使用前请阅读文档: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools -+ 免责声明: 实验性功能,有可能导致区块数据丢失. -+ 切换到Linear前请备份服务器."""); -+ -+ regionFormatTypeName = config.getString(getBasePath() + ".region-format", regionFormatTypeName, -+ config.pickStringRegionBased( -+ "Available region formats: MCA, LINEAR", -+ "可用格式: MCA, LINEAR")); -+ linearCompressionLevel = config.getInt(getBasePath() + ".linear-compress-level", linearCompressionLevel); -+ throwOnUnknownExtension = config().getBoolean(getBasePath() + ".throw-on-unknown-extension-detected", throwOnUnknownExtension); -+ linearFlushFrequency = config.getInt(getBasePath() + ".flush-interval-seconds", linearFlushFrequency); -+ -+ regionFormatType = EnumRegionFileExtension.fromName(regionFormatTypeName); -+ if (regionFormatType == EnumRegionFileExtension.UNKNOWN) { -+ logger.error("Unknown region file type {} ! Falling back to MCA format.", regionFormatTypeName); -+ regionFormatType = EnumRegionFileExtension.MCA; -+ } -+ -+ if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { -+ logger.error("Linear region compression level should be between 1 and 22 in config: {}", linearCompressionLevel); -+ logger.error("Falling back to compression level 1."); -+ linearCompressionLevel = 1; -+ } -+ } -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java b/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8eba172be9bce7cccd27d27bee2d6c6f917a9e4e ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java -@@ -0,0 +1,56 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Locale; -+ -+public enum EnumRegionFileExtension { -+ LINEAR(".linear"), -+ MCA(".mca"), -+ UNKNOWN(null); -+ -+ private final String extensionName; -+ -+ EnumRegionFileExtension(String extensionName) { -+ this.extensionName = extensionName; -+ } -+ -+ public String getExtensionName() { -+ return this.extensionName; -+ } -+ -+ @Contract(pure = true) -+ public static EnumRegionFileExtension fromName(@NotNull String name) { -+ switch (name.toUpperCase(Locale.ROOT)) { -+ default -> { -+ return UNKNOWN; -+ } -+ -+ case "MCA" -> { -+ return MCA; -+ } -+ -+ case "LINEAR" -> { -+ return LINEAR; -+ } -+ } -+ } -+ -+ @Contract(pure = true) -+ public static EnumRegionFileExtension fromExtension(@NotNull String name) { -+ switch (name.toLowerCase()) { -+ case "mca" -> { -+ return MCA; -+ } -+ -+ case "linear" -> { -+ return LINEAR; -+ } -+ -+ default -> { -+ return UNKNOWN; -+ } -+ } -+ } -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java b/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6a9b8178cca07335fbe8244fd1859b241ae35034 ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java -@@ -0,0 +1,29 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import java.io.DataInputStream; -+import java.io.DataOutputStream; -+import java.io.IOException; -+import java.nio.ByteBuffer; -+import java.nio.file.Path; -+ -+import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.level.ChunkPos; -+ -+public interface IRegionFile extends AutoCloseable, ChunkSystemRegionFile { -+ Path getPath(); -+ void flush() throws IOException; -+ void clear(ChunkPos pos) throws IOException; -+ void close() throws IOException; -+ void setOversized(int x, int z, boolean b) throws IOException; -+ void write(ChunkPos pos, ByteBuffer buffer) throws IOException; -+ -+ boolean hasChunk(ChunkPos pos); -+ boolean doesChunkExist(ChunkPos pos) throws Exception; -+ boolean isOversized(int x, int z); -+ boolean recalculateHeader() throws IOException; -+ -+ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException; -+ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException; -+ CompoundTag getOversizedData(int x, int z) throws IOException; -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java b/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..70552d63a84a4d3a73348d0dffacd89ca8f5df0f ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java -@@ -0,0 +1,52 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import java.io.IOException; -+import java.nio.file.Path; -+ -+import net.minecraft.world.level.chunk.storage.RegionFile; -+import net.minecraft.world.level.chunk.storage.RegionFileVersion; -+import net.minecraft.world.level.chunk.storage.RegionStorageInfo; -+import org.dreeam.leaf.config.modules.misc.RegionFormatConfig; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+public class IRegionFileFactory { -+ @Contract("_, _, _, _ -> new") -+ public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { -+ return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); -+ } -+ -+ @Contract("_, _, _, _, _ -> new") -+ public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, boolean canRecalcHeader) throws IOException { -+ return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); -+ } -+ -+ @Contract("_, _, _, _, _ -> new") -+ public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { -+ return getAbstractRegionFile(storageKey, path, directory, compressionFormat, dsync, true); -+ } -+ -+ @Contract("_, _, _, _, _, _ -> new") -+ public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, @NotNull Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, boolean canRecalcHeader) throws IOException { -+ final String fullFileName = path.getFileName().toString(); -+ final String[] fullNameSplit = fullFileName.split("\\."); -+ final String extensionName = fullNameSplit[fullNameSplit.length - 1]; -+ switch (EnumRegionFileExtension.fromExtension(extensionName)) { -+ case UNKNOWN -> { -+ if (RegionFormatConfig.throwOnUnknownExtension) { -+ throw new IllegalArgumentException("Unknown region file extension for file: " + fullFileName + "!"); -+ } -+ -+ return new RegionFile(storageKey, path, directory, compressionFormat, dsync); -+ } -+ -+ case LINEAR -> { -+ return new LinearRegionFile(path, RegionFormatConfig.linearCompressionLevel); -+ } -+ -+ default -> { -+ return new RegionFile(storageKey, path, directory, compressionFormat, dsync); -+ } -+ } -+ } -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ea4b9227da76143a6b1aef7924ab634899c81120 ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java -@@ -0,0 +1,309 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; -+import com.github.luben.zstd.ZstdInputStream; -+import com.github.luben.zstd.ZstdOutputStream; -+import com.mojang.logging.LogUtils; -+ -+import java.io.BufferedOutputStream; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.DataInputStream; -+import java.io.DataOutputStream; -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.FileOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.nio.ByteBuffer; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.nio.file.StandardCopyOption; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.concurrent.TimeUnit; -+import javax.annotation.Nullable; -+ -+import net.jpountz.lz4.LZ4Compressor; -+import net.jpountz.lz4.LZ4Factory; -+import net.jpountz.lz4.LZ4FastDecompressor; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.level.ChunkPos; -+import org.dreeam.leaf.config.modules.misc.RegionFormatConfig; -+import org.slf4j.Logger; -+ -+public class LinearRegionFile implements IRegionFile, AutoCloseable { -+ private static final long SUPERBLOCK = -4323716122432332390L; -+ private static final byte VERSION = 2; -+ private static final int HEADER_SIZE = 32; -+ private static final int FOOTER_SIZE = 8; -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ private static final List SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2); -+ private final byte[][] buffer = new byte[1024][]; -+ private final int[] bufferUncompressedSize = new int[1024]; -+ private final int[] chunkTimestamps = new int[1024]; -+ private final LZ4Compressor compressor; -+ private final LZ4FastDecompressor decompressor; -+ private final int compressionLevel; -+ public boolean closed = false; -+ public Path path; -+ private volatile long lastFlushed = System.nanoTime(); -+ -+ public LinearRegionFile(Path file, int compression) throws IOException { -+ this.path = file; -+ this.compressionLevel = compression; -+ this.compressor = LZ4Factory.fastestInstance().fastCompressor(); -+ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); -+ -+ File regionFile = new File(this.path.toString()); -+ -+ Arrays.fill(this.bufferUncompressedSize, 0); -+ -+ if (!regionFile.canRead()) return; -+ -+ try (FileInputStream fileStream = new FileInputStream(regionFile); -+ DataInputStream rawDataStream = new DataInputStream(fileStream)) { -+ -+ long superBlock = rawDataStream.readLong(); -+ if (superBlock != SUPERBLOCK) -+ throw new RuntimeException("Invalid superblock: " + superBlock + " in " + file); -+ -+ byte version = rawDataStream.readByte(); -+ if (!SUPPORTED_VERSIONS.contains(version)) -+ throw new RuntimeException("Invalid version: " + version + " in " + file); -+ -+ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused. -+ rawDataStream.skipBytes(11); -+ -+ int dataCount = rawDataStream.readInt(); -+ long fileLength = file.toFile().length(); -+ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) -+ throw new IOException("Invalid file length: " + this.path + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); -+ -+ rawDataStream.skipBytes(8); // Skip data hash (Long): Unused. -+ -+ byte[] rawCompressed = new byte[dataCount]; -+ rawDataStream.readFully(rawCompressed, 0, dataCount); -+ -+ superBlock = rawDataStream.readLong(); -+ if (superBlock != SUPERBLOCK) -+ throw new IOException("Footer superblock invalid " + this.path); -+ -+ try (DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) { -+ -+ int[] starts = new int[1024]; -+ for (int i = 0; i < 1024; i++) { -+ starts[i] = dataStream.readInt(); -+ dataStream.skipBytes(4); // Skip timestamps (Int): Unused. -+ } -+ -+ for (int i = 0; i < 1024; i++) { -+ if (starts[i] > 0) { -+ int size = starts[i]; -+ byte[] b = new byte[size]; -+ dataStream.readFully(b, 0, size); -+ -+ int maxCompressedLength = this.compressor.maxCompressedLength(size); -+ byte[] compressed = new byte[maxCompressedLength]; -+ int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength); -+ b = new byte[compressedLength]; -+ System.arraycopy(compressed, 0, b, 0, compressedLength); -+ -+ this.buffer[i] = b; -+ this.bufferUncompressedSize[i] = size; -+ } -+ } -+ } -+ } -+ } -+ -+ private static int getChunkIndex(int x, int z) { -+ return (x & 31) + ((z & 31) << 5); -+ } -+ -+ private static int getTimestamp() { -+ return (int) (System.currentTimeMillis() / 1000L); -+ } -+ -+ public void flush() throws IOException { -+ flushWrapper(); // sync -+ } -+ -+ public void flushWrapper() { -+ try { -+ save(); -+ } catch (IOException e) { -+ LOGGER.error("Failed to flush region file {}", path.toAbsolutePath(), e); -+ } -+ } -+ -+ public boolean doesChunkExist(ChunkPos pos) throws Exception { -+ throw new Exception("doesChunkExist is a stub"); -+ } -+ -+ private synchronized void save() throws IOException { -+ long timestamp = getTimestamp(); -+ short chunkCount = 0; -+ -+ File tempFile = new File(path.toString() + ".tmp"); -+ -+ try (FileOutputStream fileStream = new FileOutputStream(tempFile); -+ ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream(); -+ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel); -+ DataOutputStream zstdDataStream = new DataOutputStream(zstdStream); -+ DataOutputStream dataStream = new DataOutputStream(fileStream)) { -+ -+ dataStream.writeLong(SUPERBLOCK); -+ dataStream.writeByte(VERSION); -+ dataStream.writeLong(timestamp); -+ dataStream.writeByte(this.compressionLevel); -+ -+ ArrayList byteBuffers = new ArrayList<>(); -+ for (int i = 0; i < 1024; i++) { -+ if (this.bufferUncompressedSize[i] != 0) { -+ chunkCount += 1; -+ byte[] content = new byte[bufferUncompressedSize[i]]; -+ this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]); -+ -+ byteBuffers.add(content); -+ } else byteBuffers.add(null); -+ } -+ for (int i = 0; i < 1024; i++) { -+ zstdDataStream.writeInt(this.bufferUncompressedSize[i]); // Write uncompressed size -+ zstdDataStream.writeInt(this.chunkTimestamps[i]); // Write timestamp -+ } -+ for (int i = 0; i < 1024; i++) { -+ if (byteBuffers.get(i) != null) -+ zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length); -+ } -+ zstdDataStream.close(); -+ -+ dataStream.writeShort(chunkCount); -+ -+ byte[] compressed = zstdByteArray.toByteArray(); -+ -+ dataStream.writeInt(compressed.length); -+ dataStream.writeLong(0); -+ -+ dataStream.write(compressed, 0, compressed.length); -+ dataStream.writeLong(SUPERBLOCK); -+ -+ dataStream.flush(); -+ fileStream.getFD().sync(); -+ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs -+ } -+ Files.move(tempFile.toPath(), this.path, StandardCopyOption.REPLACE_EXISTING); -+ this.lastFlushed = System.nanoTime(); -+ } -+ -+ public synchronized void write(ChunkPos pos, ByteBuffer buffer) { -+ try { -+ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array())); -+ int uncompressedSize = b.length; -+ -+ int maxCompressedLength = this.compressor.maxCompressedLength(b.length); -+ byte[] compressed = new byte[maxCompressedLength]; -+ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength); -+ b = new byte[compressedLength]; -+ System.arraycopy(compressed, 0, b, 0, compressedLength); -+ -+ int index = getChunkIndex(pos.x, pos.z); -+ this.buffer[index] = b; -+ this.chunkTimestamps[index] = getTimestamp(); -+ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; -+ } catch (IOException e) { -+ LOGGER.error("Chunk write IOException {} {}", e, this.path); -+ } -+ -+ if ((System.nanoTime() - this.lastFlushed) >= TimeUnit.NANOSECONDS.toSeconds(RegionFormatConfig.linearFlushFrequency)) { -+ this.flushWrapper(); -+ } -+ } -+ -+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { -+ return new DataOutputStream(new BufferedOutputStream(new ChunkBuffer(pos))); -+ } -+ -+ @Override -+ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) throws IOException { -+ final DataOutputStream out = this.getChunkDataOutputStream(pos); -+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( -+ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, -+ out, regionFile -> out.close() -+ ); -+ } -+ -+ private byte[] toByteArray(InputStream in) throws IOException { -+ ByteArrayOutputStream out = new ByteArrayOutputStream(); -+ byte[] tempBuffer = new byte[4096]; -+ -+ int length; -+ while ((length = in.read(tempBuffer)) >= 0) { -+ out.write(tempBuffer, 0, length); -+ } -+ -+ return out.toByteArray(); -+ } -+ -+ @Nullable -+ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { -+ if (this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { -+ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]]; -+ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]); -+ return new DataInputStream(new ByteArrayInputStream(content)); -+ } -+ return null; -+ } -+ -+ public void clear(ChunkPos pos) { -+ int i = getChunkIndex(pos.x, pos.z); -+ this.buffer[i] = null; -+ this.bufferUncompressedSize[i] = 0; -+ this.chunkTimestamps[i] = getTimestamp(); -+ this.flushWrapper(); -+ } -+ -+ public Path getPath() { -+ return this.path; -+ } -+ -+ public boolean hasChunk(ChunkPos pos) { -+ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0; -+ } -+ -+ public void close() throws IOException { -+ if (closed) return; -+ closed = true; -+ flush(); // sync -+ } -+ -+ public boolean recalculateHeader() { -+ return false; -+ } -+ -+ public void setOversized(int x, int z, boolean something) { -+ } -+ -+ public CompoundTag getOversizedData(int x, int z) throws IOException { -+ throw new IOException("getOversizedData is a stub " + this.path); -+ } -+ -+ public boolean isOversized(int x, int z) { -+ return false; -+ } -+ -+ private class ChunkBuffer extends ByteArrayOutputStream { -+ private final ChunkPos pos; -+ -+ public ChunkBuffer(ChunkPos chunkcoordintpair) { -+ super(); -+ this.pos = chunkcoordintpair; -+ } -+ -+ public void close() { -+ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); -+ LinearRegionFile.this.write(this.pos, bytebuffer); -+ } -+ } -+} diff --git a/patches/server/0046-Plazma-Add-some-missing-Pufferfish-configurations.patch b/patches/server/0046-Plazma-Add-some-missing-Pufferfish-configurations.patch deleted file mode 100644 index 8c61e94d..00000000 --- a/patches/server/0046-Plazma-Add-some-missing-Pufferfish-configurations.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AlphaKR93 -Date: Wed, 27 Sep 2023 18:29:51 +0900 -Subject: [PATCH] Plazma: Add some missing Pufferfish configurations - -Original license: MIT -Original project: https://github.com/PlazmaMC/PlazmaBukkit - -Add Pufferfish DAB support for Camel, Sniffer -https://github.com/pufferfish-gg/Pufferfish/issues/83 - -diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -index 8e8345d9062b1e666cd1b987c44175741b094d07..8046dcd9740c64cc768351f3fd174a836a839e05 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -@@ -159,8 +159,10 @@ public class Armadillo extends Animal { - return ArmadilloAi.makeBrain(this.brainProvider().makeBrain(dynamic)); - } - -+ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations - ((Brain) this.brain).tick(world, this); // CraftBukkit - decompile error - ArmadilloAi.updateActivity(this); - if (this.isAlive() && !this.isBaby() && --this.scuteTime <= 0) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index 542b972b266eae642b220025c6173c738fcff80f..c0595b595905e4f876e013a16e75204d9ceeead9 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -150,10 +150,12 @@ public class Camel extends AbstractHorse { - return pose == Pose.SITTING ? Camel.SITTING_DIMENSIONS.scale(this.getAgeScale()) : super.getDefaultDimensions(pose); - } - -+ private int behaviorTick = 0; // Leaf - Plazma - Add missing Pufferfish configurations - @Override - protected void customServerAiStep(ServerLevel world) { - Brain behaviorcontroller = (Brain) this.getBrain(); // CraftBukkit - decompile error - -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations - behaviorcontroller.tick(world, this); - CamelAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index bd752597c925a68f834939d502a7224e022ae79e..dc0f8bf73170f5012a5d4af3a7e823352d684bd9 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -491,8 +491,10 @@ public class Sniffer extends Animal { - return Brain.provider(SnifferAi.MEMORY_TYPES, SnifferAi.SENSOR_TYPES); - } - -+ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations - this.getBrain().tick(world, this); - SnifferAi.updateActivity(this); - super.customServerAiStep(world); -diff --git a/src/main/java/net/minecraft/world/entity/monster/breeze/Breeze.java b/src/main/java/net/minecraft/world/entity/monster/breeze/Breeze.java -index 2e6b6ef451cd3caa1fde466e5c670b7f17814753..b38245294249086a0dfa15147a424d76b17a5af9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/breeze/Breeze.java -+++ b/src/main/java/net/minecraft/world/entity/monster/breeze/Breeze.java -@@ -232,8 +232,10 @@ public class Breeze extends Monster { - return pos.closerThan(vec3d1, 4.0D, 10.0D); - } - -+ private int behaviorTick; // Leaf - Plazma - Add missing Pufferfish configurations - @Override - protected void customServerAiStep(ServerLevel world) { -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Leaf - Plazma - Add missing Pufferfish configurations - this.getBrain().tick(world, this); - BreezeAi.updateActivity(this); - super.customServerAiStep(world); diff --git a/patches/server/0050-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch b/patches/server/0050-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch deleted file mode 100644 index e7c1fe3a..00000000 --- a/patches/server/0050-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MrPowerGamerBR -Date: Wed, 15 Nov 2023 23:39:36 -0300 -Subject: [PATCH] SparklyPaper: Skip "distanceToSqr" call in - "ServerEntity#sendChanges" if the delta movement hasn't changed - -Original project: https://github.com/SparklyPower/SparklyPaper - -The "distanceToSqr" call is a bit expensive, so avoiding it is pretty nice, around ~15% calls are skipped with this check - -We could also check if the x,y,z coordinates are equal, but for now, let's just keep the identity check, which also helps us since Minecraft's code does reuse the original delta movement Vec3 object - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 84d95b8e718609d06f5a259f22599494ced9cd90..a7a44fa556a41512d6a76626618afceccd139c64 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -245,6 +245,7 @@ public class ServerEntity { - - if ((this.trackDelta || this.entity.hasImpulse || this.entity instanceof LivingEntity && ((LivingEntity) this.entity).isFallFlying()) && this.tickCount > 0) { - Vec3 vec3d1 = this.entity.getDeltaMovement(); -+ if (vec3d1 != this.lastSentMovement) { // SparklyPaper start - skip distanceToSqr call in ServerEntity#sendChanges if the delta movement hasn't changed - double d0 = vec3d1.distanceToSqr(this.lastSentMovement); - - if (d0 > 1.0E-7D || d0 > 0.0D && vec3d1.lengthSqr() == 0.0D) { -@@ -259,6 +260,7 @@ public class ServerEntity { - this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement)); - } - } -+ } // SparklyPaper end - } - - if (packet1 != null) { diff --git a/patches/server/0051-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch b/patches/server/0051-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch deleted file mode 100644 index 60495289..00000000 --- a/patches/server/0051-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MrPowerGamerBR -Date: Fri, 17 Nov 2023 14:22:41 -0300 -Subject: [PATCH] SparklyPaper: Skip "MapItem#update()" if the map does not - have the CraftMapRenderer present - -Original project: https://github.com/SparklyPower/SparklyPaper - -Optimizes "image in map" maps, without requiring the map to be locked, which some old map plugins may not do - -This has the disadvantage that the vanilla map data will never be updated while the CraftMapRenderer is not present, but that's not a huuuge problem for u - -diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index c2f3c8b3d8eeb609b6d6067c4fb404aefbf94ec5..dfec0daeefc040cdae578f44073824773db095f9 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -276,7 +276,7 @@ public class MapItem extends Item { - mapItemSavedData.tickCarriedBy(player, stack); - } - -- if (!mapItemSavedData.locked && (selected || entity instanceof Player && ((Player)entity).getOffhandItem() == stack)) { -+ if (!mapItemSavedData.locked && (!org.dreeam.leaf.config.modules.opt.SkipMapItemDataUpdates.enabled || mapItemSavedData.mapView.getRenderers().stream().anyMatch(mapRenderer -> mapRenderer.getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class)) && (selected || entity instanceof Player && ((Player)entity).getOffhandItem() == stack)) { // SparklyPaper - don't update maps if they don't have the CraftMapRenderer in the render list - this.update(world, entity, mapItemSavedData); - } - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java b/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java -new file mode 100644 -index 0000000000000000000000000000000000000000..61687c05b5996115a40c364fbd013ee72f5de72e ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/SkipMapItemDataUpdates.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class SkipMapItemDataUpdates extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".skip-map-item-data-updates-if-map-does-not-have-craftmaprenderer", enabled); -+ } -+} diff --git a/patches/server/0054-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch b/patches/server/0054-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch deleted file mode 100644 index 7db2f005..00000000 --- a/patches/server/0054-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MrPowerGamerBR -Date: Fri, 23 Aug 2024 16:20:45 -0300 -Subject: [PATCH] SparklyPaper: Allow throttling hopper checks if the target - container is full - -Original project: https://github.com/SparklyPower/SparklyPaper - -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 aa55698303f7ffc6542f34311234be6c6b400a9d..e2fad47c7d6ff1a547404abf6abea84ff8fa867c 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 -@@ -441,6 +441,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - Direction enumdirection = blockEntity.facing.getOpposite(); - - if (HopperBlockEntity.isFullContainer(iinventory, enumdirection)) { -+ // Leaf start - Throttle hopper when full -+ if (org.dreeam.leaf.config.modules.opt.ThrottleHopperWhenFull.enabled && org.dreeam.leaf.config.modules.opt.ThrottleHopperWhenFull.skipTicks > 0) { -+ blockEntity.setCooldown(org.dreeam.leaf.config.modules.opt.ThrottleHopperWhenFull.skipTicks); -+ } -+ // Leaf end - Throttle hopper when full - return false; - } else { - // Paper start - Perf: Optimize Hoppers -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java b/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3facc08ed627a710a1cf26c67abfbfa1b380fe44 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java -@@ -0,0 +1,26 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ThrottleHopperWhenFull extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".throttle-hopper-when-full"; -+ } -+ -+ public static boolean enabled = false; -+ public static int skipTicks = 0; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased(""" -+ Throttles the hopper if target container is full.""", -+ """ -+ 是否在目标容器已满时阻塞漏斗.""")); -+ skipTicks = config.getInt(getBasePath() + ".skip-ticks", skipTicks, config.pickStringRegionBased(""" -+ How many ticks to throttle when the Hopper is throttled.""", -+ """ -+ 每次阻塞多少 tick.""")); -+ } -+} diff --git a/patches/server/0055-Polpot-Make-egg-and-snowball-can-knockback-player.patch b/patches/server/0055-Polpot-Make-egg-and-snowball-can-knockback-player.patch deleted file mode 100644 index 72663875..00000000 --- a/patches/server/0055-Polpot-Make-egg-and-snowball-can-knockback-player.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: lilingfengdev <145678359+lilingfengdev@users.noreply.github.com> -Date: Thu, 18 Jan 2024 13:30:02 +0800 -Subject: [PATCH] Polpot: Make egg and snowball can knockback player - -Original project: https://github.com/HaHaWTH/Polpot - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -index 0db58e7d63a5c1b43a2224c247979f23a1d3f899..c2ffa86e0492ed1aa6a1850e8da49fdca2c2e2c7 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -@@ -61,6 +61,12 @@ public class Snowball extends ThrowableItemProjectile { - int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - - entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float) i); -+ // Leaf start - Polpot - Make snowball can knockback player -+ if (org.dreeam.leaf.config.modules.gameplay.Knockback.snowballCanKnockback && entity instanceof net.minecraft.server.level.ServerPlayer) { -+ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0000001F); -+ ((net.minecraft.server.level.ServerPlayer) entity).knockback(0.4000000059604645D, this.getX() - entity.getX(), this.getZ() - entity.getZ()); -+ } -+ // Leaf end - Polpot - Make snowball can knockback player - } - - // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire -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 155c2bbd35adacb7c3668fbe81a7c454e5102c8b..15de9bf1c39c9cc44debd196725e1f7ebf247e60 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -@@ -51,7 +51,14 @@ public class ThrownEgg extends ThrowableItemProjectile { - @Override - protected void onHitEntity(EntityHitResult entityHitResult) { - super.onHitEntity(entityHitResult); -+ Entity entity = entityHitResult.getEntity(); // Polpot - make egg can knockback player - entityHitResult.getEntity().hurt(this.damageSources().thrown(this, this.getOwner()), 0.0F); -+ // Leaf start - Polpot - Make egg can knockback player -+ if (org.dreeam.leaf.config.modules.gameplay.Knockback.eggCanKnockback && entity instanceof ServerPlayer) { -+ entity.hurt(this.damageSources().thrown(this, this.getOwner()), 0.0000001F); -+ ((ServerPlayer) entity).knockback(0.4000000059604645D, this.getX() - entity.getX(), this.getZ() - entity.getZ()); -+ } -+ // Leaf end - Polpot - Make egg can knockback player - } - - @Override -diff --git a/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java b/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7b434158818834450ea43611e2ab5636917bb938 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java -@@ -0,0 +1,28 @@ -+package org.dreeam.leaf.config.modules.gameplay; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class Knockback extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".knockback"; -+ } -+ -+ public static boolean snowballCanKnockback = false; -+ public static boolean eggCanKnockback = false; -+ -+ @Override -+ public void onLoaded() { -+ snowballCanKnockback = config.getBoolean(getBasePath() + ".snowball-knockback-players", snowballCanKnockback, -+ config.pickStringRegionBased( -+ "Make snowball can knockback players.", -+ "使雪球可以击退玩家." -+ )); -+ eggCanKnockback = config.getBoolean(getBasePath() + ".egg-knockback-players", eggCanKnockback, -+ config.pickStringRegionBased( -+ "Make egg can knockback players.", -+ "使鸡蛋可以击退玩家." -+ )); -+ } -+} diff --git a/patches/server/0057-Including-5s-in-getTPS.patch b/patches/server/0057-Including-5s-in-getTPS.patch deleted file mode 100644 index 26c05ae0..00000000 --- a/patches/server/0057-Including-5s-in-getTPS.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Sat, 24 Feb 2024 01:16:07 -0500 -Subject: [PATCH] Including 5s in getTPS() - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b8a288da456c9cac7fd038f50c68548a0bb7b222..d288be14c647cbcfc45c2b5763762ce3570ed683 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -3156,6 +3156,8 @@ public final class CraftServer implements Server { - - @Override - public double[] getTPS() { -+ if (org.dreeam.leaf.config.modules.misc.Including5sIngetTPS.enabled) return getTPSIncluding5SecondAverage(); // Leaf - Including 5s in getTPS() -+ - return new double[] { - net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), - net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java b/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8a7eb25b407296bb0e4c1fcd33c6f4caa27735ec ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class Including5sIngetTPS extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".including-5s-in-get-tps", enabled); -+ } -+} diff --git a/patches/server/0062-Virtual-thread-for-chat-executor.patch b/patches/server/0062-Virtual-thread-for-chat-executor.patch deleted file mode 100644 index 12ba4154..00000000 --- a/patches/server/0062-Virtual-thread-for-chat-executor.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Fri, 25 Oct 2024 02:27:21 +0800 -Subject: [PATCH] Virtual thread for chat executor - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index ff0e1117cbdfd40aec9d7e54492c107165614d20..244db7e0ae0eb785deb94558eff74714d979d3de 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2941,7 +2941,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Tue, 31 Dec 2024 00:00:00 -0800 -Subject: [PATCH] Virtual thread for User Authenticator - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 0afd30a9a4eb7de7e58e1b91342cca51c771f300..e442fd1b80dd57d4420cab4da69ec3598ffc22d3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -76,7 +76,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - // CraftBukkit end - private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); - static final Logger LOGGER = LogUtils.getLogger(); -- private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads -+ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setThreadFactory(org.dreeam.leaf.config.modules.opt.VT4UserAuthenticator.enabled ? Thread.ofVirtual().factory() : java.util.concurrent.Executors.defaultThreadFactory()).setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads // Leaf - Virtual thread for User Authenticator - private static final int MAX_TICKS_BEFORE_LOGIN = 600; - private final byte[] challenge; - final MinecraftServer server; -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java b/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..751549c6ddacc40b64dc230064f5eeb6b1a2efca ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/VT4UserAuthenticator.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class VT4UserAuthenticator extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-user-authenticator", enabled, -+ config.pickStringRegionBased( -+ "Use the new Virtual Thread introduced in JDK 21 for User Authenticator.", -+ "是否为用户验证器使用虚拟线程.")); -+ } -+} diff --git a/patches/server/0064-Mirai-Configurable-chat-message-signatures.patch b/patches/server/0064-Mirai-Configurable-chat-message-signatures.patch deleted file mode 100644 index 837851fb..00000000 --- a/patches/server/0064-Mirai-Configurable-chat-message-signatures.patch +++ /dev/null @@ -1,204 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: etil2jz <81570777+etil2jz@users.noreply.github.com> -Date: Tue, 2 Aug 2022 14:48:12 +0200 -Subject: [PATCH] Mirai: Configurable chat message signatures - -Fixed & Updated by Dreeam-qwq -Original license: GPLv3 -Original project: https://github.com/Dreeam-qwq/Mirai - -Original license: GPLv3 -Original project: https://github.com/etil2jz/Mirai - -diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -index 506c746980cfca170efd249d035a572361b667c4..5d8833dddc5145868fc2ac673b3387c83cbe6999 100644 ---- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -+++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -@@ -317,7 +317,7 @@ public final class ChatProcessor { - - private void sendToServer(final ChatType.Bound chatType, final @Nullable Function msgFunction) { - final PlayerChatMessage toConsoleMessage = msgFunction == null ? ChatProcessor.this.message : ChatProcessor.this.message.withUnsignedContent(msgFunction.apply(ChatProcessor.this.server.console)); -- ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker || ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure"); // Gale - do not log Not Secure marker -+ ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker || ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure"); // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures - } - } - -diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java -index a523a83aec3a6ecbec4d60a187edc0c0167d15b4..0faefa8203b1a864a46d3e5bbe7a73ccde11d9e1 100644 ---- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java -+++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java -@@ -129,6 +129,16 @@ public class FriendlyByteBuf extends ByteBuf { - // Paper end - Adventure; add max length parameter - DataResult dataresult = codec.encodeStart(JsonOps.INSTANCE, value); - -+ // Leaf start - Configurable chat message signatures -+ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled && codec == net.minecraft.network.protocol.status.ServerStatus.CODEC) { -+ JsonElement element = dataresult.getOrThrow(string -> new EncoderException("Failed to encode: " + string + " " + value)); -+ element.getAsJsonObject().addProperty("preventsChatReports", true); -+ -+ this.writeUtf(GSON.toJson(element)); -+ return; -+ } -+ // Leaf end - Configurable chat message signatures -+ - this.writeUtf(FriendlyByteBuf.GSON.toJson((JsonElement) dataresult.getOrThrow((s) -> { - return new EncoderException("Failed to encode: " + s + " " + String.valueOf(value)); - })), maxLength); // Paper - Adventure; add max length parameter -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLoginPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLoginPacket.java -index 667d1da3b6332737d7382d383bf15b53bd726442..f37ee137a40df4e9d7c0d8c76bb0ddc65f11a7fd 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLoginPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLoginPacket.java -@@ -55,7 +55,7 @@ public record ClientboundLoginPacket( - buf.writeBoolean(this.showDeathScreen); - buf.writeBoolean(this.doLimitedCrafting); - this.commonPlayerSpawnInfo.write(buf); -- buf.writeBoolean(this.enforcesSecureChat); -+ buf.writeBoolean(!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || this.enforcesSecureChat); // Leaf - Mirai - Configurable chat message signatures - } - - @Override -diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java -index 07df3299f1d1aa5506e1f6f146347d53e0278d9c..62d5fb8b89cbcd4e7c1d1d920e12ff36ee5435f3 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java -@@ -23,7 +23,7 @@ public record ServerboundChatPacket(String message, Instant timeStamp, long salt - buf.writeUtf(this.message, 256); - buf.writeInstant(this.timeStamp); - buf.writeLong(this.salt); -- buf.writeNullable(this.signature, MessageSignature::write); -+ if (org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) buf.writeNullable(this.signature, MessageSignature::write); // Leaf - Mirai - Configurable chat message signatures - this.lastSeenMessages.write(buf); - } - -diff --git a/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java b/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java -index 50dc68a005490415b88780397ef6c26859596dd5..162115048cffc824376e54b7f60ae071719afb57 100644 ---- a/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java -+++ b/src/main/java/net/minecraft/network/protocol/status/ServerStatus.java -@@ -22,8 +22,11 @@ public record ServerStatus( - Optional favicon, - boolean enforcesSecureChat - ) { -+ // Leaf start - Mirai - Configurable chat message signatures - public static final Codec CODEC = RecordCodecBuilder.create( -- instance -> instance.group( -+ instance -> -+ org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled -+ ? instance.group( - ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), - ServerStatus.Players.CODEC.lenientOptionalFieldOf("players").forGetter(ServerStatus::players), - ServerStatus.Version.CODEC.lenientOptionalFieldOf("version").forGetter(ServerStatus::version), -@@ -31,7 +34,16 @@ public record ServerStatus( - Codec.BOOL.lenientOptionalFieldOf("enforcesSecureChat", Boolean.valueOf(false)).forGetter(ServerStatus::enforcesSecureChat) - ) - .apply(instance, ServerStatus::new) -+ : instance.group( -+ ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), -+ ServerStatus.Players.CODEC.lenientOptionalFieldOf("players").forGetter(ServerStatus::players), -+ ServerStatus.Version.CODEC.lenientOptionalFieldOf("version").forGetter(ServerStatus::version), -+ ServerStatus.Favicon.CODEC.lenientOptionalFieldOf("favicon").forGetter(ServerStatus::favicon), -+ Codec.BOOL.lenientOptionalFieldOf("enforcesSecureChat", Boolean.FALSE).forGetter(x -> true) -+ ) -+ .apply(instance, ServerStatus::new) - ); -+ // Leaf end- Mirai - Configurable chat message signatures - - public static record Favicon(byte[] iconBytes) { - private static final String PREFIX = "data:image/png;base64,"; -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 940fdcfe0e1bd68891903f33d61d63c2d72fc3df..bff1b99b142bfa980f94794bb4b3d06c197062cd 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -689,6 +689,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - @Override - public boolean enforceSecureProfile() { -+ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) return false; // Leaf - Mirai - Configurable chat message signatures - DedicatedServerProperties dedicatedserverproperties = this.getProperties(); - - // Paper start - Add setting for proxy online mode status -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index c79e9930b090057487b9c83b79935639d885e67b..3ab3e08faacabe96bbaa8271621cff53eaf919ab 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -339,10 +339,29 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - - public void send(Packet packet) { -+ // Leaf start - Mirai - Configurable chat message signatures -+ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) { -+ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { -+ packet = new net.minecraft.network.protocol.game.ClientboundSystemChatPacket( -+ chat.chatType().decorate(chat.unsignedContent() != null ? chat.unsignedContent() : Component.literal(chat.body().content())), false); -+ -+ this.send(packet); -+ return; -+ } -+ } -+ // Leaf end - Mirai - Configurable chat message signatures - this.send(packet, (PacketSendListener) null); - } - - public void send(Packet packet, @Nullable PacketSendListener callbacks) { -+ // Leaf start - Mirai - Configurable chat message signatures -+ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) { -+ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat && callbacks != null) { -+ this.send(chat); -+ return; -+ } -+ } -+ // Leaf end - Mirai - Configurable chat message signatures - // CraftBukkit start - if (packet == null || this.processedDisconnect) { // Spigot - return; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 8efd2183309c285e0a7760922bf1b4766248d5f0..4dafba6135115ff6459c36b5792b6954fef0132b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1608,7 +1608,7 @@ public abstract class PlayerList { - // Paper end - boolean flag = this.verifyChatTrusted(message); - -- this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), params, flag || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker ? null : "Not Secure"); // Paper // Gale - do not log Not Secure marker -+ this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), params, !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || flag || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker ? null : "Not Secure"); // Paper // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures - OutgoingChatMessage outgoingchatmessage = OutgoingChatMessage.create(message); - boolean flag1 = false; - -@@ -1637,6 +1637,7 @@ public abstract class PlayerList { - } - - public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public -+ if (!org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled) return true; // Leaf - Mirai - Configurable chat message signatures - return message.hasSignature() && !message.hasExpiredServer(Instant.now()); - } - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java b/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java -new file mode 100644 -index 0000000000000000000000000000000000000000..096969e2c3089e6b4f7332c06bf8a45a7dbe610b ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java -@@ -0,0 +1,25 @@ -+package org.dreeam.leaf.config.modules.network; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ChatMessageSignature extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.NETWORK.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".chat-message-signature", enabled, config.pickStringRegionBased(""" -+ Whether or not enable chat message signature, -+ disable will prevent players to report chat messages. -+ And also disables the popup when joining a server without -+ 'secure chat', such as offline-mode servers. -+ """, -+ """ -+ 是否启用聊天签名, 禁用后玩家无法进行聊天举报.""")); -+ } -+} diff --git a/patches/server/0065-Cache-player-profileResult.patch b/patches/server/0065-Cache-player-profileResult.patch deleted file mode 100644 index 4e3e3ae2..00000000 --- a/patches/server/0065-Cache-player-profileResult.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Thu, 28 Mar 2024 13:36:09 -0400 -Subject: [PATCH] Cache player profileResult - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 92fc2ab1fd20d31d1c5476f8f2349f0f64fbec9d..e83f011f8f2b37a7c8b04284b5510a131b1b7259 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -38,6 +38,10 @@ dependencies { - implementation("org.lz4:lz4-java:1.8.0") - // LinearPaper end - -+ // Leaf start - Libraries -+ implementation("com.github.ben-manes.caffeine:caffeine:3.1.8") -+ // Leaf end - Libraries -+ - // Paper start - // Leaf start - Bump Dependencies - implementation("org.jline:jline-terminal-ffm:3.28.0") // use ffm on java 22+ // Leaf Bu -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index e442fd1b80dd57d4420cab4da69ec3598ffc22d3..52fdc27ae63c2524e316620d905af7cd97112482 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -92,6 +92,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - private ServerPlayer player; // CraftBukkit - public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding - private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support -+ // Leaf start - Cache player profileResult -+ private static final com.github.benmanes.caffeine.cache.Cache playerProfileResultCahce = com.github.benmanes.caffeine.cache.Caffeine.newBuilder() -+ .expireAfterWrite(org.dreeam.leaf.config.modules.misc.Cache.cachePlayerProfileResultTimeout, java.util.concurrent.TimeUnit.MINUTES) -+ .build(); -+ // Leaf end - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -319,7 +324,19 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - String s1 = (String) Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); - - try { -- ProfileResult profileresult = ServerLoginPacketListenerImpl.this.server.getSessionService().hasJoinedServer(s1, s, this.getAddress()); -+ // Leaf start - Cache player profileResult -+ ProfileResult profileresult; -+ if (org.dreeam.leaf.config.modules.misc.Cache.cachePlayerProfileResult) { -+ profileresult = playerProfileResultCahce.getIfPresent(s1); -+ -+ if (profileresult == null) { -+ profileresult = ServerLoginPacketListenerImpl.this.server.getSessionService().hasJoinedServer(s1, s, this.getAddress()); -+ playerProfileResultCahce.put(s1, profileresult); -+ } -+ } else { -+ profileresult = ServerLoginPacketListenerImpl.this.server.getSessionService().hasJoinedServer(s1, s, this.getAddress()); -+ } -+ // Leaf end - - if (profileresult != null) { - GameProfile gameprofile = profileresult.profile(); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java b/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ca342af5e5b305fe9f8ecb88d06af63426f11f87 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java -@@ -0,0 +1,29 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class Cache extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".cache"; -+ } -+ -+ public static boolean cachePlayerProfileResult = true; -+ public static int cachePlayerProfileResultTimeout = 1440; -+ -+ @Override -+ public void onLoaded() { -+ cachePlayerProfileResult = config.getBoolean(getBasePath() + ".cache-player-profile-result", cachePlayerProfileResult, config.pickStringRegionBased(""" -+ Cache the player profile result on they first join. -+ It's useful if Mojang's verification server is down.""", -+ """ -+ 玩家首次加入时缓存 PlayerProfile. -+ 正版验证服务器宕机时非常有用.""")); -+ cachePlayerProfileResultTimeout = config.getInt(getBasePath() + ".cache-player-profile-result-timeout", cachePlayerProfileResultTimeout, -+ config.pickStringRegionBased( -+ "The timeout of the cache. Unit: Minutes.", -+ "缓存过期时间. 单位: 分钟." -+ )); -+ } -+} diff --git a/patches/server/0066-Prevent-change-non-editable-sign-warning-spam-in-con.patch b/patches/server/0066-Prevent-change-non-editable-sign-warning-spam-in-con.patch deleted file mode 100644 index d374a70c..00000000 --- a/patches/server/0066-Prevent-change-non-editable-sign-warning-spam-in-con.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Thu, 28 Mar 2024 14:04:35 -0400 -Subject: [PATCH] Prevent change non-editable sign warning spam in console - - -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 6da1eec98c08e4909ecbd48fe90b3fd62011e284..347d1a8b302b73cd0a05e8aa04a08fc27f6a5ebc 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 -@@ -189,7 +189,7 @@ public class SignBlockEntity extends BlockEntity { - this.setAllowedPlayerEditor((UUID) null); - this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); - } else { -- SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); -+ if (!org.dreeam.leaf.config.modules.misc.RemoveChangeNonEditableSignWarning.enabled) SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); // Leaf - Remove change non-editable sign warning - if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update - ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java b/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2f24d3e101e4e7517ca1927dcfef85d2e5b034b3 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java -@@ -0,0 +1,22 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class RemoveChangeNonEditableSignWarning extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".remove-change-non-editable-sign-warning", enabled, -+ config.pickStringRegionBased( -+ "Enable to prevent console spam.", -+ "移除修改无法编辑的告示牌时输出的警告." -+ )); -+ } -+} diff --git a/patches/server/0067-Matter-Secure-Seed.patch b/patches/server/0067-Matter-Secure-Seed.patch deleted file mode 100644 index 68343357..00000000 --- a/patches/server/0067-Matter-Secure-Seed.patch +++ /dev/null @@ -1,850 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Apehum -Date: Thu, 9 Dec 2021 02:18:17 +0800 -Subject: [PATCH] Matter: Secure Seed - -TODO - Dreeam: -Able to write feature seed in existed level.dat (Done) -Update to BLAKE3 - -Original license: GPLv3 -Original project: https://github.com/plasmoapp/matter - -Co-authored-by: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -index 05e16103af3fd276f0196ddf1a2e5b729b025c34..3ab0687ebe4c8059ed65372b06c4abfb42676b7b 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -@@ -167,7 +167,17 @@ public class DedicatedServerProperties extends Settings { - return GsonHelper.parse(!s1.isEmpty() ? s1 : "{}"); - }, new JsonObject()), (String) this.get("level-type", (s1) -> { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 3639292f038fb872ae99757d1c04331cf2564ec2..09e06dbb8e3f9ce65fb0f9010aeb3066b6c21671 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -684,6 +684,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - } - - public ChunkGenerator getGenerator() { -+ su.plo.matter.Globals.setupGlobals(level); // Leaf - Matter - Feature Secure Seed - return this.chunkMap.generator(); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index c8c777bc19e21fbf82b3a7e68d0e9753be7d41d3..9a7a76599a44dfd51d5e9e9a0e892994528c7680 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -642,6 +642,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen); - } - // CraftBukkit end -+ su.plo.matter.Globals.setupGlobals(this); // Leaf - Matter - Feature Secure Seed - boolean flag2 = minecraftserver.forceSynchronousWrites(); - DataFixer datafixer = minecraftserver.getFixerUpper(); - EntityPersistentStorage entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver); -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 dad4ef9c672eb4247142de5d045678795951164c..40db1b4ee806d218653d0754e39ac025e78daac2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -431,7 +431,12 @@ 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 -+ // Leaf start - Matter - Feature Secure Seed -+ boolean isSlimeChunk = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? world.getChunk(chunkcoordintpair.x, chunkcoordintpair.z).isSlimeChunk() -+ : WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper -+ boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || isSlimeChunk; -+ // Leaf end - Matter - Feature Secure Seed - - // Paper start - Replace rules for Height in Slime Chunks - final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; -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 a402e2f774cfc062afab8d86969f3e6f38874063..afdff26d6a7a0f0e4693da248be04329479bf12e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -85,6 +85,11 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh - protected final LevelHeightAccessor levelHeightAccessor; - protected final LevelChunkSection[] sections; - -+ // Leaf start - Matter - Feature Secure Seed -+ private boolean slimeChunk; -+ private boolean hasComputedSlimeChunk; -+ // Leaf end - Matter - Feature Secure Seed -+ - // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading. - 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); -@@ -189,6 +194,17 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh - return GameEventListenerRegistry.NOOP; - } - -+ // Leaf start - Matter - Feature Secure Seed -+ public boolean isSlimeChunk() { -+ if (!hasComputedSlimeChunk) { -+ hasComputedSlimeChunk = true; -+ slimeChunk = su.plo.matter.WorldgenCryptoRandom.seedSlimeChunk(chunkPos.x, chunkPos.z).nextInt(10) == 0; -+ } -+ -+ return slimeChunk; -+ } -+ // Leaf end - Matter - Feature Secure Seed -+ - public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper - @Nullable - public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); -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 b5114f84b2df2f4606702b892d32d484225d9dcf..1582771de72f713c155427587feea73ca7b4b273 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -344,7 +344,11 @@ public abstract class ChunkGenerator { - return structure.step().ordinal(); - })); - List list = (List) this.featuresPerStep.get(); -- WorldgenRandom seededrandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldgenRandom seededrandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? new su.plo.matter.WorldgenCryptoRandom(blockposition.getX(), blockposition.getZ(), su.plo.matter.Globals.Salt.UNDEFINED, 0) -+ : new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); -+ // Leaf end - Matter - Feature Secure Seed - long i = seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), blockposition.getX(), blockposition.getZ()); - Set> set = new ObjectArraySet(); - -@@ -583,9 +587,18 @@ public abstract class ChunkGenerator { - ArrayList arraylist = new ArrayList(list.size()); - - arraylist.addAll(list); -- WorldgenRandom seededrandom = new WorldgenRandom(new LegacyRandomSource(0L)); -- -- seededrandom.setLargeFeatureSeed(placementCalculator.getLevelSeed(), chunkcoordintpair.x, chunkcoordintpair.z); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldgenRandom seededrandom; -+ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { -+ seededrandom = new su.plo.matter.WorldgenCryptoRandom( -+ chunkcoordintpair.x, chunkcoordintpair.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, 0 -+ ); -+ } else { -+ seededrandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ -+ seededrandom.setLargeFeatureSeed(placementCalculator.getLevelSeed(), chunkcoordintpair.x, chunkcoordintpair.z); -+ } -+ // Leaf end - Matter - Feature Secure Seed - int i = 0; - - StructureSet.StructureSelectionEntry structureset_a1; -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -index b4d9e43b524b9cc6da4bb50d28ce1e854f5abcce..c497c0a374b8b46b0e8ddbdf492dc9aef8536c68 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -@@ -224,8 +224,12 @@ public class ChunkGeneratorStructureState { - List> list = new ArrayList(j); - int k = placement.spread(); - HolderSet holderset = placement.preferredBiomes(); -- RandomSource randomsource = RandomSource.create(); -+ // Leaf start - Matter - Feature Secure Seed -+ RandomSource randomsource = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.STRONGHOLDS, 0) -+ : RandomSource.create(); - -+ if (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { - // Paper start - Add missing structure set seed configs - if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { - randomsource.setSeed(this.conf.strongholdSeed); -@@ -233,6 +237,7 @@ public class ChunkGeneratorStructureState { - // Paper end - Add missing structure set seed configs - randomsource.setSeed(this.concentricRingsSeed); - } // Paper - Add missing structure set seed configs -+ }// Leaf end - Matter - Feature Secure Seed - double d0 = randomsource.nextDouble() * Math.PI * 2.0D; - int l = 0; - int i1 = 0; -diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java -index 4e56398a6fb8b97199f4c74ebebc1055fb718dcf..505f0ab967b53c100c91971afbf67bdcafcbee64 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java -+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java -@@ -60,6 +60,7 @@ public final class ChunkStep implements ca.spottedleaf.moonrise.patches.chunk_sy - } - - public CompletableFuture apply(WorldGenContext context, StaticCache2D staticCache2D, ChunkAccess chunk) { -+ su.plo.matter.Globals.setupGlobals(context.level()); // Leaf - Matter - Feature Secure Seed - if (chunk.getPersistedStatus().isBefore(this.targetStatus)) { - ProfiledDuration profiledDuration = JvmProfiler.INSTANCE.onChunkGenerate(chunk.getPos(), context.level().dimension(), this.targetStatus.getName()); - return this.task.doWork(context, this, staticCache2D, chunk).thenApply(generated -> this.completeChunkGeneration(generated, profiledDuration)); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/WorldOptions.java b/src/main/java/net/minecraft/world/level/levelgen/WorldOptions.java -index 41c19e4e7bde4632879da564f52f3d373de27ec4..90ea4e4c63b9f7bacaaa2f3a590000daecc6356d 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/WorldOptions.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/WorldOptions.java -@@ -9,8 +9,20 @@ import net.minecraft.util.RandomSource; - import org.apache.commons.lang3.StringUtils; - - public class WorldOptions { -+ // Leaf start - Matter - Feature Secure Seed -+ private static final com.google.gson.Gson gson = new com.google.gson.Gson(); -+ private static final boolean isSecureSeedEnabled = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled; - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( -- instance -> instance.group( -+ instance -> isSecureSeedEnabled -+ ? instance.group( -+ Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), -+ Codec.STRING.fieldOf("feature_seed").orElse(gson.toJson(su.plo.matter.Globals.createRandomWorldSeed())).stable().forGetter(WorldOptions::featureSeedSerialize), -+ Codec.BOOL.fieldOf("generate_features").orElse(true).stable().forGetter(WorldOptions::generateStructures), -+ Codec.BOOL.fieldOf("bonus_chest").orElse(false).stable().forGetter(WorldOptions::generateBonusChest), -+ Codec.STRING.lenientOptionalFieldOf("legacy_custom_options").stable().forGetter(generatorOptions -> generatorOptions.legacyCustomOptions) -+ ) -+ .apply(instance, instance.stable(WorldOptions::new)) -+ : instance.group( - Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), - Codec.BOOL.fieldOf("generate_features").orElse(true).stable().forGetter(WorldOptions::generateStructures), - Codec.BOOL.fieldOf("bonus_chest").orElse(false).stable().forGetter(WorldOptions::generateBonusChest), -@@ -18,8 +30,14 @@ public class WorldOptions { - ) - .apply(instance, instance.stable(WorldOptions::new)) - ); -- public static final WorldOptions DEMO_OPTIONS = new WorldOptions((long)"North Carolina".hashCode(), true, true); -+ // Leaf end - Matter - Feature Secure Seed -+ // Leaf start - Matter - Feature Secure Seed -+ public static final WorldOptions DEMO_OPTIONS = isSecureSeedEnabled -+ ? new WorldOptions((long) "North Carolina".hashCode(), su.plo.matter.Globals.createRandomWorldSeed(), true, true) -+ : new WorldOptions("North Carolina".hashCode(), true, true); -+ // Leaf end - Matter - Feature Secure Seed - private final long seed; -+ private long[] featureSeed = su.plo.matter.Globals.createRandomWorldSeed(); // Leaf - Matter - Feature Secure Seed - private final boolean generateStructures; - private final boolean generateBonusChest; - private final Optional legacyCustomOptions; -@@ -28,14 +46,35 @@ public class WorldOptions { - this(seed, generateStructures, bonusChest, Optional.empty()); - } - -+ // Leaf start - Matter - Feature Secure Seed -+ public WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest) { -+ this(seed, featureSeed, generateStructures, bonusChest, Optional.empty()); -+ } -+ -+ private WorldOptions(long seed, String featureSeedJson, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { -+ this(seed, gson.fromJson(featureSeedJson, long[].class), generateStructures, bonusChest, legacyCustomOptions); -+ } -+ // Leaf end - Matter - Feature Secure Seed -+ - public static WorldOptions defaultWithRandomSeed() { -- return new WorldOptions(randomSeed(), true, false); -+ // Leaf start - Matter - Feature Secure Seed -+ return isSecureSeedEnabled -+ ? new WorldOptions(randomSeed(), su.plo.matter.Globals.createRandomWorldSeed(), true, false) -+ : new WorldOptions(randomSeed(), true, false); -+ // Leaf end - Matter - Feature Secure Seed - } - - public static WorldOptions testWorldWithRandomSeed() { - return new WorldOptions(randomSeed(), false, false); - } - -+ // Leaf start - Matter - Feature Secure Seed -+ private WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { -+ this(seed, generateStructures, bonusChest, legacyCustomOptions); -+ this.featureSeed = featureSeed; -+ } -+ // Leaf end - Matter - Feature Secure Seed -+ - private WorldOptions(long seed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { - this.seed = seed; - this.generateStructures = generateStructures; -@@ -47,6 +86,16 @@ public class WorldOptions { - return this.seed; - } - -+ // Leaf start - Matter - Feature Secure Seed -+ public long[] featureSeed() { -+ return this.featureSeed; -+ } -+ -+ public String featureSeedSerialize() { -+ return gson.toJson(this.featureSeed); -+ } -+ // Leaf end - Matter - Feature Secure Seed -+ - public boolean generateStructures() { - return this.generateStructures; - } -@@ -59,17 +108,25 @@ public class WorldOptions { - return this.legacyCustomOptions.isPresent(); - } - -+ // Leaf start - Matter - Feature Secure Seed - public WorldOptions withBonusChest(boolean bonusChest) { -- return new WorldOptions(this.seed, this.generateStructures, bonusChest, this.legacyCustomOptions); -+ return isSecureSeedEnabled -+ ? new WorldOptions(this.seed, this.featureSeed, this.generateStructures, bonusChest, this.legacyCustomOptions) -+ : new WorldOptions(this.seed, this.generateStructures, bonusChest, this.legacyCustomOptions); - } - - public WorldOptions withStructures(boolean structures) { -- return new WorldOptions(this.seed, structures, this.generateBonusChest, this.legacyCustomOptions); -+ return isSecureSeedEnabled -+ ? new WorldOptions(this.seed, this.featureSeed, structures, this.generateBonusChest, this.legacyCustomOptions) -+ : new WorldOptions(this.seed, structures, this.generateBonusChest, this.legacyCustomOptions); - } - - public WorldOptions withSeed(OptionalLong seed) { -- return new WorldOptions(seed.orElse(randomSeed()), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions); -+ return isSecureSeedEnabled -+ ? new WorldOptions(seed.orElse(randomSeed()), su.plo.matter.Globals.createRandomWorldSeed(), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions) -+ : new WorldOptions(seed.orElse(randomSeed()), this.generateStructures, this.generateBonusChest, this.legacyCustomOptions); - } -+ // Leaf end - Matter - Feature Secure Seed - - public static OptionalLong parseSeed(String seed) { - seed = seed.trim(); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java -index 270db8b29cdf65e9bb932637425214eefeca86b7..56888e150a48cf338ecec591d47c979b5c24ad4d 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java -@@ -41,7 +41,11 @@ public class GeodeFeature extends Feature { - int j = geodeConfiguration.maxGenOffset; - List> list = Lists.newLinkedList(); - int k = geodeConfiguration.distributionPoints.sample(randomSource); -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.GEODE_FEATURE, 0) -+ : new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); -+ // Leaf end - Matter - Feature Secure Seed - NormalNoise normalNoise = NormalNoise.create(worldgenRandom, -4, 1.0); - List list2 = Lists.newLinkedList(); - double d = (double)k / (double)geodeConfiguration.outerWallDistance.getMaxValue(); -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 3fbd6e8d23f2e6020532530ef8ad7e64b8047d4b..c10efc79d2cab84703fa4ee6267e12b856f8e248 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 -@@ -233,6 +233,13 @@ public abstract class Structure { - } - - private static WorldgenRandom makeRandom(long seed, ChunkPos chunkPos) { -+ // Leaf start - Matter - Feature Secure Seed -+ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { -+ return new su.plo.matter.WorldgenCryptoRandom( -+ chunkPos.x, chunkPos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, seed -+ ); -+ } -+ // Leaf end - Matter - Feature Secure Seed - WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); - worldgenRandom.setLargeFeatureSeed(seed, chunkPos.x, chunkPos.z); - return worldgenRandom; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java -index f873a0a0734b4fe74ba5b5f8ae0cc3c78fa76b9f..5ae511a5a178b0202e6a60cb882fca37a2a92fd4 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java -@@ -71,8 +71,17 @@ public class RandomSpreadStructurePlacement extends StructurePlacement { - public ChunkPos getPotentialStructureChunk(long seed, int chunkX, int chunkZ) { - int i = Math.floorDiv(chunkX, this.spacing); - int j = Math.floorDiv(chunkZ, this.spacing); -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -- worldgenRandom.setLargeFeatureWithSalt(seed, i, j, this.salt()); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldgenRandom worldgenRandom; -+ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { -+ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( -+ i, j, su.plo.matter.Globals.Salt.POTENTIONAL_FEATURE, this.salt -+ ); -+ } else { -+ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ worldgenRandom.setLargeFeatureWithSalt(seed, i, j, this.salt()); -+ } -+ // Leaf end - Matter - Feature Secure Seed - int k = this.spacing - this.separation; - int l = this.spreadType.evaluate(worldgenRandom, k); - int m = this.spreadType.evaluate(worldgenRandom, k); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -index cbf13e4f2da6a27619e9bc9a7cd73bb6e69cad2a..d97bb945f59dc7d8da1374fda5beee0d6d0f0f5d 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -@@ -118,8 +118,18 @@ public abstract class StructurePlacement { - public abstract StructurePlacementType type(); - - private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -- worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldgenRandom worldgenRandom; -+ if (org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) { -+ worldgenRandom = new su.plo.matter.WorldgenCryptoRandom( -+ chunkX, chunkZ, su.plo.matter.Globals.Salt.UNDEFINED, salt -+ ); -+ } else { -+ worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); -+ } -+ // Leaf end - Matter - Feature Secure Seed -+ - return worldgenRandom.nextFloat() < frequency; - } - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java -index a59fdf91cb2f17aa2855af0a63a1396ed6179185..3115707c388b61db108fb8fc1e7aca13f239cee6 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java -@@ -64,7 +64,11 @@ public class JigsawPlacement { - ChunkGenerator chunkGenerator = context.chunkGenerator(); - StructureTemplateManager structureTemplateManager = context.structureTemplateManager(); - LevelHeightAccessor levelHeightAccessor = context.heightAccessor(); -- WorldgenRandom worldgenRandom = context.random(); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? new su.plo.matter.WorldgenCryptoRandom(context.chunkPos().x, context.chunkPos().z, su.plo.matter.Globals.Salt.JIGSAW_PLACEMENT, 0) -+ : context.random(); -+ // Leaf end - Matter - Feature Secure Seed - Registry registry = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL); - Rotation rotation = Rotation.getRandom(worldgenRandom); - StructureTemplatePool structureTemplatePool = structurePool.unwrapKey() -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java -index 712c6431d63e0b1a1edd9d7de9b13c46a1acb469..35dc49d90aa6d660a60fb76efddb530f9ec59666 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/structures/EndCityStructure.java -@@ -10,6 +10,7 @@ import net.minecraft.world.level.levelgen.structure.Structure; - import net.minecraft.world.level.levelgen.structure.StructurePiece; - import net.minecraft.world.level.levelgen.structure.StructureType; - import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; -+//import su.plo.matter.WorldgenCryptoRandom; // Leaf - Matter - Feature Secure Seed - - public class EndCityStructure extends Structure { - public static final MapCodec CODEC = simpleCodec(EndCityStructure::new); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java -index 7f4c5e9355a6f562f668e9b8134bfe65dde35f90..7a1c21696f6531c7dded774f45073df16732f252 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/structures/MineshaftStructure.java -@@ -20,6 +20,7 @@ import net.minecraft.world.level.levelgen.WorldgenRandom; - import net.minecraft.world.level.levelgen.structure.Structure; - import net.minecraft.world.level.levelgen.structure.StructureType; - import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; -+//import su.plo.matter.WorldgenCryptoRandom; // Leaf - Matter - Feature Secure Seed - - public class MineshaftStructure extends Structure { - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 2dd38f40c8c0b48b12ffe557ceeed69213066a6a..13407f2906499b297df2517f0bfb65fbf6e90649 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -205,7 +205,12 @@ public class CraftChunk implements Chunk { - @Override - public boolean isSlimeChunk() { - // 987234911L is deterimined in EntitySlime when seeing if a slime can spawn in a chunk -- return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper -+ // Leaf start - Matter - Feature Secure Seed -+ boolean isSlimeChunk = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? worldServer.getChunk(this.getX(), this.getZ()).isSlimeChunk() -+ : WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper -+ return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || isSlimeChunk; -+ // Leaf end - Matter - Feature Secure Seed - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d288be14c647cbcfc45c2b5763762ce3570ed683..7cf02a451ec2b82dc369333041ac7b93d35a2b44 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1416,7 +1416,11 @@ public final class CraftServer implements Server { - iregistrycustom_dimension = leveldataanddimensions.dimensions().dimensionsRegistryAccess(); - } else { - LevelSettings worldsettings; -- WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); -+ // Leaf start - Matter - Feature Secure Seed -+ WorldOptions worldoptions = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled -+ ? new WorldOptions(creator.seed(), su.plo.matter.Globals.createRandomWorldSeed(), creator.generateStructures(), false) -+ : new WorldOptions(creator.seed(), creator.generateStructures(), false); -+ // Leaf end - Matter - Feature Secure Seed - WorldDimensions worlddimensions; - - DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT)); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java b/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java -new file mode 100644 -index 0000000000000000000000000000000000000000..eb509447851b301e5e69c8ada9f8b268861e2fe3 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java -@@ -0,0 +1,24 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class SecureSeed extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".secure-seed"; -+ } -+ -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Once you enable secure seed, all ores and structures are generated with 1024-bit seed -+ instead of using 64-bit seed in vanilla, made seed cracker become impossible.""", -+ """ -+ 安全种子开启后, 所有矿物与结构都将使用1024位的种子进行生成, 无法被破解."""); -+ -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled); -+ } -+} -diff --git a/src/main/java/su/plo/matter/Globals.java b/src/main/java/su/plo/matter/Globals.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d325d1617094e12ca011f5ef0e259cd806e35dcc ---- /dev/null -+++ b/src/main/java/su/plo/matter/Globals.java -@@ -0,0 +1,94 @@ -+package su.plo.matter; -+ -+import com.google.common.collect.Iterables; -+import net.minecraft.server.level.ServerLevel; -+ -+import java.math.BigInteger; -+import java.security.SecureRandom; -+import java.util.Optional; -+ -+public class Globals { -+ public static final int WORLD_SEED_LONGS = 16; -+ public static final int WORLD_SEED_BITS = WORLD_SEED_LONGS * 64; -+ -+ public static final long[] worldSeed = new long[WORLD_SEED_LONGS]; -+ public static final ThreadLocal dimension = ThreadLocal.withInitial(() -> 0); -+ -+ public enum Salt { -+ UNDEFINED, -+ BASTION_FEATURE, -+ WOODLAND_MANSION_FEATURE, -+ MINESHAFT_FEATURE, -+ BURIED_TREASURE_FEATURE, -+ NETHER_FORTRESS_FEATURE, -+ PILLAGER_OUTPOST_FEATURE, -+ GEODE_FEATURE, -+ NETHER_FOSSIL_FEATURE, -+ OCEAN_MONUMENT_FEATURE, -+ RUINED_PORTAL_FEATURE, -+ POTENTIONAL_FEATURE, -+ GENERATE_FEATURE, -+ JIGSAW_PLACEMENT, -+ STRONGHOLDS, -+ POPULATION, -+ DECORATION, -+ SLIME_CHUNK -+ } -+ -+ public static void setupGlobals(ServerLevel world) { -+ if (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) return; -+ -+ long[] seed = world.getServer().getWorldData().worldGenOptions().featureSeed(); -+ System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS); -+ int worldIndex = Iterables.indexOf(world.getServer().levelKeys(), it -> it == world.dimension()); -+ if (worldIndex == -1) -+ worldIndex = world.getServer().levelKeys().size(); // if we are in world construction it may not have been added to the map yet -+ dimension.set(worldIndex); -+ } -+ -+ public static long[] createRandomWorldSeed() { -+ long[] seed = new long[WORLD_SEED_LONGS]; -+ SecureRandom rand = new SecureRandom(); -+ for (int i = 0; i < WORLD_SEED_LONGS; i++) { -+ seed[i] = rand.nextLong(); -+ } -+ return seed; -+ } -+ -+ // 1024-bit string -> 16 * 64 long[] -+ public static Optional parseSeed(String seedStr) { -+ if (seedStr.isEmpty()) return Optional.empty(); -+ -+ if (seedStr.length() != WORLD_SEED_BITS) { -+ throw new IllegalArgumentException("Secure seed length must be " + WORLD_SEED_BITS + "-bit but found " + seedStr.length() + "-bit."); -+ } -+ -+ long[] seed = new long[WORLD_SEED_LONGS]; -+ -+ for (int i = 0; i < WORLD_SEED_LONGS; i++) { -+ int start = i * 64; -+ int end = start + 64; -+ String seedSection = seedStr.substring(start, end); -+ -+ BigInteger seedInDecimal = new BigInteger(seedSection, 2); -+ seed[i] = seedInDecimal.longValue(); -+ } -+ -+ return Optional.of(seed); -+ } -+ -+ // 16 * 64 long[] -> 1024-bit string -+ public static String seedToString(long[] seed) { -+ StringBuilder sb = new StringBuilder(); -+ -+ for (long longV : seed) { -+ // Convert to 64-bit binary string per long -+ // Use format to keep 64-bit length, and use 0 to complete space -+ String binaryStr = String.format("%64s", Long.toBinaryString(longV)).replace(' ', '0'); -+ -+ sb.append(binaryStr); -+ } -+ -+ return sb.toString(); -+ } -+} -diff --git a/src/main/java/su/plo/matter/Hashing.java b/src/main/java/su/plo/matter/Hashing.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ec7a57c6af5d5f4fd27a643d253c2ecce90c01ac ---- /dev/null -+++ b/src/main/java/su/plo/matter/Hashing.java -@@ -0,0 +1,73 @@ -+package su.plo.matter; -+ -+public class Hashing { -+ // https://en.wikipedia.org/wiki/BLAKE_(hash_function) -+ // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java -+ -+ private final static long[] blake2b_IV = { -+ 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, -+ 0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, -+ 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L -+ }; -+ -+ private final static byte[][] blake2b_sigma = { -+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, -+ {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, -+ {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, -+ {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, -+ {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, -+ {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, -+ {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, -+ {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, -+ {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, -+ {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, -+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, -+ {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3} -+ }; -+ -+ public static long[] hashWorldSeed(long[] worldSeed) { -+ long[] result = blake2b_IV.clone(); -+ result[0] ^= 0x01010040; -+ hash(worldSeed, result, new long[16], 0, false); -+ return result; -+ } -+ -+ public static void hash(long[] message, long[] chainValue, long[] internalState, long messageOffset, boolean isFinal) { -+ assert message.length == 16; -+ assert chainValue.length == 8; -+ assert internalState.length == 16; -+ -+ System.arraycopy(chainValue, 0, internalState, 0, chainValue.length); -+ System.arraycopy(blake2b_IV, 0, internalState, chainValue.length, 4); -+ internalState[12] = messageOffset ^ blake2b_IV[4]; -+ internalState[13] = blake2b_IV[5]; -+ if (isFinal) internalState[14] = ~blake2b_IV[6]; -+ internalState[15] = blake2b_IV[7]; -+ -+ for (int round = 0; round < 12; round++) { -+ G(message[blake2b_sigma[round][0]], message[blake2b_sigma[round][1]], 0, 4, 8, 12, internalState); -+ G(message[blake2b_sigma[round][2]], message[blake2b_sigma[round][3]], 1, 5, 9, 13, internalState); -+ G(message[blake2b_sigma[round][4]], message[blake2b_sigma[round][5]], 2, 6, 10, 14, internalState); -+ G(message[blake2b_sigma[round][6]], message[blake2b_sigma[round][7]], 3, 7, 11, 15, internalState); -+ G(message[blake2b_sigma[round][8]], message[blake2b_sigma[round][9]], 0, 5, 10, 15, internalState); -+ G(message[blake2b_sigma[round][10]], message[blake2b_sigma[round][11]], 1, 6, 11, 12, internalState); -+ G(message[blake2b_sigma[round][12]], message[blake2b_sigma[round][13]], 2, 7, 8, 13, internalState); -+ G(message[blake2b_sigma[round][14]], message[blake2b_sigma[round][15]], 3, 4, 9, 14, internalState); -+ } -+ -+ for (int i = 0; i < 8; i++) { -+ chainValue[i] ^= internalState[i] ^ internalState[i + 8]; -+ } -+ } -+ -+ private static void G(long m1, long m2, int posA, int posB, int posC, int posD, long[] internalState) { -+ internalState[posA] = internalState[posA] + internalState[posB] + m1; -+ internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32); -+ internalState[posC] = internalState[posC] + internalState[posD]; -+ internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE -+ internalState[posA] = internalState[posA] + internalState[posB] + m2; -+ internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16); -+ internalState[posC] = internalState[posC] + internalState[posD]; -+ internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE -+ } -+} -diff --git a/src/main/java/su/plo/matter/WorldgenCryptoRandom.java b/src/main/java/su/plo/matter/WorldgenCryptoRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fab359afe44c573b8b315115810ddefd85b4d22b ---- /dev/null -+++ b/src/main/java/su/plo/matter/WorldgenCryptoRandom.java -@@ -0,0 +1,159 @@ -+package su.plo.matter; -+ -+import net.minecraft.util.Mth; -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.LegacyRandomSource; -+import net.minecraft.world.level.levelgen.WorldgenRandom; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Arrays; -+ -+public class WorldgenCryptoRandom extends WorldgenRandom { -+ // hash the world seed to guard against badly chosen world seeds -+ private static final long[] HASHED_ZERO_SEED = Hashing.hashWorldSeed(new long[Globals.WORLD_SEED_LONGS]); -+ private static final ThreadLocal LAST_SEEN_WORLD_SEED = ThreadLocal.withInitial(() -> new long[Globals.WORLD_SEED_LONGS]); -+ private static final ThreadLocal HASHED_WORLD_SEED = ThreadLocal.withInitial(() -> HASHED_ZERO_SEED); -+ -+ private final long[] worldSeed = new long[Globals.WORLD_SEED_LONGS]; -+ private final long[] randomBits = new long[8]; -+ private int randomBitIndex; -+ private static final int MAX_RANDOM_BIT_INDEX = 64 * 8; -+ private static final int LOG2_MAX_RANDOM_BIT_INDEX = 9; -+ private long counter; -+ private final long[] message = new long[16]; -+ private final long[] cachedInternalState = new long[16]; -+ -+ public WorldgenCryptoRandom(int x, int z, Globals.Salt typeSalt, long salt) { -+ super(new LegacyRandomSource(0L)); -+ if (typeSalt != null) { -+ this.setSecureSeed(x, z, typeSalt, salt); -+ } -+ } -+ -+ public void setSecureSeed(int x, int z, Globals.Salt typeSalt, long salt) { -+ System.arraycopy(Globals.worldSeed, 0, this.worldSeed, 0, Globals.WORLD_SEED_LONGS); -+ message[0] = ((long) x << 32) | ((long) z & 0xffffffffL); -+ message[1] = ((long) Globals.dimension.get() << 32) | ((long) salt & 0xffffffffL); -+ message[2] = typeSalt.ordinal(); -+ message[3] = counter = 0; -+ randomBitIndex = MAX_RANDOM_BIT_INDEX; -+ } -+ -+ private long[] getHashedWorldSeed() { -+ if (!Arrays.equals(worldSeed, LAST_SEEN_WORLD_SEED.get())) { -+ HASHED_WORLD_SEED.set(Hashing.hashWorldSeed(worldSeed)); -+ System.arraycopy(worldSeed, 0, LAST_SEEN_WORLD_SEED.get(), 0, Globals.WORLD_SEED_LONGS); -+ } -+ return HASHED_WORLD_SEED.get(); -+ } -+ -+ private void moreRandomBits() { -+ message[3] = counter++; -+ System.arraycopy(getHashedWorldSeed(), 0, randomBits, 0, 8); -+ Hashing.hash(message, randomBits, cachedInternalState, 64, true); -+ } -+ -+ private long getBits(int count) { -+ if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) { -+ moreRandomBits(); -+ randomBitIndex -= MAX_RANDOM_BIT_INDEX; -+ } -+ -+ int alignment = randomBitIndex & 63; -+ if ((randomBitIndex >>> 6) == ((randomBitIndex + count) >>> 6)) { -+ long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << count) - 1); -+ randomBitIndex += count; -+ return result; -+ } else { -+ long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << (64 - alignment)) - 1); -+ randomBitIndex += count; -+ if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) { -+ moreRandomBits(); -+ randomBitIndex -= MAX_RANDOM_BIT_INDEX; -+ } -+ alignment = randomBitIndex & 63; -+ result <<= alignment; -+ result |= (randomBits[randomBitIndex >>> 6] >>> (64 - alignment)) & ((1L << alignment) - 1); -+ -+ return result; -+ } -+ } -+ -+ @Override -+ public @NotNull RandomSource fork() { -+ WorldgenCryptoRandom fork = new WorldgenCryptoRandom(0, 0, null, 0); -+ -+ System.arraycopy(Globals.worldSeed, 0, fork.worldSeed, 0, Globals.WORLD_SEED_LONGS); -+ fork.message[0] = this.message[0]; -+ fork.message[1] = this.message[1]; -+ fork.message[2] = this.message[2]; -+ fork.message[3] = this.message[3]; -+ fork.randomBitIndex = this.randomBitIndex; -+ fork.counter = this.counter; -+ fork.nextLong(); -+ -+ return fork; -+ } -+ -+ @Override -+ public int next(int bits) { -+ return (int) getBits(bits); -+ } -+ -+ @Override -+ public void consumeCount(int count) { -+ randomBitIndex += count; -+ if (randomBitIndex >= MAX_RANDOM_BIT_INDEX * 2) { -+ randomBitIndex -= MAX_RANDOM_BIT_INDEX; -+ counter += randomBitIndex >>> LOG2_MAX_RANDOM_BIT_INDEX; -+ randomBitIndex &= MAX_RANDOM_BIT_INDEX - 1; -+ randomBitIndex += MAX_RANDOM_BIT_INDEX; -+ } -+ } -+ -+ @Override -+ public int nextInt(int bound) { -+ int bits = Mth.ceillog2(bound); -+ int result; -+ do { -+ result = (int) getBits(bits); -+ } while (result >= bound); -+ -+ return result; -+ } -+ -+ @Override -+ public long nextLong() { -+ return getBits(64); -+ } -+ -+ @Override -+ public double nextDouble() { -+ return getBits(53) * 0x1.0p-53; -+ } -+ -+ @Override -+ public long setDecorationSeed(long worldSeed, int blockX, int blockZ) { -+ setSecureSeed(blockX, blockZ, Globals.Salt.POPULATION, 0); -+ return ((long) blockX << 32) | ((long) blockZ & 0xffffffffL); -+ } -+ -+ @Override -+ public void setFeatureSeed(long populationSeed, int index, int step) { -+ setSecureSeed((int) (populationSeed >> 32), (int) populationSeed, Globals.Salt.DECORATION, index + 10000L * step); -+ } -+ -+ @Override -+ public void setLargeFeatureSeed(long worldSeed, int chunkX, int chunkZ) { -+ super.setLargeFeatureSeed(worldSeed, chunkX, chunkZ); -+ } -+ -+ @Override -+ public void setLargeFeatureWithSalt(long worldSeed, int regionX, int regionZ, int salt) { -+ super.setLargeFeatureWithSalt(worldSeed, regionX, regionZ, salt); -+ } -+ -+ public static RandomSource seedSlimeChunk(int chunkX, int chunkZ) { -+ return new WorldgenCryptoRandom(chunkX, chunkZ, Globals.Salt.SLIME_CHUNK, 0); -+ } -+} diff --git a/patches/server/0069-Faster-Random-Generator.patch b/patches/server/0069-Faster-Random-Generator.patch deleted file mode 100644 index d91ed527..00000000 --- a/patches/server/0069-Faster-Random-Generator.patch +++ /dev/null @@ -1,538 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Tue, 9 Nov 2077 00:00:00 +0800 -Subject: [PATCH] Faster Random Generator - -This patch replaces LegacyRandomSource with FasterRandomSource by default, -which is faster in general. - -Benchmark results (10,000,000 iterations) (GraalVM 21) -SimpleRandom (Moonrise): 80ms -FasterRandomSource (Leaf) (Backed by Xoroshiro128PlusPlus): 35ms -LegacyRandomSource (Vanilla): 200ms -XoroshiroRandomSource (Vanilla): 47ms - -diff --git a/src/main/java/net/minecraft/util/RandomSource.java b/src/main/java/net/minecraft/util/RandomSource.java -index 252aef3ffe0fecd47ebea1ed7df48e14fa873eb9..5b4599927effca11293b367c5bac4541570df06c 100644 ---- a/src/main/java/net/minecraft/util/RandomSource.java -+++ b/src/main/java/net/minecraft/util/RandomSource.java -@@ -15,18 +15,32 @@ public interface RandomSource { - return create(RandomSupport.generateUniqueSeed()); - } - -+ // Leaf start - Faster random generator - @Deprecated - static RandomSource createThreadSafe() { -- return new ThreadSafeLegacyRandomSource(RandomSupport.generateUniqueSeed()); -+ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled -+ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) -+ : new ThreadSafeLegacyRandomSource(RandomSupport.generateUniqueSeed()); - } - - static RandomSource create(long seed) { -- return new LegacyRandomSource(seed); -+ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled -+ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) -+ : new LegacyRandomSource(seed); -+ } -+ -+ static RandomSource createForSlimeChunk(long seed) { -+ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled && !org.dreeam.leaf.config.modules.opt.FastRNG.useLegacyForSlimeChunk -+ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) -+ : new LegacyRandomSource(seed); - } - - static RandomSource createNewThreadLocalInstance() { -- return new SingleThreadedRandomSource(ThreadLocalRandom.current().nextLong()); -+ return org.dreeam.leaf.config.modules.opt.FastRNG.enabled -+ ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) -+ : new SingleThreadedRandomSource(ThreadLocalRandom.current().nextLong()); - } -+ // Leaf end - Faster random generator - - RandomSource fork(); - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index ee64667e5c33afd68d4458bffd858c66db4c1421..8ac2b3a17d1a9df6695c8034b71544704be23036 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -184,7 +184,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - // Paper start - Share random for entities to make them more random -- public static RandomSource SHARED_RANDOM = new RandomRandomSource(); -+ public static RandomSource SHARED_RANDOM = org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? org.dreeam.leaf.util.math.random.FasterRandomSource.SHARED_INSTANCE : new RandomRandomSource(); // Leaf - Faster random generator - // Paper start - replace random - private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom { - public RandomRandomSource() { -diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java -index b725eea9d3ca81d2ef7802f5d0346d924aa1f808..c669c64189b9ae3ddd871290d18e54e3aa7463ea 100644 ---- a/src/main/java/net/minecraft/world/level/biome/Biome.java -+++ b/src/main/java/net/minecraft/world/level/biome/Biome.java -@@ -50,14 +50,14 @@ public final class Biome { - ); - public static final Codec> CODEC = RegistryFileCodec.create(Registries.BIOME, DIRECT_CODEC); - public static final Codec> LIST_CODEC = RegistryCodecs.homogeneousList(Registries.BIOME, DIRECT_CODEC); -- private static final PerlinSimplexNoise TEMPERATURE_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(1234L)), ImmutableList.of(0)); -+ private static final PerlinSimplexNoise TEMPERATURE_NOISE = new PerlinSimplexNoise(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(1234L) : new LegacyRandomSource(1234L)), ImmutableList.of(0)); // Leaf - Faster random generator - static final PerlinSimplexNoise FROZEN_TEMPERATURE_NOISE = new PerlinSimplexNoise( -- new WorldgenRandom(new LegacyRandomSource(3456L)), ImmutableList.of(-2, -1, 0) -+ new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(3456L) : new LegacyRandomSource(3456L)), ImmutableList.of(-2, -1, 0) // Leaf - Faster random generator - ); - @Deprecated( - forRemoval = true - ) -- public static final PerlinSimplexNoise BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(2345L)), ImmutableList.of(0)); -+ public static final PerlinSimplexNoise BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(2345L) : new LegacyRandomSource(2345L)), ImmutableList.of(0)); // Leaf - Faster random generator - private static final int TEMPERATURE_CACHE_SIZE = 1024; - public final Biome.ClimateSettings climateSettings; - private final BiomeGenerationSettings generationSettings; -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 1582771de72f713c155427587feea73ca7b4b273..c731c13f69dcf92de539707ccddc7089192a2f7c 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -494,7 +494,7 @@ public abstract class ChunkGenerator { - int x = ichunkaccess.getPos().x; - int z = ichunkaccess.getPos().z; - for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { -- WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed())); -+ WorldgenRandom seededrandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(generatoraccessseed.getSeed()) : new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed())); // Leaf - Faster random generator - seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z); - populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion); - } -@@ -594,7 +594,7 @@ public abstract class ChunkGenerator { - chunkcoordintpair.x, chunkcoordintpair.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, 0 - ); - } else { -- seededrandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ seededrandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - - seededrandom.setLargeFeatureSeed(placementCalculator.getLevelSeed(), chunkcoordintpair.x, chunkcoordintpair.z); - } -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 ac8447e20531ad59d5e26c6db541d6e844d56c0f..0dab53e8e99e7de71cbd2d22a283b49bac635178 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -@@ -521,7 +521,7 @@ public final class DensityFunctions { - // Paper end - Perf: Optimize end generation - - public EndIslandDensityFunction(long seed) { -- RandomSource randomSource = new LegacyRandomSource(seed); -+ RandomSource randomSource = org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) : new LegacyRandomSource(seed); // Leaf - Faster random generator - randomSource.consumeCount(17292); - this.islandNoise = new SimplexNoise(randomSource); - } -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 aa63b49738ef42122e7cd0f9dbec0d019c9b97b0..bd0eaa7d5253fd4ea9f9a6f0c7bfcf11fbc675a7 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -@@ -232,7 +232,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - BiomeManager biomemanager1 = biomeAccess.withDifferentSource((j, k, l) -> { - return this.biomeSource.getNoiseBiome(j, k, l, noiseConfig.sampler()); - }); -- WorldgenRandom seededrandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed())); -+ WorldgenRandom seededrandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) : new LegacyRandomSource(RandomSupport.generateUniqueSeed())); // Leaf - Faster random generator - boolean flag = true; - ChunkPos chunkcoordintpair = chunk.getPos(); - NoiseChunk noisechunk = chunk.getOrCreateNoiseChunk((ichunkaccess1) -> { -@@ -427,7 +427,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - if (!((NoiseGeneratorSettings) this.settings.value()).disableMobGeneration()) { - ChunkPos chunkcoordintpair = region.getCenter(); - Holder holder = region.getBiome(chunkcoordintpair.getWorldPosition().atY(region.getMaxY())); -- WorldgenRandom seededrandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed())); -+ WorldgenRandom seededrandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) : new LegacyRandomSource(RandomSupport.generateUniqueSeed())); // Leaf - Faster random generator - - seededrandom.setDecorationSeed(region.getSeed(), chunkcoordintpair.getMinBlockX(), chunkcoordintpair.getMinBlockZ()); - NaturalSpawner.spawnMobsForChunkGeneration(region, holder, chunkcoordintpair, seededrandom); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/WorldgenRandom.java b/src/main/java/net/minecraft/world/level/levelgen/WorldgenRandom.java -index c6efe6faf68c7a7b1df344e2e527aa7e44bfacb8..fe89e7b7c4267ee2969d1505f83cba1ac17cb13e 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/WorldgenRandom.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/WorldgenRandom.java -@@ -69,9 +69,9 @@ public class WorldgenRandom extends LegacyRandomSource { - } - - public static RandomSource seedSlimeChunk(int chunkX, int chunkZ, long worldSeed, long scrambler) { -- return RandomSource.create( -+ return RandomSource.createForSlimeChunk( - worldSeed + (long)(chunkX * chunkX * 4987142) + (long)(chunkX * 5947611) + (long)(chunkZ * chunkZ) * 4392871L + (long)(chunkZ * 389711) ^ scrambler -- ); -+ ); // Leaf - Faster RNG - } - - public static enum Algorithm { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java -index 56888e150a48cf338ecec591d47c979b5c24ad4d..321eb6b9f6ae244c74b801a7d6556dceb82a19dc 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/GeodeFeature.java -@@ -44,7 +44,7 @@ public class GeodeFeature extends Feature { - // Leaf start - Matter - Feature Secure Seed - WorldgenRandom worldgenRandom = org.dreeam.leaf.config.modules.misc.SecureSeed.enabled - ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.GEODE_FEATURE, 0) -- : new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); -+ : new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(worldGenLevel.getSeed()) : new LegacyRandomSource(worldGenLevel.getSeed())); // Leaf - Faster random generator - // Leaf end - Matter - Feature Secure Seed - NormalNoise normalNoise = NormalNoise.create(worldgenRandom, -4, 1.0); - List list2 = Lists.newLinkedList(); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java b/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java -index c7eabcbf9132c5dac1e376332f563d8912447b03..8b8cef5f8e73185ea2f8143fbd4fb5f5afc9ef6e 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/DualNoiseProvider.java -@@ -43,7 +43,7 @@ public class DualNoiseProvider extends NoiseProvider { - this.variety = variety; - this.slowNoiseParameters = slowNoiseParameters; - this.slowScale = slowScale; -- this.slowNoise = NormalNoise.create(new WorldgenRandom(new LegacyRandomSource(seed)), slowNoiseParameters); -+ this.slowNoise = NormalNoise.create(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) : new LegacyRandomSource(seed)), slowNoiseParameters); // Leaf - Faster random generator - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java b/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java -index 990457fb23d22c3b8732d70da54ac5aaed8221db..f55475b812fd989d076c7715b8f060ab8f9ddd26 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/stateproviders/NoiseBasedStateProvider.java -@@ -28,7 +28,7 @@ public abstract class NoiseBasedStateProvider extends BlockStateProvider { - this.seed = seed; - this.parameters = noiseParameters; - this.scale = scale; -- this.noise = NormalNoise.create(new WorldgenRandom(new LegacyRandomSource(seed)), noiseParameters); -+ this.noise = NormalNoise.create(new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(seed) : new LegacyRandomSource(seed)), noiseParameters); // Leaf - Faster random generator - } - - protected double getNoiseValue(BlockPos pos, double scale) { -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 c10efc79d2cab84703fa4ee6267e12b856f8e248..fb44cb370bdda8d161603ab06851dea6cbbc9d08 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 -@@ -240,7 +240,7 @@ public abstract class Structure { - ); - } - // Leaf end - Matter - Feature Secure Seed -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - worldgenRandom.setLargeFeatureSeed(seed, chunkPos.x, chunkPos.z); - return worldgenRandom; - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java -index 5ae511a5a178b0202e6a60cb882fca37a2a92fd4..c763b8a11786d8d746ee72218805aad601deaad4 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/RandomSpreadStructurePlacement.java -@@ -78,7 +78,7 @@ public class RandomSpreadStructurePlacement extends StructurePlacement { - i, j, su.plo.matter.Globals.Salt.POTENTIONAL_FEATURE, this.salt - ); - } else { -- worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - worldgenRandom.setLargeFeatureWithSalt(seed, i, j, this.salt()); - } - // Leaf end - Matter - Feature Secure Seed -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -index d97bb945f59dc7d8da1374fda5beee0d6d0f0f5d..9973f89e65a6a6f7edfbd40b722f1d7b030872d5 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -@@ -125,7 +125,7 @@ public abstract class StructurePlacement { - chunkX, chunkZ, su.plo.matter.Globals.Salt.UNDEFINED, salt - ); - } else { -- worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); - } - // Leaf end - Matter - Feature Secure Seed -@@ -134,7 +134,7 @@ public abstract class StructurePlacement { - } - - private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - if (saltOverride == null) { // Paper - Add missing structure set seed configs - worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ); - // Paper start - Add missing structure set seed configs -@@ -146,7 +146,7 @@ public abstract class StructurePlacement { - } - - private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs - return worldgenRandom.nextFloat() < frequency; - } -@@ -154,7 +154,7 @@ public abstract class StructurePlacement { - private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here - int i = chunkX >> 4; - int j = chunkZ >> 4; -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); // Leaf - Faster random generator - worldgenRandom.setSeed((long)(i ^ j << 4) ^ seed); - worldgenRandom.nextInt(); - return worldgenRandom.nextInt((int)(1.0F / frequency)) == 0; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java -index 9693641e4129458d94f78a487d66d27fe35cd94e..019f9fb994a1836b73a68cfc9e120515884fba1e 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/structures/OceanMonumentStructure.java -@@ -55,7 +55,7 @@ public class OceanMonumentStructure extends Structure { - if (pieces.isEmpty()) { - return pieces; - } else { -- WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed())); -+ WorldgenRandom worldgenRandom = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(RandomSupport.generateUniqueSeed()) : new LegacyRandomSource(RandomSupport.generateUniqueSeed())); // Leaf - Faster random generator - worldgenRandom.setLargeFeatureSeed(worldSeed, pos.x, pos.z); - StructurePiece structurePiece = pieces.pieces().get(0); - BoundingBox boundingBox = structurePiece.getBoundingBox(); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java -index 64bddc5c2722f7d2a18c31dc654547907b663710..b7302317d47a13c153b7c9fea993635c8f347ef4 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinSimplexNoise.java -@@ -43,7 +43,7 @@ public class PerlinSimplexNoise { - - if (j > 0) { - long n = (long)(simplexNoise.getValue(simplexNoise.xo, simplexNoise.yo, simplexNoise.zo) * 9.223372E18F); -- RandomSource randomSource = new WorldgenRandom(new LegacyRandomSource(n)); -+ RandomSource randomSource = new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(n) : new LegacyRandomSource(n)); // Leaf - Faster random generator - - for (int o = l - 1; o >= 0; o--) { - if (o < k && octaves.contains(l - o)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -index e34060c21755c61228ba91e468b7c92fc4c4cf0c..b45349c38011aca9b55fa9a82438e67b338cff6f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -@@ -95,7 +95,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator { - } - - private static WorldgenRandom getSeededRandom() { -- return new WorldgenRandom(new LegacyRandomSource(0)); -+ return new WorldgenRandom(org.dreeam.leaf.config.modules.opt.FastRNG.worldgenEnabled() ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0) : new LegacyRandomSource(0)); // Leaf - Faster random generator - } - - @Override -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java b/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c0fe3e9dfed1bbf3d1f5848b343928d9b80c443b ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java -@@ -0,0 +1,80 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.LeafConfig; -+ -+import java.util.random.RandomGeneratorFactory; -+ -+public class FastRNG extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".faster-random-generator"; -+ } -+ -+ public static boolean enabled = false; -+ public static boolean enableForWorldgen = false; -+ public static String randomGenerator = "Xoroshiro128PlusPlus"; -+ public static boolean warnForSlimeChunk = true; -+ public static boolean useLegacyForSlimeChunk = false; -+ -+ public static boolean worldgenEnabled() { return enabled && enableForWorldgen; } // Helper function -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Use faster random generator? -+ Requires a JVM that supports RandomGenerator. -+ Some JREs don't support this.""", -+ """ -+ 是否使用更快的随机生成器? -+ 需要支持 RandomGenerator 的 JVM. -+ 一些 JRE 不支持此功能."""); -+ -+ enabled = config.getBoolean(getBasePath() + ".enabled", enabled); -+ randomGenerator = config.getString(getBasePath() + ".random-generator", randomGenerator, -+ config.pickStringRegionBased( -+ """ -+ Which random generator will be used? -+ See https://openjdk.org/jeps/356""", -+ """ -+ 使用什么种类的随机生成器. -+ 请参阅 https://openjdk.org/jeps/356""")); -+ enableForWorldgen = config.getBoolean(getBasePath() + ".enable-for-worldgen", enableForWorldgen, -+ config.pickStringRegionBased( -+ """ -+ Enable faster random generator for world generation. -+ WARNING: This will affect world generation!!!""", -+ """ -+ 是否为世界生成启用更快的随机生成器. -+ 警告: 此项会影响世界生成!!!""")); -+ warnForSlimeChunk = config.getBoolean(getBasePath() + ".warn-for-slime-chunk", warnForSlimeChunk, -+ config.pickStringRegionBased( -+ "Warn if you are not using legacy random source for slime chunk generation.", -+ "是否在没有为史莱姆区块使用原版随机生成器的情况下进行警告.")); -+ useLegacyForSlimeChunk = config.getBoolean(getBasePath() + ".use-legacy-random-for-slime-chunk", useLegacyForSlimeChunk, config.pickStringRegionBased( -+ """ -+ Use legacy random source for slime chunk generation, -+ to follow vanilla behavior.""", -+ """ -+ 是否使用原版随机生成器来生成史莱姆区块.""")); -+ -+ if (enabled) { -+ try { -+ RandomGeneratorFactory.of(randomGenerator); -+ } catch (Exception e) { -+ LeafConfig.LOGGER.error("Faster random generator is enabled but {} is not supported by your JVM, " + -+ "falling back to legacy random source.", randomGenerator); -+ enabled = false; -+ } -+ } -+ -+ if (enabled && warnForSlimeChunk) { -+ LeafConfig.LOGGER.warn("You enabled faster random generator, it will offset location of slime chunk"); -+ LeafConfig.LOGGER.warn("If your server has slime farms or facilities need vanilla slime chunk,"); -+ LeafConfig.LOGGER.warn("set performance.faster-random-generator.use-legacy-random-for-slime-chunk " + -+ "to true to use LegacyRandomSource for slime chunk generation."); -+ LeafConfig.LOGGER.warn("Set performance.faster-random-generator.warn-for-slime-chunk to false to " + -+ "disable this warning."); -+ } -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java b/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java -new file mode 100644 -index 0000000000000000000000000000000000000000..77fe5ab83535156c016a4808969ae76bd6b92288 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java -@@ -0,0 +1,127 @@ -+package org.dreeam.leaf.util.math.random; -+ -+import com.google.common.annotations.VisibleForTesting; -+import net.minecraft.util.Mth; -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.BitRandomSource; -+import net.minecraft.world.level.levelgen.PositionalRandomFactory; -+import org.dreeam.leaf.config.modules.opt.FastRNG; -+ -+import java.util.concurrent.ThreadLocalRandom; -+import java.util.random.RandomGenerator; -+import java.util.random.RandomGeneratorFactory; -+ -+ -+public class FasterRandomSource implements BitRandomSource { -+ private static final int INT_BITS = 48; -+ private static final long SEED_MASK = 0xFFFFFFFFFFFFL; -+ private static final long MULTIPLIER = 25214903917L; -+ private static final long INCREMENT = 11L; -+ private static final RandomGeneratorFactory RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of(FastRNG.randomGenerator); -+ private static final boolean isSplittableGenerator = RANDOM_GENERATOR_FACTORY.isSplittable(); -+ private long seed; -+ private RandomGenerator randomGenerator; -+ public static final FasterRandomSource SHARED_INSTANCE = new FasterRandomSource(ThreadLocalRandom.current().nextLong()); -+ -+ public FasterRandomSource(long seed) { -+ this.seed = seed; -+ this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed); -+ } -+ -+ private FasterRandomSource(long seed, RandomGenerator.SplittableGenerator randomGenerator) { -+ this.seed = seed; -+ this.randomGenerator = randomGenerator; -+ } -+ -+ @Override -+ public final RandomSource fork() { -+ if (isSplittableGenerator) { -+ return new FasterRandomSource(seed, ((RandomGenerator.SplittableGenerator) this.randomGenerator).split()); -+ } -+ return new FasterRandomSource(this.nextLong()); -+ } -+ -+ @Override -+ public final PositionalRandomFactory forkPositional() { -+ return new FasterRandomSourcePositionalRandomFactory(this.seed); -+ } -+ -+ @Override -+ public final void setSeed(long seed) { -+ this.seed = seed; -+ this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed); -+ } -+ -+ @Override -+ public final int next(int bits) { -+ // >>> instead of Mojang's >> fixes MC-239059 -+ return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> INT_BITS - bits); -+ } -+ -+ public static class FasterRandomSourcePositionalRandomFactory implements PositionalRandomFactory { -+ private final long seed; -+ -+ public FasterRandomSourcePositionalRandomFactory(long seed) { -+ this.seed = seed; -+ } -+ -+ @Override -+ public RandomSource at(int x, int y, int z) { -+ long l = Mth.getSeed(x, y, z); -+ long m = l ^ this.seed; -+ return new FasterRandomSource(m); -+ } -+ -+ @Override -+ public RandomSource fromHashOf(String seed) { -+ int i = seed.hashCode(); -+ return new FasterRandomSource((long)i ^ this.seed); -+ } -+ -+ @Override -+ public RandomSource fromSeed(long seed) { -+ return new FasterRandomSource(seed); -+ } -+ -+ @VisibleForTesting -+ @Override -+ public void parityConfigString(StringBuilder info) { -+ info.append("FasterRandomSourcePositionalRandomFactory{").append(this.seed).append("}"); -+ } -+ } -+ -+ @Override -+ public final int nextInt() { -+ return randomGenerator.nextInt(); -+ } -+ -+ @Override -+ public final int nextInt(int bound) { -+ return randomGenerator.nextInt(bound); -+ } -+ -+ @Override -+ public final long nextLong() { -+ return randomGenerator.nextLong(); -+ } -+ -+ @Override -+ public final boolean nextBoolean() { -+ return randomGenerator.nextBoolean(); -+ } -+ -+ @Override -+ public final float nextFloat() { -+ return randomGenerator.nextFloat(); -+ } -+ -+ @Override -+ public final double nextDouble() { -+ return randomGenerator.nextDouble(); -+ } -+ -+ @Override -+ public final double nextGaussian() { -+ return randomGenerator.nextGaussian(); -+ } -+} -diff --git a/src/main/java/su/plo/matter/WorldgenCryptoRandom.java b/src/main/java/su/plo/matter/WorldgenCryptoRandom.java -index fab359afe44c573b8b315115810ddefd85b4d22b..42f4ca3f8ef3b5c4152b4b58e51cdba229ff1a0a 100644 ---- a/src/main/java/su/plo/matter/WorldgenCryptoRandom.java -+++ b/src/main/java/su/plo/matter/WorldgenCryptoRandom.java -@@ -24,7 +24,7 @@ public class WorldgenCryptoRandom extends WorldgenRandom { - private final long[] cachedInternalState = new long[16]; - - public WorldgenCryptoRandom(int x, int z, Globals.Salt typeSalt, long salt) { -- super(new LegacyRandomSource(0L)); -+ super(org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? new org.dreeam.leaf.util.math.random.FasterRandomSource(0L) : new LegacyRandomSource(0L)); - if (typeSalt != null) { - this.setSecureSeed(x, z, typeSalt, salt); - } diff --git a/patches/server/0070-Don-t-save-primed-tnt-entity.patch b/patches/server/0070-Don-t-save-primed-tnt-entity.patch deleted file mode 100644 index 9a0587ae..00000000 --- a/patches/server/0070-Don-t-save-primed-tnt-entity.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nostalfinals -Date: Mon, 29 Apr 2024 23:30:21 +0800 -Subject: [PATCH] Don't save primed tnt entity - - -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 287ba483614e79e78022e703ef891f59f41ac455..bee0b636685097ee53994ec1a6ffbcf6eed87032 100644 ---- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -276,4 +276,11 @@ public class PrimedTnt extends Entity implements TraceableEntity { - return super.interact(player, hand); - } - // Purpur end - Shears can defuse TNT -+ -+ // Leaf start - PMC - Don't save primed tnt entity -+ @Override -+ public boolean shouldBeSaved() { -+ return !org.dreeam.leaf.config.modules.opt.DontSaveEntity.dontSavePrimedTNT && super.shouldBeSaved(); -+ } -+ // Leaf - PMC - Don't save primed tnt entity - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java b/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java -new file mode 100644 -index 0000000000000000000000000000000000000000..43c9fcc5a27d523d00656e058c46c34f36da8b63 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java -@@ -0,0 +1,26 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class DontSaveEntity extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".dont-save-entity"; -+ } -+ -+ public static boolean dontSavePrimedTNT = false; -+ -+ @Override -+ public void onLoaded() { -+ dontSavePrimedTNT = config.getBoolean(getBasePath() + ".dont-save-primed-tnt", dontSavePrimedTNT, -+ config.pickStringRegionBased( -+ """ -+ Disable save primed tnt on chunk unloads. -+ Useful for redstone/technical servers, can prevent machines from being exploded by TNT, -+ when player disconnected caused by Internet issue.""", -+ """ -+ 区块卸载时不保存掉落的方块和激活的 TNT, -+ 可以避免在玩家掉线时机器被炸毁.""")); -+ } -+} diff --git a/patches/server/0071-Don-t-save-falling-block-entity.patch b/patches/server/0071-Don-t-save-falling-block-entity.patch deleted file mode 100644 index 5d244957..00000000 --- a/patches/server/0071-Don-t-save-falling-block-entity.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nostalfinals -Date: Mon, 29 Apr 2024 23:31:25 +0800 -Subject: [PATCH] Don't save falling block entity - - -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 06d9a519e64d4b8b8764b3ad7691ad93b5cee065..e4ad284f57b532c260b9dd25ea32f670fc4c9283 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -438,4 +438,11 @@ public class FallingBlockEntity extends Entity { - this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper - return entity; - } -+ -+ // Leaf start - PMC - Don't save falling block entity -+ @Override -+ public boolean shouldBeSaved() { -+ return !org.dreeam.leaf.config.modules.opt.DontSaveEntity.dontSaveFallingBlock && super.shouldBeSaved(); -+ } -+ // Leaf end - PMC - Don't save falling block entity - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java b/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java -index 43c9fcc5a27d523d00656e058c46c34f36da8b63..28dc5e191a26a4783d9e8c18b2653b9ed3238a88 100644 ---- a/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java -@@ -10,6 +10,7 @@ public class DontSaveEntity extends ConfigModules { - } - - public static boolean dontSavePrimedTNT = false; -+ public static boolean dontSaveFallingBlock = false; - - @Override - public void onLoaded() { -@@ -22,5 +23,6 @@ public class DontSaveEntity extends ConfigModules { - """ - 区块卸载时不保存掉落的方块和激活的 TNT, - 可以避免在玩家掉线时机器被炸毁.""")); -+ dontSaveFallingBlock = config.getBoolean(getBasePath() + ".dont-save-falling-block", dontSaveFallingBlock); - } - } diff --git a/patches/server/0072-Configurable-connection-message.patch b/patches/server/0072-Configurable-connection-message.patch deleted file mode 100644 index 8174d1af..00000000 --- a/patches/server/0072-Configurable-connection-message.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Sun, 2 Jun 2024 01:21:36 +0800 -Subject: [PATCH] Configurable connection message - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4dafba6135115ff6459c36b5792b6954fef0132b..bed0b5ff2c84252bddcedcac30fe0a02252d01bf 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -456,7 +456,7 @@ public abstract class PlayerList { - // Ensure that player inventory is populated with its viewer - player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); - -- PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(bukkitPlayer, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure -+ PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(bukkitPlayer, getJoinMsg(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent), bukkitPlayer)); // Paper - Adventure // Leaf - Configurable connection message - join message - this.cserver.getPluginManager().callEvent(playerJoinEvent); - - if (!player.connection.isAcceptingMessages()) { -@@ -469,7 +469,7 @@ public abstract class PlayerList { - - final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); - -- if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure -+ if (org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinEnabled && jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure // Leaf - Configurable connection message - join message - joinMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(jm); // Paper - Adventure - this.server.getPlayerList().broadcastSystemMessage(joinMessage, false); // Paper - Adventure - } -@@ -717,7 +717,7 @@ public abstract class PlayerList { - entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason - } - -- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - Adventure & Add API for quit reason -+ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), getQuitMsg(leaveMessage, entityplayer.getBukkitEntity()), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason // Leaf - Configurable connection message - quit message - this.cserver.getPluginManager().callEvent(playerQuitEvent); - entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); - -@@ -1784,4 +1784,29 @@ public abstract class PlayerList { - public boolean isAllowCommandsForAllPlayers() { - return this.allowCommandsForAllPlayers; - } -+ -+ // Leaf start - Configurable connection message -+ private net.kyori.adventure.text.Component getJoinMsg(net.kyori.adventure.text.Component defaultJoinMsg, Player player) { -+ if (org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinEnabled) { -+ return "default".equals(org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage) -+ ? defaultJoinMsg -+ : net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage) -+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_name%").replacement(player.getName()).build()) -+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_displayname%").replacement(player.displayName()).build()); -+ } -+ -+ return net.kyori.adventure.text.Component.empty(); -+ } -+ private net.kyori.adventure.text.Component getQuitMsg(net.kyori.adventure.text.Component defaultQuitMsg, Player player) { -+ if (org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitEnabled) { -+ return "default".equals(org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage) -+ ? defaultQuitMsg -+ : net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage) -+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_name%").replacement(player.getName()).build()) -+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("%player_displayname%").replacement(player.displayName()).build()); -+ } -+ -+ return net.kyori.adventure.text.Component.empty(); -+ } -+ // Leaf end - Configurable connection message - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java b/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java -new file mode 100644 -index 0000000000000000000000000000000000000000..72782ef6159a718b4d751a65db92812402cfab61 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java -@@ -0,0 +1,41 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ConnectionMessage extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".connection-message"; -+ } -+ -+ public static boolean joinEnabled = true; -+ public static String joinMessage = "default"; -+ public static boolean quitEnabled = true; -+ public static String quitMessage = "default"; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), """ -+ Connection message, using MiniMessage format, set to "default" to use vanilla join message. -+ available placeholders: -+ %player_name% - player name -+ %player_displayname% - player display name""", -+ """ -+ 自定义加入 & 退出消息 (MiniMessage 格式), 设置为 'default' 将使用原版消息. -+ 可用的内置变量: -+ %player_name% - 玩家名称 -+ %player_displayname% - 玩家显示名称"""); -+ -+ joinEnabled = config.getBoolean(getBasePath() + ".join.enabled", joinEnabled); -+ joinMessage = config.getString(getBasePath() + ".join.message", joinMessage, config.pickStringRegionBased( -+ "Join message of player", -+ "玩家加入服务器时的消息" -+ )); -+ -+ quitEnabled = config.getBoolean(getBasePath() + ".quit.enabled", quitEnabled); -+ quitMessage = config.getString(getBasePath() + ".quit.message", quitMessage, config.pickStringRegionBased( -+ "Quit message of player", -+ "玩家退出服务器时的消息")); -+ } -+} diff --git a/patches/server/0073-Configurable-unknown-command-message.patch b/patches/server/0073-Configurable-unknown-command-message.patch deleted file mode 100644 index 0142297d..00000000 --- a/patches/server/0073-Configurable-unknown-command-message.patch +++ /dev/null @@ -1,184 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Wed, 7 Aug 2024 18:54:01 +0800 -Subject: [PATCH] Configurable unknown command message - - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 11f9ac80dd5e53ae9a43ab5f4e9d3c867ab94b15..2df889e29a915dfc3554385f1de1f14464f4c67f 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -394,33 +394,10 @@ public class Commands { - // Paper start - Add UnknownCommandEvent - final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); - // commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); -- builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage())); -+ final net.kyori.adventure.text.TextComponent message = getUnknownCommandMessage(builder, commandsyntaxexception, label); // Leaf - Configurable unknown command message - // Paper end - Add UnknownCommandEvent -- if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { -- int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); -- MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { -- return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper -- }); -- -- if (i > 10) { -- ichatmutablecomponent.append(CommonComponents.ELLIPSIS); -- } -- -- ichatmutablecomponent.append(commandsyntaxexception.getInput().substring(Math.max(0, i - 10), i)); -- if (i < commandsyntaxexception.getInput().length()) { -- MutableComponent ichatmutablecomponent1 = Component.literal(commandsyntaxexception.getInput().substring(i)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE); - -- ichatmutablecomponent.append((Component) ichatmutablecomponent1); -- } -- -- ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); -- // Paper start - Add UnknownCommandEvent -- // commandlistenerwrapper.sendFailure(ichatmutablecomponent); -- builder -- .append(net.kyori.adventure.text.Component.newline()) -- .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); -- } -- org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build()); -+ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, message); // Leaf - Configurable unknown command message - org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); - if (event.message() != null) { - commandlistenerwrapper.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); -@@ -703,6 +680,88 @@ public class Commands { - }; - } - -+ // Leaf start - Configurable unknown command message -+ private static net.kyori.adventure.text.TextComponent getUnknownCommandMessage( -+ net.kyori.adventure.text.TextComponent.Builder builder, CommandSyntaxException commandsyntaxexception, String label -+ ) { -+ String rawMessage = org.dreeam.leaf.config.modules.misc.UnknownCommandMessage.unknownCommandMessage; -+ -+ if (!"default".equals(rawMessage)) { -+ final String input = commandsyntaxexception.getInput(); -+ final int cursor = commandsyntaxexception.getCursor(); -+ -+ if (rawMessage.contains("") && input != null && cursor >= 0) { -+ final int i = Math.min(input.length(), cursor); -+ final net.kyori.adventure.text.TextComponent.Builder detail = net.kyori.adventure.text.Component.text(); -+ final net.kyori.adventure.text.Component context = net.kyori.adventure.text.Component.translatable("command.context.here") -+ .color(net.kyori.adventure.text.format.NamedTextColor.RED) -+ .decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); -+ final net.kyori.adventure.text.event.ClickEvent event = net.kyori.adventure.text.event.ClickEvent.suggestCommand("/" + label); -+ -+ detail.color(net.kyori.adventure.text.format.NamedTextColor.GRAY); -+ -+ if (i > 10) { -+ detail.append(net.kyori.adventure.text.Component.text("...")); -+ } -+ -+ detail.append(net.kyori.adventure.text.Component.text(input.substring(Math.max(0, i - 10), i))); -+ if (i < input.length()) { -+ net.kyori.adventure.text.Component commandInput = net.kyori.adventure.text.Component.text(input.substring(i)) -+ .color(net.kyori.adventure.text.format.NamedTextColor.RED) -+ .decorate(net.kyori.adventure.text.format.TextDecoration.UNDERLINED); -+ -+ detail.append(commandInput); -+ } -+ -+ detail.append(context); -+ detail.clickEvent(event); -+ -+ builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("detail", detail.build()))); -+ } else { -+ rawMessage = rawMessage.replace("", ""); -+ builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage)); -+ } -+ -+ return builder.build(); -+ } -+ -+ return getVanillaUnknownCommandMessage(builder, commandsyntaxexception, label); -+ } -+ -+ private static net.kyori.adventure.text.TextComponent getVanillaUnknownCommandMessage( -+ net.kyori.adventure.text.TextComponent.Builder builder, CommandSyntaxException commandsyntaxexception, String label -+ ) { -+ builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage())); -+ -+ if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { -+ int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); -+ MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { -+ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper -+ }); -+ -+ if (i > 10) { -+ ichatmutablecomponent.append(CommonComponents.ELLIPSIS); -+ } -+ -+ ichatmutablecomponent.append(commandsyntaxexception.getInput().substring(Math.max(0, i - 10), i)); -+ if (i < commandsyntaxexception.getInput().length()) { -+ MutableComponent ichatmutablecomponent1 = Component.literal(commandsyntaxexception.getInput().substring(i)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE); -+ -+ ichatmutablecomponent.append(ichatmutablecomponent1); -+ } -+ -+ ichatmutablecomponent.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); -+ // Paper start - Add UnknownCommandEvent -+ // commandlistenerwrapper.sendFailure(ichatmutablecomponent); -+ builder -+ .append(net.kyori.adventure.text.Component.newline()) -+ .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); -+ } -+ -+ return builder.build(); -+ } -+ // Leaf end - Configurable unknown command message -+ - public static void validate() { - CommandBuildContext commandbuildcontext = Commands.createValidationContext(VanillaRegistries.createLookup()); - com.mojang.brigadier.CommandDispatcher com_mojang_brigadier_commanddispatcher = (new Commands(Commands.CommandSelection.ALL, commandbuildcontext)).getDispatcher(); -diff --git a/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java b/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java -new file mode 100644 -index 0000000000000000000000000000000000000000..66fbab025b1eae951d5318e3d11df5dd2cc85854 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java -@@ -0,0 +1,23 @@ -+package org.dreeam.leaf.config.modules.misc; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class UnknownCommandMessage extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.MISC.getBaseKeyName() + ".message"; -+ } -+ -+ public static String unknownCommandMessage = ""; -+ -+ @Override -+ public void onLoaded() { -+ unknownCommandMessage = config.getString(getBasePath() + ".unknown-command", unknownCommandMessage, config.pickStringRegionBased(""" -+ Unknown command message, using MiniMessage format, set to "default" to use vanilla message, -+ placeholder: , shows detail of the unknown command information.""", -+ """ -+ 发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息. -+ 变量: , 显示未知命令详细信息.""")); -+ } -+} -diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index 4dbb109d0526afee99b9190fc256585121aac9b5..3de9968bc87b3dc024e423b382eb611135bf1b2c 100644 ---- a/src/main/java/org/spigotmc/SpigotConfig.java -+++ b/src/main/java/org/spigotmc/SpigotConfig.java -@@ -191,7 +191,6 @@ public class SpigotConfig - } - - public static String whitelistMessage; -- public static String unknownCommandMessage; - public static String serverFullMessage; - public static String outdatedClientMessage = "Outdated client! Please use {0}"; - public static String outdatedServerMessage = "Outdated server! I\'m still on {0}"; -@@ -208,7 +207,6 @@ public class SpigotConfig - } - - SpigotConfig.whitelistMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.whitelist", "You are not whitelisted on this server!" ) ); -- SpigotConfig.unknownCommandMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.unknown-command", "Unknown command. Type \"/help\" for help." ) ); - SpigotConfig.serverFullMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.server-full", "The server is full!" ) ); - SpigotConfig.outdatedClientMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.outdated-client", SpigotConfig.outdatedClientMessage ) ); - SpigotConfig.outdatedServerMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.outdated-server", SpigotConfig.outdatedServerMessage ) ); diff --git a/patches/server/0075-Remove-stream-in-BlockBehaviour-cache-blockstate.patch b/patches/server/0075-Remove-stream-in-BlockBehaviour-cache-blockstate.patch deleted file mode 100644 index 80b48099..00000000 --- a/patches/server/0075-Remove-stream-in-BlockBehaviour-cache-blockstate.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Fri, 7 Jun 2024 17:43:43 +0800 -Subject: [PATCH] Remove stream in BlockBehaviour cache blockstate - - -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 dcf2dcece3e995ce4646b931329246be19a4e1c2..9b94d8bf3415734776c81297d5d34eea46ad7e78 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 -@@ -1439,7 +1439,7 @@ public abstract class BlockBehaviour implements FeatureElement { - private static final Direction[] DIRECTIONS = Direction.values(); - private static final int SUPPORT_TYPE_COUNT = SupportType.values().length; - protected final VoxelShape collisionShape; -- protected final boolean largeCollisionShape; -+ protected boolean largeCollisionShape; // Leaf - not final - private final boolean[] faceSturdy; - protected final boolean isCollisionShapeFullBlock; - -@@ -1450,9 +1450,14 @@ public abstract class BlockBehaviour implements FeatureElement { - if (!this.collisionShape.isEmpty() && state.hasOffsetFunction()) { - throw new IllegalStateException(String.format(Locale.ROOT, "%s has a collision shape and an offset type, but is not marked as dynamicShape in its properties.", BuiltInRegistries.BLOCK.getKey(block))); - } else { -- this.largeCollisionShape = Arrays.stream(Direction.Axis.values()).anyMatch((enumdirection_enumaxis) -> { -- return this.collisionShape.min(enumdirection_enumaxis) < 0.0D || this.collisionShape.max(enumdirection_enumaxis) > 1.0D; -- }); -+ // Leaf start - Remove stream -+ for (Direction.Axis axis : Direction.Axis.values()) { -+ if (this.collisionShape.min(axis) < 0.0D || this.collisionShape.max(axis) > 1.0D) { -+ this.largeCollisionShape = true; -+ break; -+ } -+ } -+ // Leaf end - Remove stream - this.faceSturdy = new boolean[BlockBehaviour.BlockStateBase.Cache.DIRECTIONS.length * BlockBehaviour.BlockStateBase.Cache.SUPPORT_TYPE_COUNT]; - Direction[] aenumdirection = BlockBehaviour.BlockStateBase.Cache.DIRECTIONS; - int i = aenumdirection.length; diff --git a/patches/server/0076-Remove-stream-in-entity-visible-effects-filter.patch b/patches/server/0076-Remove-stream-in-entity-visible-effects-filter.patch deleted file mode 100644 index bdd311d6..00000000 --- a/patches/server/0076-Remove-stream-in-entity-visible-effects-filter.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Tue, 17 Sep 2024 02:26:44 -0400 -Subject: [PATCH] Remove stream in entity visible effects filter - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d5395553034c148bdc12b065328f52ef72b9bd86..39c898e0b5ec7203491982ef56cfdcf507d040d7 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1029,7 +1029,15 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - private void updateSynchronizedMobEffectParticles() { -- List list = this.activeEffects.values().stream().filter(MobEffectInstance::isVisible).map(MobEffectInstance::getParticleOptions).toList(); -+ // Leaf start - Remove stream in entity visible effects filter -+ List list = new ArrayList<>(); -+ -+ for (MobEffectInstance effect : this.activeEffects.values()) { -+ if (effect.isVisible()) { -+ list.add(effect.getParticleOptions()); -+ } -+ } -+ // Leaf end - Remove stream in entity visible effects filter - - this.entityData.set(LivingEntity.DATA_EFFECT_PARTICLES, list); - this.entityData.set(LivingEntity.DATA_EFFECT_AMBIENCE_ID, LivingEntity.areAllEffectsAmbient(this.activeEffects.values())); diff --git a/patches/server/0078-Remove-stream-in-trial-spawner-ticking.patch b/patches/server/0078-Remove-stream-in-trial-spawner-ticking.patch deleted file mode 100644 index e27a8614..00000000 --- a/patches/server/0078-Remove-stream-in-trial-spawner-ticking.patch +++ /dev/null @@ -1,82 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Sat, 5 Oct 2024 15:39:15 -0400 -Subject: [PATCH] Remove stream in trial spawner ticking - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java -index 192ee216b617d3533f592e62719b6d75d20b5a96..a979db9095d96698b26a9cb4beb75cf5f73d32e5 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java -@@ -173,17 +173,22 @@ public enum TrialSpawnerState implements StringRepresentable { - } - - private static Optional calculatePositionToSpawnSpawner(ServerLevel world, BlockPos pos, TrialSpawner logic, TrialSpawnerData data) { -- List list = data.detectedPlayers -- .stream() -- .map(world::getPlayerByUUID) -- .filter(Objects::nonNull) -- .filter( -- player -> !player.isCreative() -- && !player.isSpectator() -- && player.isAlive() -- && player.distanceToSqr(pos.getCenter()) <= (double)Mth.square(logic.getRequiredPlayerRange()) -- ) -- .toList(); -+ // Leaf start - Remove stream in trial spawner ticking -+ List list = new java.util.ArrayList<>(); -+ -+ for (UUID uuid : data.detectedPlayers) { -+ Player player = world.getPlayerByUUID(uuid); -+ -+ if (player != null -+ && !player.isCreative() -+ && !player.isSpectator() -+ && player.isAlive() -+ && player.distanceToSqr(pos.getCenter()) <= (double) Mth.square(logic.getRequiredPlayerRange())) { -+ list.add(player); -+ } -+ } -+ // Leaf end - Remove stream in trial spawner ticking -+ - if (list.isEmpty()) { - return Optional.empty(); - } else { -@@ -203,16 +208,29 @@ public enum TrialSpawnerState implements StringRepresentable { - - @Nullable - private static Entity selectEntityToSpawnItemAbove(List players, Set entityUuids, TrialSpawner logic, BlockPos pos, ServerLevel world) { -- Stream stream = entityUuids.stream() -- .map(world::getEntity) -- .filter(Objects::nonNull) -- .filter(entity -> entity.isAlive() && entity.distanceToSqr(pos.getCenter()) <= (double)Mth.square(logic.getRequiredPlayerRange())); -- List list = world.random.nextBoolean() ? stream.toList() : players; -- if (list.isEmpty()) { -- return null; -+ // Leaf start - Remove stream in trial spawner ticking -+ if (world.random.nextBoolean()) { -+ List list = new java.util.ArrayList<>(); -+ for (UUID uuid : entityUuids) { -+ Entity entity = world.getEntity(uuid); -+ if (entity != null && entity.isAlive() && entity.distanceToSqr(pos.getCenter()) <= (double) Mth.square(logic.getRequiredPlayerRange())) { -+ list.add(entity); -+ } -+ } -+ -+ if (list.isEmpty()) { -+ return null; -+ } else { -+ return list.size() == 1 ? list.getFirst() : Util.getRandom(list, world.random); -+ } - } else { -- return list.size() == 1 ? list.getFirst() : Util.getRandom(list, world.random); -+ if (players.isEmpty()) { -+ return null; -+ } else { -+ return players.size() == 1 ? players.getFirst() : Util.getRandom(players, world.random); -+ } - } -+ // Leaf end - Remove stream in trial spawner ticking - } - - private boolean timeToSpawnItemSpawner(ServerLevel world, TrialSpawnerData data) { diff --git a/patches/server/0079-Remove-stream-in-Brain.patch b/patches/server/0079-Remove-stream-in-Brain.patch deleted file mode 100644 index 22dd9bc6..00000000 --- a/patches/server/0079-Remove-stream-in-Brain.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Sat, 26 Oct 2024 00:06:04 +0800 -Subject: [PATCH] Remove stream in Brain - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java -index 2ae8c9d56d88987b750e57025d313cfa9300c7e4..88ba505fc5ca084317aaf6be402472ccd42413d8 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/Brain.java -+++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java -@@ -69,13 +69,22 @@ public class Brain { - mutableObject.setValue( - (new MapCodec>() { - public Stream keys(DynamicOps dynamicOps) { -- return memoryModules.stream() -- .flatMap( -- memoryType -> memoryType.getCodec() -- .map(codec -> BuiltInRegistries.MEMORY_MODULE_TYPE.getKey((MemoryModuleType)memoryType)) -- .stream() -- ) -- .map(id -> dynamicOps.createString(id.toString())); -+ // Leaf start - Remove stream in Brain -+ List results = new java.util.ArrayList<>(); -+ -+ for (MemoryModuleType memoryType : memoryModules) { -+ final Optional codec = memoryType.getCodec(); -+ -+ if (codec.isPresent()) { -+ final net.minecraft.resources.ResourceLocation id = BuiltInRegistries.MEMORY_MODULE_TYPE.getKey(memoryType); -+ final T ops = dynamicOps.createString(id.toString()); -+ -+ results.add(ops); -+ } -+ } -+ -+ return results.stream(); -+ // Leaf end - Remove stream in Brain - } - - public DataResult> decode(DynamicOps dynamicOps, MapLike mapLike) { -@@ -110,7 +119,8 @@ public class Brain { - } - - public RecordBuilder encode(Brain brain, DynamicOps dynamicOps, RecordBuilder recordBuilder) { -- brain.memories().forEach(entry -> entry.serialize(dynamicOps, recordBuilder)); -+ brain.serializeMemories(dynamicOps, recordBuilder); // Leaf - Remove stream in Brain -+ - return recordBuilder; - } - }) -@@ -152,8 +162,28 @@ public class Brain { - } - - Stream> memories() { -- return this.memories.entrySet().stream().map(entry -> Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue())); -+ // Leaf start - Remove stream in Brain -+ return memoriesList().stream(); -+ } -+ -+ List> memoriesList() { -+ List> result = new java.util.ArrayList<>(); -+ -+ for (Entry, Optional>> entry : this.memories.entrySet()) { -+ result.add(Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue())); -+ } -+ -+ return result; -+ } -+ -+ void serializeMemories(DynamicOps dynamicOps, RecordBuilder recordBuilder) { -+ for (Entry, Optional>> entry : this.memories.entrySet()) { -+ final Brain.MemoryValue result = Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue()); -+ -+ result.serialize(dynamicOps, recordBuilder); -+ } - } -+ // Leaf end - Remove stream in Brain - - public boolean hasMemoryValue(MemoryModuleType type) { - return this.checkMemory(type, MemoryStatus.VALUE_PRESENT); diff --git a/patches/server/0080-Remove-stream-in-BehaviorUtils.patch b/patches/server/0080-Remove-stream-in-BehaviorUtils.patch deleted file mode 100644 index ec585db4..00000000 --- a/patches/server/0080-Remove-stream-in-BehaviorUtils.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Sat, 26 Oct 2024 00:56:24 +0800 -Subject: [PATCH] Remove stream in BehaviorUtils - -Dreeam TODO: Check this - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java -index 25dacd924f182d197bcbdcd7c1d8caa482fd8d37..3f8a90e3fab1ace796d2433b90a2472a7a143968 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java -@@ -121,12 +121,20 @@ public class BehaviorUtils { - - public static SectionPos findSectionClosestToVillage(ServerLevel world, SectionPos center, int radius) { - int j = world.sectionsToVillage(center); -- Stream stream = SectionPos.cube(center, radius).filter((sectionposition1) -> { // CraftBukkit - decompile error -- return world.sectionsToVillage(sectionposition1) < j; -- }); -+ // Leaf start - Remove stream in BehaviorUtils -+ SectionPos closestSection = center; -+ int closestDistance = j; - - Objects.requireNonNull(world); -- return (SectionPos) stream.min(Comparator.comparingInt(world::sectionsToVillage)).orElse(center); -+ for (SectionPos sectionPosition : SectionPos.cube(center, radius).toList()) { -+ int distance = world.sectionsToVillage(sectionPosition); -+ if (distance < closestDistance) { -+ closestDistance = distance; -+ closestSection = sectionPosition; -+ } -+ } -+ return closestSection; -+ // Leaf end - Remove stream in BehaviorUtils - } - - public static boolean isWithinAttackRange(Mob mob, LivingEntity target, int rangedWeaponReachReduction) { diff --git a/patches/server/0081-Remove-stream-in-YieldJobSite.patch b/patches/server/0081-Remove-stream-in-YieldJobSite.patch deleted file mode 100644 index 4704a868..00000000 --- a/patches/server/0081-Remove-stream-in-YieldJobSite.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Sat, 26 Oct 2024 14:04:54 +0800 -Subject: [PATCH] Remove stream in YieldJobSite - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java b/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java -index d1a9b62d3304916275dd6b4c4e783cf1563b5e21..572d266c3af268a5dbbc31272b5aa15d46442408 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java -@@ -38,24 +38,25 @@ public class YieldJobSite { - if (optional.isEmpty()) { - return true; - } else { -- context.>get(mobs) -- .stream() -- .filter(mob -> mob instanceof Villager && mob != entity) -- .map(villager -> (Villager)villager) -- .filter(LivingEntity::isAlive) -- .filter(villager -> nearbyWantsJobsite(optional.get(), villager, blockPos)) -- .findFirst() -- .ifPresent(villager -> { -- walkTarget.erase(); -- lookTarget.erase(); -- potentialJobSite.erase(); -- if (villager.getBrain().getMemory(MemoryModuleType.JOB_SITE).isEmpty()) { -- BehaviorUtils.setWalkAndLookTargetMemories(villager, blockPos, speed, 1); -- villager.getBrain() -- .setMemory(MemoryModuleType.POTENTIAL_JOB_SITE, GlobalPos.of(world.dimension(), blockPos)); -- DebugPackets.sendPoiTicketCountPacket(world, blockPos); -+ // Leaf start - Remove stream in YieldJobSite -+ List mobsList = context.get(mobs); -+ for (LivingEntity mob : mobsList) { -+ if (mob instanceof Villager villager && mob != entity && villager.isAlive()) { -+ if (nearbyWantsJobsite(optional.get(), villager, blockPos)) { -+ walkTarget.erase(); -+ lookTarget.erase(); -+ potentialJobSite.erase(); -+ -+ if (villager.getBrain().getMemory(MemoryModuleType.JOB_SITE).isEmpty()) { -+ BehaviorUtils.setWalkAndLookTargetMemories(villager, blockPos, speed, 1); -+ villager.getBrain().setMemory(MemoryModuleType.POTENTIAL_JOB_SITE, GlobalPos.of(world.dimension(), blockPos)); -+ DebugPackets.sendPoiTicketCountPacket(world, blockPos); -+ } -+ break; - } -- }); -+ } -+ } -+ // Leaf end - Remove stream in YieldJobSite - return true; - } - } diff --git a/patches/server/0083-Remove-stream-in-GolemSensor.patch b/patches/server/0083-Remove-stream-in-GolemSensor.patch deleted file mode 100644 index b7de8985..00000000 --- a/patches/server/0083-Remove-stream-in-GolemSensor.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Tue, 9 Nov 2077 00:00:00 +0800 -Subject: [PATCH] Remove stream in GolemSensor - -Stream operations in GolemSensor is really expensive and takes -up 80% time per method call. -Before: 192ms -After: 17ms - -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java -index 03485bcf2f25942bd2550b4855bf16bea9ef88b6..c7c8a1ae9ab8f406f5a807e5a56c110c9e0e832d 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/GolemSensor.java -@@ -34,7 +34,15 @@ public class GolemSensor extends Sensor { - public static void checkForNearbyGolem(LivingEntity entity) { - Optional> optional = entity.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES); - if (!optional.isEmpty()) { -- boolean bl = optional.get().stream().anyMatch(seenEntity -> seenEntity.getType().equals(EntityType.IRON_GOLEM)); -+ // Leaf start - Remove stream in GolemSensor -+ boolean bl = false; -+ for (LivingEntity seenEntity : optional.get()) { -+ if (seenEntity.getType().equals(EntityType.IRON_GOLEM)) { -+ bl = true; -+ break; -+ } -+ } -+ // Leaf end - Remove stream in GolemSensor - if (bl) { - golemDetected(entity); - } diff --git a/patches/server/0084-Remove-stream-in-GateBehavior.patch b/patches/server/0084-Remove-stream-in-GateBehavior.patch deleted file mode 100644 index c95810ad..00000000 --- a/patches/server/0084-Remove-stream-in-GateBehavior.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Tue, 9 Nov 2077 00:00:00 +0800 -Subject: [PATCH] Remove stream in GateBehavior - - -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 d4581127366736c54f74e4ef7479236b18fb487d..67aa59deebdf47e53d9f6949b26c9b313de45035 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 -@@ -73,9 +73,20 @@ public class GateBehavior implements BehaviorControl - } - } - // Paper end - Perf: Remove streams from hot code -- if (this.behaviors.stream().noneMatch(task -> task.getStatus() == Behavior.Status.RUNNING)) { -+ -+ // Leaf start - Remove more streams in GateBehavior -+ boolean hasRunningTask = false; -+ for (final BehaviorControl task : this.behaviors) { -+ if (task.getStatus() == Behavior.Status.RUNNING) { -+ hasRunningTask = true; -+ break; -+ } -+ } -+ -+ if (!hasRunningTask) { - this.doStop(world, entity, time); - } -+ // Leaf end - Remove more streams in GateBehavior - } - - @Override diff --git a/patches/server/0085-Remove-stream-in-updateFluidOnEyes.patch b/patches/server/0085-Remove-stream-in-updateFluidOnEyes.patch deleted file mode 100644 index 48d14562..00000000 --- a/patches/server/0085-Remove-stream-in-updateFluidOnEyes.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Mon, 11 Nov 2024 16:55:50 -0500 -Subject: [PATCH] Remove stream in updateFluidOnEyes - - -diff --git a/src/main/java/net/minecraft/core/Holder.java b/src/main/java/net/minecraft/core/Holder.java -index e91c4e26c25980645941ca8fbdcc3a9d02e31063..006044af19cb70f6bd1dbda31bc13e60cd37a0fb 100644 ---- a/src/main/java/net/minecraft/core/Holder.java -+++ b/src/main/java/net/minecraft/core/Holder.java -@@ -29,6 +29,8 @@ public interface Holder { - - Stream> tags(); - -+ Set> tagsAsSet(); // Leaf - Remove stream in updateFluidOnEyes -+ - Either, T> unwrap(); - - Optional> unwrapKey(); -@@ -105,6 +107,13 @@ public interface Holder { - public Stream> tags() { - return Stream.of(); - } -+ -+ // Leaf start - Remove stream in updateFluidOnEyes -+ @Override -+ public Set> tagsAsSet() { -+ return Set.of(); -+ } -+ // Leaf end - Remove stream in updateFluidOnEyes - } - - public static enum Kind { -@@ -238,6 +247,13 @@ public interface Holder { - return this.boundTags().stream(); - } - -+ // Leaf start - Remove stream in updateFluidOnEyes -+ @Override -+ public Set> tagsAsSet() { -+ return this.tags; -+ } -+ // Leaf end - Remove stream in updateFluidOnEyes -+ - @Override - public String toString() { - return "Reference{" + this.key + "=" + this.value + "}"; -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8ac2b3a17d1a9df6695c8034b71544704be23036..d203ab8cd02432e05d6e3b8766d5a733e570173d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2096,11 +2096,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - double d1 = (double) ((float) blockposition.getY() + fluid.getHeight(this.level(), blockposition)); - - if (d1 > d0) { -- Stream stream = fluid.getTags(); -- Set set = this.fluidOnEyes; -+ // Leaf start - Remove stream in updateFluidOnEyes -+ Set> tagsSet = fluid.getTagsAsSet(); -+ Set> set = this.fluidOnEyes; - -- Objects.requireNonNull(this.fluidOnEyes); -- stream.forEach(set::add); -+ Objects.requireNonNull(set); -+ set.addAll(tagsSet); -+ // Leaf end - Remove stream in updateFluidOnEyes - } - - } -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 2d50d72bf026d0cf9c546a3c6fc1859379bfd805..ae8b7e2b185918e197de211a3f541bc0dd1943c3 100644 ---- a/src/main/java/net/minecraft/world/level/material/FluidState.java -+++ b/src/main/java/net/minecraft/world/level/material/FluidState.java -@@ -158,4 +158,10 @@ public final class FluidState extends StateHolder implements - public Stream> getTags() { - return this.owner.builtInRegistryHolder().tags(); - } -+ -+ // Leaf start - Remove stream in updateFluidOnEyes -+ public java.util.Set> getTagsAsSet() { -+ return this.owner.builtInRegistryHolder().tagsAsSet(); -+ } -+ // Leaf end - Remove stream in updateFluidOnEyes - } diff --git a/patches/server/0091-Reduce-worldgen-allocations.patch b/patches/server/0091-Reduce-worldgen-allocations.patch deleted file mode 100644 index f11c4343..00000000 --- a/patches/server/0091-Reduce-worldgen-allocations.patch +++ /dev/null @@ -1,137 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Fri, 14 Jun 2024 23:19:55 +0800 -Subject: [PATCH] Reduce worldgen allocations - -This change optimizes the way SurfaceRules update their biome supplier,avoiding unnecessary object creations and thus reducing memory allocations -during world generation. The update method now reuses the existing PositionalBiomeGetter object if it's already present, otherwise it -initializes a new one. -Additionally, the tryApply method in SurfaceRules now avoids iterator -allocation by directly accessing the rules list, which further contributes -to reducing garbage collection pressure during world generation. - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseChunk.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseChunk.java -index eeb0411b3bd8ffa6e9a1ba303f2251aed4e2c8a2..f870b838f4ee8f325cd68332a4483aaeac3eb2e0 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseChunk.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseChunk.java -@@ -370,7 +370,14 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct - } - - protected DensityFunction wrap(DensityFunction function) { -- return this.wrapped.computeIfAbsent(function, this::wrapNew); -+ // Leaf start - Avoid lambda allocation -+ DensityFunction func = this.wrapped.get(function); -+ if (func == null) { -+ func = this.wrapNew(function); -+ this.wrapped.put(function, func); -+ } -+ return func; -+ // Leaf end - Avoid lambda allocation - } - - private DensityFunction wrapNew(DensityFunction function) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/SurfaceRules.java b/src/main/java/net/minecraft/world/level/levelgen/SurfaceRules.java -index 38428ba2c522108f4f9f7986bc3535d1232ac1f8..02f143b41350660486de79e240259245175bf7dc 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/SurfaceRules.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/SurfaceRules.java -@@ -314,8 +314,14 @@ public class SurfaceRules { - } - - protected void updateY(int stoneDepthAbove, int stoneDepthBelow, int fluidHeight, int blockX, int blockY, int blockZ) { -- this.lastUpdateY++; -- this.biome = Suppliers.memoize(() -> this.biomeGetter.apply(this.pos.set(blockX, blockY, blockZ))); -+ // Leaf start - Reuse supplier object instead of creating new ones every time -+ ++this.lastUpdateY; -+ Supplier> getter = this.biome; -+ if (getter == null) { -+ this.biome = getter = new org.dreeam.leaf.util.biome.PositionalBiomeGetter(this.biomeGetter, this.pos); -+ } -+ ((org.dreeam.leaf.util.biome.PositionalBiomeGetter)getter).update(blockX, blockY, blockZ); -+ // Leaf end - Reuse supplier object instead of creating new ones every time - this.blockY = blockY; - this.waterHeight = fluidHeight; - this.stoneDepthBelow = stoneDepthBelow; -@@ -585,8 +591,12 @@ public class SurfaceRules { - @Nullable - @Override - public BlockState tryApply(int x, int y, int z) { -- for (SurfaceRules.SurfaceRule surfaceRule : this.rules) { -- BlockState blockState = surfaceRule.tryApply(x, y, z); -+ // Leaf start - Avoid iterator allocation -+ int size = this.rules.size(); -+ //noinspection ForLoopReplaceableByForEach -+ for (int i = 0; i < size; i++) { -+ BlockState blockState = this.rules.get(i).tryApply(x, y, z); -+ // Leaf end - Avoid iterator allocation - if (blockState != null) { - return blockState; - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java b/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java -index 0e6dfe2635ea5f5e410049b05f94f5083b2f18a4..75a378bea8fdb03b6864fad49bc38fee83523bd9 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/material/MaterialRuleList.java -@@ -9,13 +9,16 @@ public record MaterialRuleList(NoiseChunk.BlockStateFiller[] materialRuleList) i - @Nullable - @Override - public BlockState calculate(DensityFunction.FunctionContext pos) { -- for (NoiseChunk.BlockStateFiller blockStateFiller : this.materialRuleList) { -- BlockState blockState = blockStateFiller.calculate(pos); -- if (blockState != null) { -- return blockState; -- } -+ // Leaf start - Avoid iterator allocation -+ BlockState blockState = null; -+ int length = this.materialRuleList.length; -+ -+ for (int i = 0; blockState == null && i < length; i++) { -+ NoiseChunk.BlockStateFiller blockStateFiller = this.materialRuleList[i]; -+ blockState = blockStateFiller.calculate(pos); - } - -- return null; -+ return blockState; -+ // Leaf end - Avoid iterator allocation - } - } -diff --git a/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java b/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3683c61ebc673cf5d2f8197fffdf44270f1287f4 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java -@@ -0,0 +1,36 @@ -+package org.dreeam.leaf.util.biome; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Holder; -+import net.minecraft.world.level.biome.Biome; -+ -+import java.util.function.Function; -+import java.util.function.Supplier; -+ -+public class PositionalBiomeGetter implements Supplier> { -+ private final Function> biomeGetter; -+ private final BlockPos.MutableBlockPos pos; -+ private int nextX, nextY, nextZ; -+ private volatile Holder curBiome; -+ -+ public PositionalBiomeGetter(Function> biomeGetter, BlockPos.MutableBlockPos pos) { -+ this.biomeGetter = biomeGetter; -+ this.pos = pos; -+ } -+ -+ public void update(int nextX, int nextY, int nextZ) { -+ this.nextX = nextX; -+ this.nextY = nextY; -+ this.nextZ = nextZ; -+ this.curBiome = null; -+ } -+ -+ @Override -+ public Holder get() { -+ Holder biome = curBiome; -+ if (biome == null) { -+ curBiome = biome = biomeGetter.apply(pos.set(nextX, nextY, nextZ)); -+ } -+ return biome; -+ } -+} diff --git a/patches/server/0093-Do-not-place-player-if-the-server-is-full.patch b/patches/server/0093-Do-not-place-player-if-the-server-is-full.patch deleted file mode 100644 index eb9b9974..00000000 --- a/patches/server/0093-Do-not-place-player-if-the-server-is-full.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Mon, 24 Jun 2024 10:49:04 +0800 -Subject: [PATCH] Do not place player if the server is full - -Fix https://github.com/PaperMC/Paper/issues/10668 - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index bed0b5ff2c84252bddcedcac30fe0a02252d01bf..2ccecc0a81c62d96978906aec2124563f3be7541 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -378,6 +378,13 @@ public abstract class PlayerList { - return; - } - // Gale end - MultiPaper - do not place player in world if kicked before being spawned in -+ // Leaf start - Do not place player if the server is full - copied from canPlayerLogin -+ if (org.dreeam.leaf.config.modules.fixes.DontPlacePlayerIfFull.enabled && this.realPlayers.size() >= this.maxPlayers && !(player.getBukkitEntity().hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur // Leaves - skip -+ connection.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage))); -+ //playerconnection.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); -+ return; -+ } -+ // Leaf end - Do not place player if the server is full - copied from canPlayerLogin - - Location loc = ev.getSpawnLocation(); - worldserver1 = ((CraftWorld) loc.getWorld()).getHandle(); -@@ -878,7 +885,7 @@ public abstract class PlayerList { - event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure - } else { - // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; -- if (this.realPlayers.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur // Leaves - only real player -+ if (this.realPlayers.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur // Leaves - only real player // Leaf - Do not place player if the server is full - diff on change - event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure - } - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java b/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ae21bcac9d57b760d751d25f2bdb746e4bd636f1 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/fixes/DontPlacePlayerIfFull.java -@@ -0,0 +1,25 @@ -+package org.dreeam.leaf.config.modules.fixes; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class DontPlacePlayerIfFull extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.FIXES.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".dont-place-player-if-server-full", enabled, config.pickStringRegionBased(""" -+ Don't let player join server if the server is full. -+ If enable this, you should use 'purpur.joinfullserver' permission instead of -+ PlayerLoginEvent#allow to let player join full server.""", -+ """ -+ 服务器已满时禁止玩家加入. -+ 开启后需使用权限 'purpur.joinfullserver' 而不是 -+ PlayerLoginEvent#allow 让玩家进入已满的服务器.""")); -+ } -+} diff --git a/patches/server/0110-Nitori-Async-playerdata-Save.patch b/patches/server/0110-Nitori-Async-playerdata-Save.patch deleted file mode 100644 index a869fac1..00000000 --- a/patches/server/0110-Nitori-Async-playerdata-Save.patch +++ /dev/null @@ -1,119 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Fri, 23 Aug 2024 22:04:20 -0400 -Subject: [PATCH] Nitori: Async playerdata Save - -Original license: GPL v3 -Original project: https://github.com/Gensokyo-Reimagined/Nitori - -diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java -index cdca5ae69991cc068bfbc0686b5defb3604a5440..5ab705731209fd4d4c20cd342ee7bf1bf26f3b0c 100644 ---- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java -+++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java -@@ -605,7 +605,11 @@ public class LevelStorageSource { - CompoundTag nbttagcompound2 = new CompoundTag(); - - nbttagcompound2.put("Data", nbttagcompound1); -- this.saveLevelData(nbttagcompound2); -+ -+ // Leaf start - Nitori - Async playerdata save -+ Runnable runnable = () -> this.saveLevelData(nbttagcompound2); -+ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); -+ // Leaf end - Nitori - Async playerdata save - } - - private void saveLevelData(CompoundTag nbt) { -@@ -702,7 +706,11 @@ public class LevelStorageSource { - CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(this.levelDirectory.dataFile()); - - nbtProcessor.accept(nbttagcompound.getCompound("Data")); -- this.saveLevelData(nbttagcompound); -+ -+ // Leaf start - Nitori - Async playerdata save -+ Runnable runnable = () -> this.saveLevelData(nbttagcompound); -+ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); -+ // Leaf end - Nitori - Async playerdata save - } - - public long makeWorldBackup() throws IOException { -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 b148cf247acdd36f856d0495cde4cc5ad32b5a2f..e825d9e573a38531f5a3b3f9cdccc24570953015 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -36,6 +36,13 @@ public class PlayerDataStorage { - - public void save(Player player) { - if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot -+ -+ // Leaf start - Nitori - Async playerdata save -+ Runnable runnable = () -> save0(player); -+ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); -+ } -+ private void save0(Player player) { -+ // Leaf end - Nitori - Async playerdata save - try { - CompoundTag nbttagcompound = player.saveWithoutId(new CompoundTag()); - Path path = this.playerDir.toPath(); -diff --git a/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java b/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5e661cdb936c85c566b33d8645f84eb80f50d9d ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java -@@ -0,0 +1,23 @@ -+package org.dreeam.leaf.async; -+ -+import net.minecraft.Util; -+import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave; -+ -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.ExecutorService; -+ -+public class AsyncPlayerDataSaving { -+ -+ private AsyncPlayerDataSaving() { -+ } -+ -+ public static void saveAsync(Runnable runnable) { -+ if (!AsyncPlayerDataSave.enabled) { -+ runnable.run(); -+ return; -+ } -+ -+ ExecutorService ioExecutor = Util.backgroundExecutor().service(); -+ CompletableFuture.runAsync(runnable, ioExecutor); -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java -new file mode 100644 -index 0000000000000000000000000000000000000000..148a75e8aa545145fe25638623145b0b585462b6 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java -@@ -0,0 +1,28 @@ -+package org.dreeam.leaf.config.modules.async; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.annotations.Experimental; -+ -+public class AsyncPlayerDataSave extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save"; -+ } -+ -+ @Experimental -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ config.addCommentRegionBased(getBasePath(), -+ """ -+ **Experimental feature, may have data lost in some circumstances!** -+ Make PlayerData saving asynchronously.""", -+ """ -+ **实验性功能, 在部分场景下可能丢失玩家数据!** -+ 异步保存玩家数据."""); -+ -+ enabled = config().getBoolean(getBasePath() + ".enabled", enabled); -+ } -+} diff --git a/patches/server/0116-Further-reduce-memory-footprint-of-CompoundTag.patch b/patches/server/0116-Further-reduce-memory-footprint-of-CompoundTag.patch deleted file mode 100644 index f9486173..00000000 --- a/patches/server/0116-Further-reduce-memory-footprint-of-CompoundTag.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Sat, 26 Oct 2024 22:38:30 +0800 -Subject: [PATCH] Further reduce memory footprint of CompoundTag - -Original license: GPLv3 -Original project: https://github.com/embeddedt/ModernFix - -diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java -index ea48637234fdb1e5f54342590def30e11b6a5df0..a7a5e1c70d1e4f7df6533d0531a200fe50b38b7a 100644 ---- a/src/main/java/net/minecraft/nbt/CompoundTag.java -+++ b/src/main/java/net/minecraft/nbt/CompoundTag.java -@@ -49,7 +49,7 @@ public class CompoundTag implements Tag { - - private static CompoundTag loadCompound(DataInput input, NbtAccounter tracker) throws IOException { - tracker.accountBytes(48L); -- it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag -+ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new org.dreeam.leaf.util.map.StringCanonizingOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag // Leaf - Further reduce memory footprint of CompoundTag - - byte b; - while ((b = input.readByte()) != 0) { -@@ -166,7 +166,7 @@ public class CompoundTag implements Tag { - } - - public CompoundTag() { -- this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - Reduce memory footprint of CompoundTag -+ this(new org.dreeam.leaf.util.map.StringCanonizingOpenHashMap<>(8, 0.8f)); // Paper - Reduce memory footprint of CompoundTag // Leaf - Further reduce memory footprint of CompoundTag - } - - @Override -@@ -497,6 +497,11 @@ public class CompoundTag implements Tag { - - @Override - public CompoundTag copy() { -+ // Leaf start - Further reduce memory footprint of CompoundTag -+ if (this.tags instanceof org.dreeam.leaf.util.map.StringCanonizingOpenHashMap stringCanonizingTags) { -+ return new CompoundTag(org.dreeam.leaf.util.map.StringCanonizingOpenHashMap.deepCopy(stringCanonizingTags, Tag::copy)); -+ } -+ // Leaf end - Further reduce memory footprint of CompoundTag - // Paper start - Reduce memory footprint of CompoundTag - 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(); -diff --git a/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java b/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..72538e772102e4ce9fca36eaaba44de1b35afe30 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java -@@ -0,0 +1,58 @@ -+package org.dreeam.leaf.util.map; -+ -+import com.github.benmanes.caffeine.cache.Interner; -+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ObjectIterator; -+ -+import java.util.Map; -+import java.util.function.Function; -+ -+/** -+ * Backed by an {@link Object2ObjectOpenHashMap}, with string keys interned to save memory. -+ */ -+public class StringCanonizingOpenHashMap extends Object2ObjectOpenHashMap { -+ private static final Interner KEY_INTERNER = Interner.newWeakInterner(); -+ -+ private static String intern(String key) { -+ return key != null ? KEY_INTERNER.intern(key) : null; -+ } -+ -+ public StringCanonizingOpenHashMap() { -+ super(); -+ } -+ -+ public StringCanonizingOpenHashMap(int expectedSize) { -+ super(expectedSize); -+ } -+ -+ public StringCanonizingOpenHashMap(int expectedSize, float loadFactor) { -+ super(expectedSize, loadFactor); -+ } -+ -+ @Override -+ public T put(String key, T value) { -+ return super.put(intern(key), value); -+ } -+ -+ @Override -+ public void putAll(Map m) { -+ if (m.isEmpty()) return; -+ Map tmp = new Object2ObjectOpenHashMap<>(m.size()); -+ m.forEach((k, v) -> tmp.put(intern(k), v)); -+ super.putAll(tmp); -+ } -+ -+ private void putWithoutInterning(String key, T value) { -+ super.put(key, value); -+ } -+ -+ public static StringCanonizingOpenHashMap deepCopy(StringCanonizingOpenHashMap incomingMap, Function deepCopier) { -+ StringCanonizingOpenHashMap newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f); -+ ObjectIterator> iterator = incomingMap.object2ObjectEntrySet().fastIterator(); -+ while (iterator.hasNext()) { -+ Map.Entry entry = iterator.next(); -+ newMap.putWithoutInterning(entry.getKey(), deepCopier.apply(entry.getValue())); -+ } -+ return newMap; -+ } -+} -\ No newline at end of file diff --git a/patches/server/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch b/patches/server/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch deleted file mode 100644 index c2449cfa..00000000 --- a/patches/server/0118-EMC-Don-t-use-snapshots-for-TileEntity-getOwner.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 7 Nov 2017 00:01:04 -0500 -Subject: [PATCH] EMC: Don't use snapshots for TileEntity::getOwner - -Original license: MIT -Original project: https://github.com/starlis/empirecraft - -Also see Leaf's EMC-Default-don-t-use-blockstate-snapshots.patch - -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 eaa6ece956f90632831f0558924eaf18680a252b..8a20b0ef9ea684a4a5e79b42f11834e3fe78b4fd 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 -@@ -399,7 +399,7 @@ public abstract class BlockEntity { - // CraftBukkit start - add method - public InventoryHolder getOwner() { - // Paper start -- return getOwner(true); -+ return getOwner(org.dreeam.leaf.config.modules.opt.TileEntitySnapshotCreation.enabled); // Leaf - EMC - don't use snapshots - } - public InventoryHolder getOwner(boolean useSnapshot) { - // Paper end -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java b/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1c0d34c15c12f9587f518de17e3a3d5289e21f9c ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/TileEntitySnapshotCreation.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class TileEntitySnapshotCreation extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".create-snapshot-on-retrieving-blockstate", enabled); -+ } -+} diff --git a/patches/server/0121-TT20-Lag-compensation.patch b/patches/server/0121-TT20-Lag-compensation.patch deleted file mode 100644 index 5697a6d5..00000000 --- a/patches/server/0121-TT20-Lag-compensation.patch +++ /dev/null @@ -1,220 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> -Date: Mon, 1 Nov 2077 00:00:00 +0800 -Subject: [PATCH] TT20 Lag compensation - -Original license: AGPL-3.0 -Original project: https://github.com/snackbag/TT20 - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 244db7e0ae0eb785deb94558eff74714d979d3de..59d5b758471fc00b09ecf84dc1757f543866d480 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1668,6 +1668,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 ? newTicks : 1; -+ else return newTicks; -+ } -+ -+ public static int tt20(int ticks, boolean limitZero) { -+ int newTicks = (int) Math.ceil(rawTT20(ticks)); -+ -+ if (limitZero) return newTicks > 0 ? newTicks : 1; -+ else return newTicks; -+ } -+ -+ public static double tt20(double ticks, boolean limitZero) { -+ double newTicks = rawTT20(ticks); -+ -+ if (limitZero) return newTicks > 0 ? newTicks : 1; -+ else return newTicks; -+ } -+ -+ public static double rawTT20(double ticks) { -+ return ticks == 0 ? 0 : ticks * TPSCalculator.getMostAccurateTPS() / TPSCalculator.MAX_TPS; -+ } -+ -+ public static class TPSCalculator { -+ public static Long lastTick; -+ public static Long currentTick; -+ private static double allMissedTicks = 0; -+ private static final List tpsHistory = Collections.synchronizedList(new DoubleArrayList()); -+ private static final int historyLimit = 40; -+ -+ public static final int MAX_TPS = 20; -+ public static final int FULL_TICK = 50; -+ -+ private TPSCalculator() {} -+ -+ public static void onTick() { -+ if (currentTick != null) { -+ lastTick = currentTick; -+ } -+ -+ currentTick = System.currentTimeMillis(); -+ addToHistory(getTPS()); -+ clearMissedTicks(); -+ missedTick(); -+ } -+ -+ private static void addToHistory(double tps) { -+ if (tpsHistory.size() >= historyLimit) { -+ tpsHistory.removeFirst(); -+ } -+ -+ tpsHistory.add(tps); -+ } -+ -+ public static long getMSPT() { -+ return currentTick - lastTick; -+ } -+ -+ public static double getAverageTPS() { -+ double sum = 0.0; -+ for (double value : tpsHistory) { -+ sum += value; -+ } -+ return tpsHistory.isEmpty() ? 0.1 : sum / tpsHistory.size(); -+ } -+ -+ public static double getTPS() { -+ if (lastTick == null) return -1; -+ if (getMSPT() <= 0) return 0.1; -+ -+ double tps = 1000 / (double) getMSPT(); -+ return tps > MAX_TPS ? MAX_TPS : tps; -+ } -+ -+ public static void missedTick() { -+ if (lastTick == null) return; -+ -+ long mspt = getMSPT() <= 0 ? 50 : getMSPT(); -+ double missedTicks = (mspt / (double) FULL_TICK) - 1; -+ allMissedTicks += missedTicks <= 0 ? 0 : missedTicks; -+ } -+ -+ public static double getMostAccurateTPS() { -+ return Math.min(getTPS(), getAverageTPS()); -+ } -+ -+ public double getAllMissedTicks() { -+ return allMissedTicks; -+ } -+ -+ public static int applicableMissedTicks() { -+ return (int) Math.floor(allMissedTicks); -+ } -+ -+ public static void clearMissedTicks() { -+ allMissedTicks -= applicableMissedTicks(); -+ } -+ -+ public void resetMissedTicks() { -+ allMissedTicks = 0; -+ } -+ } -+} -\ No newline at end of file diff --git a/patches/server/0122-C2ME-Reduce-Allocations.patch b/patches/server/0122-C2ME-Reduce-Allocations.patch deleted file mode 100644 index cef3e78f..00000000 --- a/patches/server/0122-C2ME-Reduce-Allocations.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Thu, 7 Nov 2024 19:45:31 +0100 -Subject: [PATCH] C2ME: Reduce Allocations - -This patch is based on the following mixin: -"com/ishland/c2me/opts/allocs/mixin/object_pooling_caching/MixinOreFeature.java" -By: ishland -As part of: C2ME (https://github.com/RelativityMC/C2ME-fabric) -Licensed under: MIT (https://opensource.org/licenses/MIT) - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java -index 506b2afd099c9b7e9ac3f6f2fcea8e523fae396b..df119e67ffdd73a7c2c93dfb35985d0c850f9e64 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java -@@ -69,7 +69,7 @@ public class OreFeature extends Feature { - int verticalSize - ) { - int i = 0; -- BitSet bitSet = new BitSet(horizontalSize * verticalSize * horizontalSize); -+ BitSet bitSet = org.dreeam.leaf.util.cache.CachedOrNewBitsGetter.getCachedOrNewBitSet(horizontalSize * verticalSize * horizontalSize); // Leaf - C2ME - Reduce Allocations - BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); - int j = config.size; - double[] ds = new double[j * 4]; -diff --git a/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java b/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5a8abdff3069d64a9866ebf01e2c8b70f4791a74 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.util.cache; -+ -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+import java.util.BitSet; -+import java.util.function.IntFunction; -+ -+public class CachedOrNewBitsGetter { -+ private static final IntFunction bitSetConstructor = BitSet::new; -+ -+ public static ThreadLocal> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new); -+ -+ private CachedOrNewBitsGetter() { -+ } -+ -+ public static BitSet getCachedOrNewBitSet(int bits) { -+ final BitSet bitSet = BITSETS.get().computeIfAbsent(bits, bitSetConstructor); -+ bitSet.clear(); -+ return bitSet; -+ } -+} diff --git a/patches/server/0125-Lithium-CompactSineLUT.patch b/patches/server/0125-Lithium-CompactSineLUT.patch deleted file mode 100644 index 5d7e442b..00000000 --- a/patches/server/0125-Lithium-CompactSineLUT.patch +++ /dev/null @@ -1,134 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Thu, 7 Nov 2024 23:51:51 +0100 -Subject: [PATCH] Lithium: CompactSineLUT - -This patch is based on the following mixin: -"net/caffeinemc/mods/lithium/mixin/math/sine_lut/MthMixin.java" -By: 2No2Name <2No2Name@web.de> -As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) -Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) - -diff --git a/src/main/java/net/minecraft/util/Mth.java b/src/main/java/net/minecraft/util/Mth.java -index 34bfbbabe3dfbf033f4a4e22a049323213fb23f3..c79bf9ea9456ac01533e8aa0326eb2f231626a49 100644 ---- a/src/main/java/net/minecraft/util/Mth.java -+++ b/src/main/java/net/minecraft/util/Mth.java -@@ -29,7 +29,7 @@ public class Mth { - public static final Vector3f Y_AXIS = new Vector3f(0.0F, 1.0F, 0.0F); - public static final Vector3f X_AXIS = new Vector3f(1.0F, 0.0F, 0.0F); - public static final Vector3f Z_AXIS = new Vector3f(0.0F, 0.0F, 1.0F); -- private static final float[] SIN = Util.make(new float[65536], sineTable -> { -+ public static final float[] SIN = Util.make(new float[65536], sineTable -> { // Leaf - Lithium - private -> public - for (int ix = 0; ix < sineTable.length; ix++) { - sineTable[ix] = (float)Math.sin((double)ix * Math.PI * 2.0 / 65536.0); - } -@@ -46,11 +46,11 @@ public class Mth { - private static final double[] COS_TAB = new double[257]; - - public static float sin(float value) { -- return SIN[(int)(value * 10430.378F) & 65535]; -+ return org.dreeam.leaf.util.math.CompactSineLUT.sin(value); // Leaf - Lithium - CompactSineLUT - } - - public static float cos(float value) { -- return SIN[(int)(value * 10430.378F + 16384.0F) & 65535]; -+ return org.dreeam.leaf.util.math.CompactSineLUT.cos(value); // Leaf - Lithium - CompactSineLUT - } - - public static float sqrt(float value) { -diff --git a/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java b/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef19bc097a5125afcac77497d4ab51d3fe1692c0 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/math/CompactSineLUT.java -@@ -0,0 +1,90 @@ -+package org.dreeam.leaf.util.math; -+ -+import net.minecraft.util.Mth; -+ -+/** -+ * A replacement for the sine angle lookup table used in {@link Mth}, both reducing the size of LUT and improving -+ * the access patterns for common paired sin/cos operations. -+ *

-+ * sin(-x) = -sin(x) -+ * ... to eliminate negative angles from the LUT. -+ *

-+ * sin(x) = sin(pi/2 - x) -+ * ... to eliminate supplementary angles from the LUT. -+ *

-+ * Using these identities allows us to reduce the LUT from 64K entries (256 KB) to just 16K entries (64 KB), enabling -+ * it to better fit into the CPU's caches at the expense of some cycles on the fast path. The implementation has been -+ * tightly optimized to avoid branching where possible and to use very quick integer operations. -+ *

-+ * Generally speaking, reducing the size of a lookup table is always a good optimization, but since we need to spend -+ * extra CPU cycles trying to maintain parity with vanilla, there is the potential risk that this implementation ends -+ * up being slower than vanilla when the lookup table is able to be kept in cache memory. -+ *

-+ * Unlike other "fast math" implementations, the values returned by this class are *bit-for-bit identical* with those -+ * from {@link Mth}. Validation is performed during runtime to ensure that the table is correct. -+ * -+ * @author coderbot16 Author of the original (and very clever) implementation in Rust: -+ * https://gitlab.com/coderbot16/i73/-/tree/master/i73-trig/src -+ * @author jellysquid3 Additional optimizations, port to Java -+ */ -+public class CompactSineLUT { -+ private static final int[] SINE_TABLE_INT = new int[16384 + 1]; -+ private static final float SINE_TABLE_MIDPOINT; -+ -+ static { -+ final float[] SINE_TABLE = Mth.SIN; -+ // Copy the sine table, covering to raw int bits -+ for (int i = 0; i < SINE_TABLE_INT.length; i++) { -+ SINE_TABLE_INT[i] = Float.floatToRawIntBits(SINE_TABLE[i]); -+ } -+ -+ SINE_TABLE_MIDPOINT = SINE_TABLE[SINE_TABLE.length / 2]; -+ -+ // Test that the lookup table is correct during runtime -+ for (int i = 0; i < SINE_TABLE.length; i++) { -+ float expected = SINE_TABLE[i]; -+ float value = lookup(i); -+ -+ if (expected != value) { -+ throw new IllegalArgumentException(String.format("LUT error at index %d (expected: %s, found: %s)", i, expected, value)); -+ } -+ } -+ } -+ -+ // [VanillaCopy] MathHelper#sin(float) -+ public static float sin(float f) { -+ return lookup((int) (f * 10430.378f) & 0xFFFF); -+ } -+ -+ // [VanillaCopy] MathHelper#cos(float) -+ public static float cos(float f) { -+ return lookup((int) (f * 10430.378f + 16384.0f) & 0xFFFF); -+ } -+ -+ private static float lookup(int index) { -+ // A special case... Is there some way to eliminate this? -+ if (index == 32768) { -+ return SINE_TABLE_MIDPOINT; -+ } -+ -+ // Trigonometric identity: sin(-x) = -sin(x) -+ // Given a domain of 0 <= x <= 2*pi, just negate the value if x > pi. -+ // This allows the sin table size to be halved. -+ int neg = (index & 0x8000) << 16; -+ -+ // All bits set if (pi/2 <= x), none set otherwise -+ // Extracts the 15th bit from 'half' -+ int mask = (index << 17) >> 31; -+ -+ // Trigonometric identity: sin(x) = sin(pi/2 - x) -+ int pos = (0x8001 & mask) + (index ^ mask); -+ -+ // Wrap the position in the table. Moving this down to immediately before the array access -+ // seems to help the Hotspot compiler optimize the bit math better. -+ pos &= 0x7fff; -+ -+ // Fetch the corresponding value from the LUT and invert the sign bit as needed -+ // This directly manipulate the sign bit on the float bits to simplify logic -+ return Float.intBitsToFloat(SINE_TABLE_INT[pos] ^ neg); -+ } -+} diff --git a/patches/server/0126-Lithium-IterateOutwardsCache.patch b/patches/server/0126-Lithium-IterateOutwardsCache.patch deleted file mode 100644 index 0777fb93..00000000 --- a/patches/server/0126-Lithium-IterateOutwardsCache.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Fri, 8 Nov 2024 00:06:34 +0100 -Subject: [PATCH] Lithium: IterateOutwardsCache - -By: 2No2Name <2No2Name@web.de> -As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) -Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) - -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 21ea63da99c5b3e2e1ab9cc1049c903bba6cf288..350a5d47cca11c0e49437a4b05029ba5c29b7ee5 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -344,7 +344,19 @@ public class BlockPos extends Vec3i { - }; - } - -+ // JettPack start - lithium: cached iterate outwards -+ private static final org.dreeam.leaf.util.cache.IterateOutwardsCache ITERATE_OUTWARDS_CACHE = new org.dreeam.leaf.util.cache.IterateOutwardsCache(50); -+ private static final it.unimi.dsi.fastutil.longs.LongList HOGLIN_PIGLIN_CACHE = ITERATE_OUTWARDS_CACHE.getOrCompute(8, 4, 8); -+ // JettPack end -+ -+ - public static Iterable withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) { -+ // JettPack start - lithium: cached iterate outwards -+ if (center != org.dreeam.leaf.util.cache.IterateOutwardsCache.POS_ZERO) { -+ final it.unimi.dsi.fastutil.longs.LongList positions = rangeX == 8 && rangeY == 4 && rangeZ == 8 ? HOGLIN_PIGLIN_CACHE : ITERATE_OUTWARDS_CACHE.getOrCompute(rangeX, rangeY, rangeZ); -+ return new org.dreeam.leaf.util.cache.LongList2BlockPosMutableIterable(center, positions); -+ } -+ // JettPack end - int i = rangeX + rangeY + rangeZ; - // Paper start - rename variables to fix conflict with anonymous class (remap fix) - int centerX = center.getX(); -diff --git a/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java b/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9ae6b0a5e1e1c8584a982b1dbd65da3f2de38eaa ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java -@@ -0,0 +1,71 @@ -+package org.dreeam.leaf.util.cache; -+ -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongList; -+import java.util.Iterator; -+import java.util.Random; -+import java.util.concurrent.ConcurrentHashMap; -+import net.minecraft.core.BlockPos; -+ -+/** -+ * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 -+ */ -+public class IterateOutwardsCache { -+ //POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache -+ public static final BlockPos POS_ZERO = new BlockPos(0,0,0); -+ -+ -+ private final ConcurrentHashMap table; -+ private final int capacity; -+ private final Random random; -+ -+ public IterateOutwardsCache(int capacity) { -+ this.capacity = capacity; -+ this.table = new ConcurrentHashMap<>(31); -+ this.random = new Random(); -+ } -+ -+ private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { -+ // Add all positions to the cached list -+ for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) { -+ entry.add(pos.asLong()); -+ } -+ } -+ -+ public LongList getOrCompute(int xRange, int yRange, int zRange) { -+ long key = BlockPos.asLong(xRange, yRange, zRange); -+ -+ LongArrayList entry = this.table.get(key); -+ if (entry != null) { -+ return entry; -+ } -+ -+ // Cache miss: compute and store -+ entry = new LongArrayList(128); -+ -+ this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange); -+ -+ //decrease the array size, as of now it won't be modified anymore anyways -+ entry.trim(); -+ -+ //this might overwrite an entry as the same entry could have been computed and added during this thread's computation -+ //we do not use computeIfAbsent, as it can delay other threads for too long -+ Object previousEntry = this.table.put(key, entry); -+ -+ -+ if (previousEntry == null && this.table.size() > this.capacity) { -+ //prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded -+ final Iterator iterator = this.table.keySet().iterator(); -+ //prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting -+ for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) { -+ Long key2 = iterator.next(); -+ //random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers -+ if (this.random.nextInt(8) == 0 && key2 != key) { -+ iterator.remove(); -+ } -+ } -+ } -+ -+ return entry; -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java b/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bdc1e6a9093dd403e9acacf1ffea873c4738f8ec ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java -@@ -0,0 +1,46 @@ -+package org.dreeam.leaf.util.cache; -+ -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongList; -+import java.util.Iterator; -+import net.minecraft.core.BlockPos; -+ -+/** -+ * @author 2No2Name -+ */ -+public class LongList2BlockPosMutableIterable implements Iterable { -+ -+ private final LongList positions; -+ private final int xOffset, yOffset, zOffset; -+ -+ public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) { -+ this.xOffset = offset.getX(); -+ this.yOffset = offset.getY(); -+ this.zOffset = offset.getZ(); -+ this.positions = posList; -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return new Iterator() { -+ -+ private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator(); -+ private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ public boolean hasNext() { -+ return it.hasNext(); -+ } -+ -+ @Override -+ public net.minecraft.core.BlockPos next() { -+ long nextPos = this.it.nextLong(); -+ return this.pos.set( -+ LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos), -+ LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos), -+ LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos)); -+ } -+ }; -+ } -+ -+} -\ No newline at end of file diff --git a/patches/server/0127-Lithium-HashedList.patch b/patches/server/0127-Lithium-HashedList.patch deleted file mode 100644 index cabc6686..00000000 --- a/patches/server/0127-Lithium-HashedList.patch +++ /dev/null @@ -1,316 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Fri, 8 Nov 2024 00:14:03 +0100 -Subject: [PATCH] Lithium: HashedList - -This patch is based on the following mixins: -* "me/jellysquid/mods/lithium/mixin/world/block_entity_ticking/collections/WorldMixin.java" (1.16.x/dev branch) -* "net/caffeinemc/mods/lithium/common/util/collections/HashedReferenceList.java" -By: 2No2Name <2No2Name@web.de> -As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) -Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 859708f1ab4b9f1bd318ca08c73cb67b1c3fe010..c1a3dbefc8d0ef0c36b8cc40a4cfd3c1015d8509 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -116,9 +116,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - public static final int TICKS_PER_DAY = 24000; - public static final int MAX_ENTITY_SPAWN_Y = 20000000; - public static final int MIN_ENTITY_SPAWN_Y = -20000000; -- public final List blockEntityTickers = Lists.newArrayList(); // Paper - public -+ public final List blockEntityTickers = new org.dreeam.leaf.util.HashedReferenceList<>(Lists.newArrayList()); // Paper - public // Leaf - Lithium - hashed list - protected final NeighborUpdater neighborUpdater; -- private final List pendingBlockEntityTickers = Lists.newArrayList(); -+ private final List pendingBlockEntityTickers = new org.dreeam.leaf.util.HashedReferenceList<>(Lists.newArrayList()); // Leaf - Lithium - hashed list - private boolean tickingBlockEntities; - public final Thread thread; - private final boolean isDebug; -diff --git a/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java b/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8c08207496fac9b0cc839293354674e3ce7083ad ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/util/HashedReferenceList.java -@@ -0,0 +1,282 @@ -+package org.dreeam.leaf.util; -+ -+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceArrayList; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Collection; -+import java.util.Iterator; -+import java.util.List; -+import java.util.ListIterator; -+import java.util.NoSuchElementException; -+ -+/** -+ * Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type -+ * contained by this list must use reference-equality semantics. -+ */ -+@SuppressWarnings("SuspiciousMethodCalls") -+public class HashedReferenceList implements List { -+ private final ReferenceArrayList list; -+ private final Reference2IntOpenHashMap counter; -+ -+ public HashedReferenceList(List list) { -+ this.list = new ReferenceArrayList<>(); -+ this.list.addAll(list); -+ -+ this.counter = new Reference2IntOpenHashMap<>(); -+ this.counter.defaultReturnValue(0); -+ -+ for (T obj : this.list) { -+ this.counter.addTo(obj, 1); -+ } -+ } -+ -+ @Override -+ public int size() { -+ return this.list.size(); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return this.list.isEmpty(); -+ } -+ -+ @Override -+ public boolean contains(Object o) { -+ return this.counter.containsKey(o); -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return this.listIterator(); -+ } -+ -+ @Override -+ public Object[] toArray() { -+ return this.list.toArray(); -+ } -+ -+ @SuppressWarnings("SuspiciousToArrayCall") -+ @Override -+ public T1[] toArray(T1 @NotNull [] a) { -+ return this.list.toArray(a); -+ } -+ -+ @Override -+ public boolean add(T t) { -+ this.trackReferenceAdded(t); -+ -+ return this.list.add(t); -+ } -+ -+ @Override -+ public boolean remove(Object o) { -+ this.trackReferenceRemoved(o); -+ -+ return this.list.remove(o); -+ } -+ -+ @Override -+ public boolean containsAll(Collection c) { -+ for (Object obj : c) { -+ if (!this.counter.containsKey(obj)) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public boolean addAll(Collection c) { -+ for (T obj : c) { -+ this.trackReferenceAdded(obj); -+ } -+ -+ return this.list.addAll(c); -+ } -+ -+ @Override -+ public boolean addAll(int index, Collection c) { -+ for (T obj : c) { -+ this.trackReferenceAdded(obj); -+ } -+ -+ return this.list.addAll(index, c); -+ } -+ -+ @Override -+ public boolean removeAll(@NotNull Collection c) { -+ if (this.size() >= 2 && c.size() > 4 && c instanceof List) { -+ //HashReferenceList uses reference equality, so using ReferenceOpenHashSet is fine -+ c = new ReferenceOpenHashSet<>(c); -+ } -+ this.counter.keySet().removeAll(c); -+ return this.list.removeAll(c); -+ } -+ -+ @Override -+ public boolean retainAll(@NotNull Collection c) { -+ this.counter.keySet().retainAll(c); -+ return this.list.retainAll(c); -+ } -+ -+ @Override -+ public void clear() { -+ this.counter.clear(); -+ this.list.clear(); -+ } -+ -+ @Override -+ public T get(int index) { -+ return this.list.get(index); -+ } -+ -+ @Override -+ public T set(int index, T element) { -+ T prev = this.list.set(index, element); -+ -+ if (prev != element) { -+ if (prev != null) { -+ this.trackReferenceRemoved(prev); -+ } -+ -+ this.trackReferenceAdded(element); -+ } -+ -+ return prev; -+ } -+ -+ @Override -+ public void add(int index, T element) { -+ this.trackReferenceAdded(element); -+ -+ this.list.add(index, element); -+ } -+ -+ @Override -+ public T remove(int index) { -+ T prev = this.list.remove(index); -+ -+ if (prev != null) { -+ this.trackReferenceRemoved(prev); -+ } -+ -+ return prev; -+ } -+ -+ @Override -+ public int indexOf(Object o) { -+ return this.list.indexOf(o); -+ } -+ -+ @Override -+ public int lastIndexOf(Object o) { -+ return this.list.lastIndexOf(o); -+ } -+ -+ @Override -+ public ListIterator listIterator() { -+ return this.listIterator(0); -+ } -+ -+ @Override -+ public ListIterator listIterator(int index) { -+ return new ListIterator<>() { -+ private final ListIterator inner = HashedReferenceList.this.list.listIterator(index); -+ -+ @Override -+ public boolean hasNext() { -+ return this.inner.hasNext(); -+ } -+ -+ @Override -+ public T next() { -+ return this.inner.next(); -+ } -+ -+ @Override -+ public boolean hasPrevious() { -+ return this.inner.hasPrevious(); -+ } -+ -+ @Override -+ public T previous() { -+ return this.inner.previous(); -+ } -+ -+ @Override -+ public int nextIndex() { -+ return this.inner.nextIndex(); -+ } -+ -+ @Override -+ public int previousIndex() { -+ return this.inner.previousIndex(); -+ } -+ -+ @Override -+ public void remove() { -+ int last = this.previousIndex(); -+ -+ if (last == -1) { -+ throw new NoSuchElementException(); -+ } -+ -+ T prev = HashedReferenceList.this.get(last); -+ -+ if (prev != null) { -+ HashedReferenceList.this.trackReferenceRemoved(prev); -+ } -+ -+ this.inner.remove(); -+ } -+ -+ @Override -+ public void set(T t) { -+ int last = this.previousIndex(); -+ -+ if (last == -1) { -+ throw new NoSuchElementException(); -+ } -+ -+ T prev = HashedReferenceList.this.get(last); -+ -+ if (prev != t) { -+ if (prev != null) { -+ HashedReferenceList.this.trackReferenceRemoved(prev); -+ } -+ -+ HashedReferenceList.this.trackReferenceAdded(t); -+ } -+ -+ this.inner.remove(); -+ } -+ -+ @Override -+ public void add(T t) { -+ HashedReferenceList.this.trackReferenceAdded(t); -+ -+ this.inner.add(t); -+ } -+ }; -+ } -+ -+ @Override -+ public List subList(int fromIndex, int toIndex) { -+ return this.list.subList(fromIndex, toIndex); -+ } -+ -+ private void trackReferenceAdded(T t) { -+ this.counter.addTo(t, 1); -+ } -+ -+ @SuppressWarnings("unchecked") -+ private void trackReferenceRemoved(Object o) { -+ if (this.counter.addTo((T) o, -1) <= 1) { -+ this.counter.removeInt(o); -+ } -+ } -+ -+} diff --git a/patches/server/0137-Configurable-tripwire-dupe.patch b/patches/server/0137-Configurable-tripwire-dupe.patch deleted file mode 100644 index 0d2b5fc0..00000000 --- a/patches/server/0137-Configurable-tripwire-dupe.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Tue, 24 Dec 2024 13:28:56 -0500 -Subject: [PATCH] Configurable tripwire dupe - -Bring back MC-59471, MC-129055 on 1.21.2+, which fixed in 1.21.2 snapshots 24w33a and 24w36a - -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 c2589f42c467ca672417c24076313da51bb2dcbb..fda1f2840f5a79d99217bd0fa72f4a1b1a75eb7e 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -@@ -206,7 +206,7 @@ public class TripWireHookBlock extends Block { - if (iblockdata4 != null) { - BlockState iblockdata5 = world.getBlockState(blockposition2); - -- if (iblockdata5.is(Blocks.TRIPWIRE) || iblockdata5.is(Blocks.TRIPWIRE_HOOK)) { -+ if (org.dreeam.leaf.config.modules.gameplay.ConfigurableTripWireDupe.enabled || iblockdata5.is(Blocks.TRIPWIRE) || iblockdata5.is(Blocks.TRIPWIRE_HOOK)) { // Leaf - Configurable tripwire dupe - world.setBlock(blockposition2, (BlockState) iblockdata4.trySetValue(TripWireHookBlock.ATTACHED, flag4), 3); - } - } -diff --git a/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java b/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b259b7023315b4632b05b511695c1f32b7890b9c ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.gameplay; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class ConfigurableTripWireDupe extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.GAMEPLAY.getBaseKeyName(); -+ } -+ -+ public static boolean enabled = false; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config.getBoolean(getBasePath() + ".allow-tripwire-dupe", enabled); -+ } -+} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6599e2c1..124b899f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Locale + pluginManagement { repositories { gradlePluginPortal() @@ -5,9 +7,14 @@ pluginManagement { } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} + rootProject.name = "leaf" -for (name in listOf("Leaf-API", "Leaf-Server", "paper-api-generator")) { - val projName = name.lowercase() + +for (name in listOf("leaf-api", "leaf-server")) { + val projName = name.lowercase(Locale.ENGLISH) include(projName) findProject(":$projName")!!.projectDir = file(name) } diff --git a/todos.md b/todos.md index dde81949..2cd16f97 100644 --- a/todos.md +++ b/todos.md @@ -1,7 +1,17 @@ # TODOs - - [ ] Check Fix MC-65198 fix - [ ] refactor leaves protocol manager opt and pr it. - [ ] check multithreaded tracker, that moonrise change - [ ] Check and apply work patches - [ ] Backport Cache canHoldAnyFluid result to 1.21.1 +- [ ] Transfer patch notes to file for Gale and Leaf +- [ ] Add spigot config unknown message to leaf docs +- [ ] Add server full join config explaination to docs +- [ ] leaf at +- [ ] Add purpur config changes to docs moved config + +# Cleanup +- [ ] 检查注释都应该start end - patch名或者合理注释 +- [ ] leaf at (public, final) +- [ ] 格式调整, 变量名调整检查 +- [ ] 类中新增的方法全部转private \ No newline at end of file