From a67dc0f576e03b4004ca6e527fc3a970357e66f0 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Sat, 22 Feb 2025 15:13:16 +0300 Subject: [PATCH] implement new patches --- build-data/divinemc.at | 66 +- build.gradle.kts | 4 +- .../paper-patches/features/0001-Rebrand.patch | 38 + .../features/0002-Configuration.patch | 24 + ...imings.patch => 0003-Delete-Timings.patch} | 144 ++ ...4-Disable-reload-command-by-default.patch} | 0 divinemc-server/build.gradle.kts.patch | 13 +- .../features/0001-Rebrand.patch | 69 + ...uration.patch => 0002-Configuration.patch} | 26 +- ...03-Add-missing-purpur-config-options.patch | 183 --- .../features/0003-Implement-Secure-Seed.patch | 433 ++++++ .../features/0004-Async-Pathfinding.patch | 810 +++++++++++ .../features/0005-Threaded-light-engine.patch | 40 + .../features/0006-Multithreaded-Tracker.patch | 333 +++++ .../features/0007-Async-locate-command.patch | 129 ++ .../0008-Parallel-world-ticking.patch | 988 ++++++++++++++ .../features/0009-C2ME-opts_native_math.patch | 401 ++++++ .../features/0010-Optimize-Fluids.patch | 145 ++ ...11-World-and-Noise-gen-optimizations.patch | 918 +++++++++++++ .../0012-Chunk-System-optimization.patch | 95 ++ .../features/0013-Optimize-hoppers.patch | 682 ++++++++++ .../features/0014-Some-optimizations.patch | 144 ++ .../0015-Optimize-entity-stupid-brain.patch | 394 ++++++ .../features/0016-Clump-experience-orbs.patch | 211 +++ .../features/0017-Optimize-explosions.patch | 192 +++ ...g-players-when-they-move-too-quickly.patch | 51 + .../features/0019-Lag-compensation.patch | 331 +++++ .../0020-MSPT-Tracking-for-each-world.patch | 43 + ...uler-s-executeTick-checks-if-there-i.patch | 48 + ...-Carpet-Fixes-RecipeManager-Optimize.patch | 52 + .../0023-Snowball-and-Egg-knockback.patch | 42 + .../0024-Block-Log4Shell-exploit.patch | 34 + .../features/0025-Re-Fix-MC-117075.patch | 36 + ...qr-call-in-ServerEntity-sendChanges-.patch | 53 + .../0027-Optimize-canSee-checks.patch | 19 + .../0028-Implement-NoChatReports.patch | 309 +++++ .../0029-Optimize-Structure-Generation.patch | 275 ++++ .../0030-Verify-Minecraft-EULA-earlier.patch | 38 + .../0031-Density-Function-Compiler.patch | 1205 +++++++++++++++++ .../RegionizedPlayerChunkLoader.java.patch | 15 + .../subcommands/FixLightCommand.java.patch | 32 + .../SimpleCriterionTrigger.java.patch | 11 + .../server/MinecraftServer.java.patch | 16 + .../server/PlayerAdvancements.java.patch | 11 + .../server/level/ServerPlayer.java.patch | 10 + .../stats/ServerStatsCounter.java.patch | 28 + .../sources/net/minecraft/util/Mth.java.patch | 16 + .../util/thread/BlockableEventLoop.java.patch | 21 + .../world/entity/LivingEntity.java.patch | 27 + .../goal/target/HurtByTargetGoal.java.patch | 10 + .../ai/sensing/SecondaryPoiSensor.java.patch | 15 + .../entity/monster/ZombieVillager.java.patch | 15 + .../world/entity/player/Player.java.patch | 14 + .../world/level/GameRules.java.patch | 11 + .../world/level/block/Blocks.java.patch | 10 + .../world/level/block/LeavesBlock.java.patch | 27 + .../world/level/chunk/LevelChunk.java.patch | 26 + .../paper-patches/features/0001-Rebrand.patch | 394 +----- ...uration.patch => 0002-Configuration.patch} | 36 +- .../features/0003-Delete-timings.patch | 62 + ...-Optimize-default-values-for-configs.patch | 377 ------ .../features/0004-Implement-Secure-Seed.patch | 43 + .../0004-Remove-Spigot-tick-limiter.patch | 52 - .../0005-Parallel-world-ticking.patch | 613 +++++++++ ...uler-s-executeTick-checks-if-there-i.patch | 90 ++ .../0007-Optimize-canSee-checks.patch | 36 + .../0008-Verify-Minecraft-EULA-earlier.patch | 45 + .../0009-Add-chunk-worker-algorithm.patch | 52 + .../common/util/MoonriseConstants.java.patch | 11 + .../paper/command/MSPTCommand.java.patch | 50 + .../manager/PaperEventManager.java.patch | 27 + .../bukkit/craftbukkit/CraftServer.java.patch | 11 + .../event/CraftEventFactory.java.patch | 11 + divinemc-server/src/c/CMakeLists.txt | 32 + divinemc-server/src/c/exports.c | 34 + .../src/c/exports_x86_64_nogather.c | 9 + divinemc-server/src/c/flibc.c | 672 +++++++++ divinemc-server/src/c/includes/ext_math.h | 744 ++++++++++ .../src/c/includes/target_macros.h | 28 + divinemc-server/src/c/system_isa_aarch64.c | 11 + divinemc-server/src/c/system_isa_x86_64.c | 154 +++ .../src/c/targets/darwin-x86_64.cmake | 11 + .../src/c/targets/linux-aarch_64.cmake | 10 + .../src/c/targets/linux-x86_64.cmake | 11 + .../src/c/targets/windows-x86_64.cmake | 11 + .../common/util/math/CompactSineLUT.java | 89 ++ .../org/bxteam/divinemc/DivineConfig.java | 372 +++++ .../bxteam/divinemc/DivineWorldConfig.java | 82 ++ .../divinemc/command/DivineCommands.java | 3 +- .../command/subcommands/ReloadCommand.java | 4 +- .../divinemc/configuration/DivineConfig.java | 218 --- .../configuration/DivineWorldConfig.java | 121 -- .../common/IDensityFunctionsCaveScaler.java | 13 + .../divinemc/dfc/common/ast/AstNode.java | 22 + .../dfc/common/ast/AstTransformer.java | 5 + .../divinemc/dfc/common/ast/EvalType.java | 25 + .../divinemc/dfc/common/ast/McToAst.java | 106 ++ .../common/ast/binary/AbstractBinaryNode.java | 106 ++ .../dfc/common/ast/binary/AddNode.java | 50 + .../dfc/common/ast/binary/MaxNode.java | 50 + .../dfc/common/ast/binary/MaxShortNode.java | 92 ++ .../dfc/common/ast/binary/MinNode.java | 50 + .../dfc/common/ast/binary/MinShortNode.java | 92 ++ .../dfc/common/ast/binary/MulNode.java | 92 ++ .../common/ast/dfvisitor/StripBlending.java | 23 + .../dfc/common/ast/misc/CacheLikeNode.java | 256 ++++ .../dfc/common/ast/misc/ConstantNode.java | 72 + .../dfc/common/ast/misc/DelegateNode.java | 140 ++ .../dfc/common/ast/misc/RangeChoiceNode.java | 337 +++++ .../dfc/common/ast/misc/RootNode.java | 82 ++ .../common/ast/misc/YClampedGradientNode.java | 100 ++ .../dfc/common/ast/noise/DFTNoiseNode.java | 131 ++ .../dfc/common/ast/noise/DFTShiftANode.java | 115 ++ .../dfc/common/ast/noise/DFTShiftBNode.java | 115 ++ .../dfc/common/ast/noise/DFTShiftNode.java | 123 ++ .../ast/noise/DFTWeirdScaledSamplerNode.java | 169 +++ .../common/ast/noise/ShiftedNoiseNode.java | 215 +++ .../dfc/common/ast/spline/SplineAstNode.java | 453 +++++++ .../dfc/common/ast/spline/SplineSupport.java | 29 + .../dfc/common/ast/unary/AbsNode.java | 49 + .../common/ast/unary/AbstractUnaryNode.java | 72 + .../dfc/common/ast/unary/CubeNode.java | 56 + .../dfc/common/ast/unary/NegMulNode.java | 83 ++ .../dfc/common/ast/unary/SquareNode.java | 52 + .../dfc/common/ast/unary/SqueezeNode.java | 84 ++ .../dfc/common/ducks/IArrayCacheCapable.java | 7 + .../common/ducks/IBlendingAwareVisitor.java | 5 + .../dfc/common/ducks/ICoordinatesFilling.java | 5 + .../dfc/common/ducks/IEqualityOverriding.java | 7 + .../dfc/common/ducks/IFastCacheLike.java | 20 + .../divinemc/dfc/common/gen/BytecodeGen.java | 499 +++++++ .../common/gen/CompiledDensityFunction.java | 98 ++ .../dfc/common/gen/CompiledEntry.java | 15 + .../gen/DelegatingBlendingAwareVisitor.java | 28 + .../divinemc/dfc/common/gen/IMultiMethod.java | 9 + .../dfc/common/gen/ISingleMethod.java | 8 + .../gen/SubCompiledDensityFunction.java | 142 ++ .../divinemc/dfc/common/util/ArrayCache.java | 57 + .../dfc/common/vif/AstVanillaInterface.java | 98 ++ .../vif/EachApplierVanillaInterface.java | 58 + .../common/vif/NoisePosVanillaInterface.java | 35 + .../entity/pathfinding/AsyncPath.java | 284 ++++ .../pathfinding/AsyncPathProcessor.java | 48 + .../pathfinding/NodeEvaluatorCache.java | 44 + .../pathfinding/NodeEvaluatorFeatures.java | 23 + .../pathfinding/NodeEvaluatorGenerator.java | 8 + .../entity/pathfinding/NodeEvaluatorType.java | 17 + .../entity/pathfinding/PathProcessState.java | 7 + .../entity/tracking/MultithreadedTracker.java | 141 ++ .../org/bxteam/divinemc/math/Bindings.java | 105 ++ .../divinemc/math/BindingsTemplate.java | 407 ++++++ .../org/bxteam/divinemc/math/ISATarget.java | 20 + .../bxteam/divinemc/math/NativeLoader.java | 179 +++ .../bxteam/divinemc/math/isa/ISA_aarch64.java | 25 + .../bxteam/divinemc/math/isa/ISA_x86_64.java | 34 + .../ServerLevelTickExecutorThreadFactory.java | 27 + .../server/chunk/ChunkSystemAlgorithms.java | 116 ++ .../java/org/bxteam/divinemc/util/Assert.java | 421 ++++++ .../org/bxteam/divinemc/util/Assertions.java | 27 + .../divinemc/util/BlockEntityTickersList.java | 98 ++ .../java/org/bxteam/divinemc/util/Files.java | 35 + .../org/bxteam/divinemc/util/MemoryUtil.java | 12 + .../org/bxteam/divinemc/util/ObjectUtil.java | 108 ++ .../org/bxteam/divinemc/util/RandomUtil.java | 40 + .../org/bxteam/divinemc/util/StringUtil.java | 9 + .../util/collections/LongJumpChoiceList.java | 217 +++ .../divinemc/util/collections/MaskedList.java | 150 ++ .../divinemc/util/entity/SensorHelper.java | 53 + .../util/map/ConcurrentReferenceHashMap.java | 1054 ++++++++++++++ .../divinemc/util/structure/BoxOctree.java | 165 +++ .../divinemc/util/structure/GeneralUtils.java | 90 ++ .../PalettedStructureBlockInfoList.java | 274 ++++ ...alettedStructureBlockInfoListIterator.java | 65 + .../structure/StructureTemplateOptimizer.java | 105 ++ .../util/structure/TrojanArrayList.java | 11 + .../util/structure/TrojanVoxelShape.java | 21 + .../divinemc/util/structure/package-info.java | 5 + .../divinemc/util/tps/TPSCalculator.java | 83 ++ .../org/bxteam/divinemc/util/tps/TPSUtil.java | 35 + .../src/main/java/su/plo/matter/Globals.java | 95 ++ .../src/main/java/su/plo/matter/Hashing.java | 73 + .../su/plo/matter/WorldgenCryptoRandom.java | 159 +++ .../linux-x86_64-libc2me-opts-natives-math.so | Bin 0 -> 943176 bytes .../windows-x86_64-c2me-opts-natives-math.dll | Bin 0 -> 1266176 bytes 184 files changed, 21942 insertions(+), 1357 deletions(-) create mode 100644 divinemc-api/paper-patches/features/0001-Rebrand.patch create mode 100644 divinemc-api/paper-patches/features/0002-Configuration.patch rename divinemc-api/paper-patches/features/{0002-Delete-Timings.patch => 0003-Delete-Timings.patch} (91%) rename divinemc-api/paper-patches/features/{0001-Disable-reload-command-by-default.patch => 0004-Disable-reload-command-by-default.patch} (100%) rename divinemc-server/minecraft-patches/features/{0002-DivineMC-Configuration.patch => 0002-Configuration.patch} (68%) delete mode 100644 divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch create mode 100644 divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch create mode 100644 divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch create mode 100644 divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch create mode 100644 divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch create mode 100644 divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch create mode 100644 divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch create mode 100644 divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch create mode 100644 divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch create mode 100644 divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch create mode 100644 divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch create mode 100644 divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch create mode 100644 divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch create mode 100644 divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch create mode 100644 divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch create mode 100644 divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch create mode 100644 divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch create mode 100644 divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch create mode 100644 divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch create mode 100644 divinemc-server/minecraft-patches/features/0021-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch create mode 100644 divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch create mode 100644 divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch create mode 100644 divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch create mode 100644 divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch create mode 100644 divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch create mode 100644 divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch create mode 100644 divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch create mode 100644 divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch create mode 100644 divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch create mode 100644 divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch create mode 100644 divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch create mode 100644 divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch rename divinemc-server/paper-patches/features/{0002-DivineMC-Configuration.patch => 0002-Configuration.patch} (64%) create mode 100644 divinemc-server/paper-patches/features/0003-Delete-timings.patch delete mode 100644 divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch create mode 100644 divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch delete mode 100644 divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch create mode 100644 divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch create mode 100644 divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch create mode 100644 divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch create mode 100644 divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch create mode 100644 divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch create mode 100644 divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch create mode 100644 divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch create mode 100644 divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch create mode 100644 divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch create mode 100644 divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch create mode 100644 divinemc-server/src/c/CMakeLists.txt create mode 100644 divinemc-server/src/c/exports.c create mode 100644 divinemc-server/src/c/exports_x86_64_nogather.c create mode 100644 divinemc-server/src/c/flibc.c create mode 100644 divinemc-server/src/c/includes/ext_math.h create mode 100644 divinemc-server/src/c/includes/target_macros.h create mode 100644 divinemc-server/src/c/system_isa_aarch64.c create mode 100644 divinemc-server/src/c/system_isa_x86_64.c create mode 100644 divinemc-server/src/c/targets/darwin-x86_64.cmake create mode 100644 divinemc-server/src/c/targets/linux-aarch_64.cmake create mode 100644 divinemc-server/src/c/targets/linux-x86_64.cmake create mode 100644 divinemc-server/src/c/targets/windows-x86_64.cmake create mode 100644 divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java delete mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java delete mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java create mode 100644 divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java create mode 100644 divinemc-server/src/main/java/su/plo/matter/Globals.java create mode 100644 divinemc-server/src/main/java/su/plo/matter/Hashing.java create mode 100644 divinemc-server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java create mode 100644 divinemc-server/src/main/resources/linux-x86_64-libc2me-opts-natives-math.so create mode 100644 divinemc-server/src/main/resources/windows-x86_64-c2me-opts-natives-math.dll diff --git a/build-data/divinemc.at b/build-data/divinemc.at index 262146c..a8ffb2d 100644 --- a/build-data/divinemc.at +++ b/build-data/divinemc.at @@ -1,10 +1,62 @@ # This file is auto generated, any changes may be overridden! # See CONTRIBUTING.md on how to add access transformers. -public net.minecraft.world.entity.projectile.AbstractArrow startFalling()V -public net.minecraft.world.level.block.state.pattern.BlockPattern matches(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;Lnet/minecraft/core/Direction;Lcom/google/common/cache/LoadingCache;)Lnet/minecraft/world/level/block/state/pattern/BlockPattern$BlockPatternMatch; -public net.minecraft.world.level.chunk.storage.RegionFile getOversizedData(II)Lnet/minecraft/nbt/CompoundTag; -public net.minecraft.world.level.chunk.storage.RegionFile isOversized(II)Z -public net.minecraft.world.level.chunk.storage.RegionFile recalculateHeader()Z -public net.minecraft.world.level.chunk.storage.RegionFile setOversized(IIZ)V -public net.minecraft.world.level.chunk.storage.RegionFile write(Lnet/minecraft/world/level/ChunkPos;Ljava/nio/ByteBuffer;)V +private-f net.minecraft.world.level.levelgen.NoiseChunk$Cache2D function +private-f net.minecraft.world.level.levelgen.NoiseChunk$CacheOnce function +private-f net.minecraft.world.level.levelgen.NoiseChunk$FlatCache noiseFiller +private-f net.minecraft.world.level.levelgen.NoiseChunk$NoiseInterpolator noiseFiller +private-f net.minecraft.world.level.levelgen.RandomState router +private-f net.minecraft.world.level.levelgen.RandomState sampler +public net.minecraft.util.Mth SIN +public net.minecraft.world.entity.ai.Brain sensors +public net.minecraft.world.entity.ai.sensing.Sensor scanRate +public net.minecraft.world.entity.ai.sensing.Sensor timeToTick +public net.minecraft.world.level.ServerExplosion damageSource +public net.minecraft.world.level.ServerExplosion source +public net.minecraft.world.level.chunk.LevelChunkSection nonEmptyBlockCount +public net.minecraft.world.level.chunk.LevelChunkSection tickingBlockCount +public net.minecraft.world.level.chunk.LevelChunkSection tickingFluidCount +public net.minecraft.world.level.chunk.PalettedContainer palette +public net.minecraft.world.level.chunk.PalettedContainer strategy +public net.minecraft.world.level.levelgen.DensityFunctions$BlendAlpha +public net.minecraft.world.level.levelgen.DensityFunctions$BlendDensity +public net.minecraft.world.level.levelgen.DensityFunctions$BlendOffset +public net.minecraft.world.level.levelgen.DensityFunctions$Clamp +public net.minecraft.world.level.levelgen.DensityFunctions$Constant +public net.minecraft.world.level.levelgen.DensityFunctions$Mapped +public net.minecraft.world.level.levelgen.DensityFunctions$Mapped$Type +public net.minecraft.world.level.levelgen.DensityFunctions$Noise +public net.minecraft.world.level.levelgen.DensityFunctions$RangeChoice +public net.minecraft.world.level.levelgen.DensityFunctions$Shift +public net.minecraft.world.level.levelgen.DensityFunctions$ShiftA +public net.minecraft.world.level.levelgen.DensityFunctions$ShiftB +public net.minecraft.world.level.levelgen.DensityFunctions$ShiftedNoise +public net.minecraft.world.level.levelgen.DensityFunctions$TwoArgumentSimpleFunction +public net.minecraft.world.level.levelgen.DensityFunctions$WeirdScaledSampler +public net.minecraft.world.level.levelgen.DensityFunctions$WeirdScaledSampler$RarityValueMapper mapper +public net.minecraft.world.level.levelgen.DensityFunctions$YClampedGradient +public net.minecraft.world.level.levelgen.NoiseChunk$BlendAlpha +public net.minecraft.world.level.levelgen.NoiseChunk$BlendOffset +public net.minecraft.world.level.levelgen.NoiseRouterData$QuantizedSpaghettiRarity +public net.minecraft.world.level.levelgen.NoiseRouterData$QuantizedSpaghettiRarity getSpaghettiRarity3D(D)D +public net.minecraft.world.level.levelgen.NoiseRouterData$QuantizedSpaghettiRarity getSphaghettiRarity2D(D)D +public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedHi +public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedLo +public net.minecraft.world.level.levelgen.XoroshiroRandomSource randomNumberGenerator +public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool rawTemplates +public net.minecraft.world.level.levelgen.synth.BlendedNoise mainNoise +public net.minecraft.world.level.levelgen.synth.BlendedNoise maxLimitNoise +public net.minecraft.world.level.levelgen.synth.BlendedNoise minLimitNoise +public net.minecraft.world.level.levelgen.synth.BlendedNoise smearScaleMultiplier +public net.minecraft.world.level.levelgen.synth.BlendedNoise xzFactor +public net.minecraft.world.level.levelgen.synth.BlendedNoise xzMultiplier +public net.minecraft.world.level.levelgen.synth.BlendedNoise xzScale +public net.minecraft.world.level.levelgen.synth.BlendedNoise yFactor +public net.minecraft.world.level.levelgen.synth.BlendedNoise yMultiplier +public net.minecraft.world.level.levelgen.synth.BlendedNoise yScale +public net.minecraft.world.level.levelgen.synth.ImprovedNoise p +public net.minecraft.world.level.levelgen.synth.PerlinNoise amplitudes +public net.minecraft.world.level.levelgen.synth.PerlinNoise lowestFreqInputFactor +public net.minecraft.world.level.levelgen.synth.PerlinNoise lowestFreqValueFactor +public net.minecraft.world.level.levelgen.synth.PerlinNoise noiseLevels +public net.minecraft.world.level.levelgen.synth.SimplexNoise p public net.minecraft.world.level.pathfinder.SwimNodeEvaluator allowBreaching diff --git a/build.gradle.kts b/build.gradle.kts index 3c42348..fb5fe28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,13 +43,13 @@ subprojects { extensions.configure { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(22) } } tasks.withType { options.encoding = Charsets.UTF_8.name() - options.release = 21 + options.release = 22 options.isFork = true } tasks.withType { diff --git a/divinemc-api/paper-patches/features/0001-Rebrand.patch b/divinemc-api/paper-patches/features/0001-Rebrand.patch new file mode 100644 index 0000000..4ea15a9 --- /dev/null +++ b/divinemc-api/paper-patches/features/0001-Rebrand.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 21 Feb 2025 22:58:46 +0300 +Subject: [PATCH] Rebrand + + +diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java +index fb1fe2651e53a9bf46b3632c638e13eea9dcda93..81e92d1053efd15c079e318a4ae087948bc07866 100644 +--- a/src/main/java/io/papermc/paper/ServerBuildInfo.java ++++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java +@@ -25,6 +25,14 @@ public interface ServerBuildInfo { + */ + Key BRAND_PURPUR_ID = Key.key("purpurmc", "purpur"); + // Purpur end ++ ++ // DivineMC start - Rebrand ++ /** ++ * The brand id for DivineMC. ++ */ ++ Key BRAND_DIVINEMC_ID = Key.key("bxteam", "divinemc"); ++ // DivineMC end ++ + /** + * Gets the {@code ServerBuildInfo}. + * +diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +index c880d0010849ab733ad13bbd18fab3c864d0cf61..a76439e59eefa4b6dbd0e100d72c21055d0ca008 100644 +--- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +@@ -259,7 +259,7 @@ public class VersionCommand extends BukkitCommand { + // Purpur start + int distance = getVersionFetcher().distance(); + final Component message = Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), +- ChatColor.parseMM("Current Purpur Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), ++ ChatColor.parseMM("Current DivineMC Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), // DivineMC - Rebrand + // Purpur end + msg + ); diff --git a/divinemc-api/paper-patches/features/0002-Configuration.patch b/divinemc-api/paper-patches/features/0002-Configuration.patch new file mode 100644 index 0000000..c0c586c --- /dev/null +++ b/divinemc-api/paper-patches/features/0002-Configuration.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 21 Feb 2025 23:00:22 +0300 +Subject: [PATCH] Configuration + + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 78637a4f9650c1dd7ccc94bbfeb1fac048aa7f69..225c13225837c2748843cece816e2ad70da4b056 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -2346,6 +2346,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + } + // Purpur end + ++ // DivineMC start - Configuration ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getDivineConfig() { ++ throw new UnsupportedOperationException("Not supported yet"); ++ } ++ // DivineMC end - Configuration ++ + /** + * Sends the component to the player + * diff --git a/divinemc-api/paper-patches/features/0002-Delete-Timings.patch b/divinemc-api/paper-patches/features/0003-Delete-Timings.patch similarity index 91% rename from divinemc-api/paper-patches/features/0002-Delete-Timings.patch rename to divinemc-api/paper-patches/features/0003-Delete-Timings.patch index 7ff3eeb..f907ba1 100644 --- a/divinemc-api/paper-patches/features/0002-Delete-Timings.patch +++ b/divinemc-api/paper-patches/features/0003-Delete-Timings.patch @@ -2104,6 +2104,150 @@ index 632c4961515f5052551f841cfa840e60bba7a257..00000000000000000000000000000000 - super.stopTiming(); - } -} +diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java +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 { + protected String usageMessage; + private String permission; + private net.kyori.adventure.text.Component permissionMessage; // Paper +- /** +- * @deprecated Timings will be removed in the future +- */ +- @Deprecated(forRemoval = true) +- public co.aikar.timings.Timing timings; // Paper +- /** +- * @deprecated Timings will be removed in the future +- */ +- @Deprecated(forRemoval = true) +- @NotNull public String getTimingName() {return getName();} // Paper + + protected Command(@NotNull String name) { + this(name, "", "/" + name, new ArrayList()); +diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +index abe256e1e45ce28036da4aa1586715bc8a1a3414..9eab8024e0675865f17669847759a26d28f74f3a 100644 +--- a/src/main/java/org/bukkit/command/FormattedCommandAlias.java ++++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +@@ -12,7 +12,6 @@ public class FormattedCommandAlias extends Command { + + public FormattedCommandAlias(@NotNull String alias, @NotNull String[] formatStrings) { + super(alias); +- timings = co.aikar.timings.TimingsManager.getCommandTiming("minecraft", this); // Spigot + this.formatStrings = formatStrings; + } + +@@ -120,10 +119,6 @@ public class FormattedCommandAlias extends Command { + return formatString.trim(); // Paper - Causes an extra space at the end, breaks with brig commands + } + +- @NotNull +- @Override // Paper +- public String getTimingName() {return "Command Forwarder - " + super.getTimingName();} // Paper +- + private static boolean inRange(int i, int j, int k) { + 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 685aa9d73f14a5e3a85b251b83fbe8134b0a244c..6878e8bff4900a6d9b41cb981a69821da7f51242 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 { + register("bukkit", new VersionCommand("version")); + register("bukkit", new ReloadCommand("reload")); + //register("bukkit", new PluginsCommand("plugins")); // Paper +- register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Paper + } + + public void setFallbackCommands() { +@@ -71,7 +70,6 @@ public class SimpleCommandMap implements CommandMap { + */ + @Override + public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) { +- command.timings = co.aikar.timings.TimingsManager.getCommandTiming(fallbackPrefix, command); // Paper + label = label.toLowerCase(Locale.ROOT).trim(); + fallbackPrefix = fallbackPrefix.toLowerCase(Locale.ROOT).trim(); + boolean registered = register(label, command, false, fallbackPrefix); +@@ -166,12 +164,6 @@ public class SimpleCommandMap implements CommandMap { + parsedArgs = event.getArgs(); + // Purpur end - ExecuteCommandEvent + +- // Paper start - Plugins do weird things to workaround normal registration +- if (target.timings == null) { +- target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target); +- } +- // Paper end +- + try { + //try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources // Purpur - Remove Timings + // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 001465eedafa51ac027a4db51cba6223edfe1171..9cb0f09b821a4020d17771a5b64ddd53e7d78478 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -720,12 +720,7 @@ public final class SimplePluginManager implements PluginManager { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + +- executor = new co.aikar.timings.TimedEventExecutor(executor, plugin, null, event); // Paper +- if (false) { // Spigot - RL handles useTimings check now // Paper +- getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); +- } else { +- getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); +- } ++ getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); // DivineMC - Delete Timings + } + + @NotNull +@@ -955,8 +950,7 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public boolean useTimings() { +- if (true) {return this.paperPluginManager.useTimings();} // Paper +- return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot ++ return false; // DivineMC - Delete Timings + } + + /** +@@ -966,7 +960,7 @@ public final class SimplePluginManager implements PluginManager { + */ + @Deprecated(forRemoval = true) + public void useTimings(boolean use) { +- co.aikar.timings.Timings.setTimingsEnabled(use); // Paper ++ // DivineMC - Delete Timings + } + + // Paper start +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index 065f182fbfe4541d602f57d548f286ee3c2fab19..40350504b5f7a92d834e95bb2e4e4268195ec9e7 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -43,7 +43,6 @@ import org.bukkit.plugin.TimedRegisteredListener; + import org.bukkit.plugin.UnknownDependencyException; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; +-import org.spigotmc.CustomTimingsHandler; // Spigot + import org.yaml.snakeyaml.error.YAMLException; + + /** +@@ -294,7 +293,7 @@ public final class JavaPluginLoader implements PluginLoader { + } + } + +- EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Paper ++ EventExecutor executor = new EventExecutor() { // Paper // DivineMC - Delete Timings + @Override + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { // Paper + try { +@@ -308,7 +307,7 @@ public final class JavaPluginLoader implements PluginLoader { + throw new EventException(t); + } + } +- }, plugin, method, eventClass); // Paper ++ }; // Paper // DivineMC - Delete Timings + if (false) { // Spigot - RL handles useTimings check now + eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } else { diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java deleted file mode 100644 index b4249da3eb26eae26ec000cc4d56cd21ac2fc6d5..0000000000000000000000000000000000000000 diff --git a/divinemc-api/paper-patches/features/0001-Disable-reload-command-by-default.patch b/divinemc-api/paper-patches/features/0004-Disable-reload-command-by-default.patch similarity index 100% rename from divinemc-api/paper-patches/features/0001-Disable-reload-command-by-default.patch rename to divinemc-api/paper-patches/features/0004-Disable-reload-command-by-default.patch diff --git a/divinemc-server/build.gradle.kts.patch b/divinemc-server/build.gradle.kts.patch index 10256b7..d27dd65 100644 --- a/divinemc-server/build.gradle.kts.patch +++ b/divinemc-server/build.gradle.kts.patch @@ -57,17 +57,16 @@ 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 -@@ -176,6 +_,9 @@ +@@ -176,6 +_,8 @@ implementation("org.mozilla:rhino-engine:1.7.14") // Purpur implementation("dev.omega24:upnp4j:1.0") // Purpur -+ implementation("com.github.luben:zstd-jni:1.5.6-9") // DivineMC -+ implementation("org.lz4:lz4-java:1.8.0") // DivineMC ++ implementation("net.objecthunter:exp4j:0.4.8") // DivineMC + 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") -@@ -203,26 +_,35 @@ +@@ -203,30 +_,41 @@ implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") } @@ -91,6 +90,7 @@ val implementationVersion = "$mcVersion-${build ?: "DEV"}-$gitHash" val date = git.exec(providers, "show", "-s", "--format=%ci", gitHash).get().trim() val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim() ++ val experimental = rootProject.providers.gradleProperty("experimental").get() attributes( "Main-Class" to "org.bukkit.craftbukkit.Main", - "Implementation-Title" to "Purpur", // Purpur @@ -109,3 +109,8 @@ "Build-Number" to (build ?: ""), "Build-Time" to buildTime.toString(), "Git-Branch" to gitBranch, + "Git-Commit" to gitHash, ++ "Experimental" to experimental, // DivineMC - Experimental flag + ) + for (tld in setOf("net", "com", "org")) { + attributes("$tld/bukkit", "Sealed" to true) diff --git a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch index f4e6ae1..ceca597 100644 --- a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch +++ b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch @@ -17,6 +17,75 @@ index 394443d00e661715439be1e56dddc129947699a4..480ad57a6b7b74e6b83e9c6ceb69ea1f public CrashReport(String title, Throwable exception) { io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper +diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java +index 1485186d4989874ef89c4e83830f26358a43759c..680369af59fd2aa36bf1cf4e28b598854383abe3 100644 +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -62,6 +62,14 @@ import org.slf4j.Logger; + + public class Main { + private static final Logger LOGGER = LogUtils.getLogger(); ++ // DivineMC start - Log experimental warning ++ static { ++ io.papermc.paper.ServerBuildInfo info = io.papermc.paper.ServerBuildInfo.buildInfo(); ++ if (io.papermc.paper.ServerBuildInfoImpl.IS_EXPERIMENTAL) { ++ LOGGER.warn("Running an experimental version of {}, please proceed with caution.", info.brandName()); ++ } ++ } ++ // DivineMC end - Log experimental warning + + @SuppressForbidden( + reason = "System.out needed before bootstrap" +@@ -114,6 +122,18 @@ public class Main { + 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 + ++ // DivineMC start - Server startup settings ++ org.bukkit.configuration.file.YamlConfiguration divinemcConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("divinemc-settings")); ++ boolean divinemcNativeMathEnabled = divinemcConfiguration.getBoolean("settings.chunk-generation.native-acceleration-enabled", true); ++ if (divinemcNativeMathEnabled) { ++ try { ++ Class.forName("org.bxteam.divinemc.math.NativeLoader").getField("lookup").get(null); ++ } catch (Throwable e) { ++ e.printStackTrace(); ++ } ++ } ++ // DivineMC end - Server startup settings ++ + io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper + Bootstrap.bootStrap(); + Bootstrap.validate(); +diff --git a/net/minecraft/server/gui/MinecraftServerGui.java b/net/minecraft/server/gui/MinecraftServerGui.java +index 614c7d9f673c926562acc8fa3b3788623900db41..33456c7c106abbddf743e1203a6e8122cf10b797 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("DivineMC Minecraft server"); // Purpur - Improve GUI // DivineMC - Rebrand + 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("DivineMC Minecraft server"); // Purpur - Improve GUI // DivineMC - Rebrand + 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("DivineMC Minecraft server - shutting down!"); // Purpur - Improve GUI // DivineMC - Rebrand + server.halt(true); + minecraftServerGui.runFinalizers(); + } diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index 80ed0e4b8c867d031413b4140e52af1342fdcb54..6ebd1300c2561116b83cb2472ac7939ead36d576 100644 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java diff --git a/divinemc-server/minecraft-patches/features/0002-DivineMC-Configuration.patch b/divinemc-server/minecraft-patches/features/0002-Configuration.patch similarity index 68% rename from divinemc-server/minecraft-patches/features/0002-DivineMC-Configuration.patch rename to divinemc-server/minecraft-patches/features/0002-Configuration.patch index 54bb57d..296c93f 100644 --- a/divinemc-server/minecraft-patches/features/0002-DivineMC-Configuration.patch +++ b/divinemc-server/minecraft-patches/features/0002-Configuration.patch @@ -1,11 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 16:19:14 +0300 -Subject: [PATCH] DivineMC Configuration +Date: Mon, 27 Jan 2025 20:22:52 +0300 +Subject: [PATCH] Configuration diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index db82329935145ec12fa47eef730613ee9c7666ee..0eecc41b02f205022a717691a18114d5c091bc3d 100644 +index 4f90ebcd86fba38dec313143e36614e992c7dbc7..959d87f4cd1efe8cf591e98c7d32728067f7117c 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -243,6 +243,17 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -13,36 +13,36 @@ index db82329935145ec12fa47eef730613ee9c7666ee..0eecc41b02f205022a717691a18114d5 org.purpurmc.purpur.PurpurConfig.registerCommands(); */// Purpur end - Purpur config files // Purpur - Configurable void damage height and damage + -+ // DivineMC start - DivineMC configuration ++ // DivineMC start - Configuration + try { -+ org.bxteam.divinemc.configuration.DivineConfig.init((java.io.File) options.valueOf("divinemc-settings")); ++ org.bxteam.divinemc.DivineConfig.init((java.io.File) options.valueOf("divinemc-settings")); + } catch (Exception e) { + DedicatedServer.LOGGER.error("Unable to load server configuration", e); + return false; + } -+ org.bxteam.divinemc.command.DivineCommands.registerCommands(this); // DivineMC - register commands -+ // DivineMC end - DivineMC configuration ++ org.bxteam.divinemc.command.DivineCommands.registerCommands(this); ++ // DivineMC end - Configuration + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now this.setPvpAllowed(properties.pvp); diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 66d1525eff7e23c49d0a2d4b217a58f88c2a3d6c..3ed76a5eabbe35f23d1809b669fc94dd63399764 100644 +index 0fe8f4601eedfa68c38ebadc7847ba7a07ff6fb6..3856bbe579ef6df2f220c46bc69461cab026a131 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -171,6 +171,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files -+ public final org.bxteam.divinemc.configuration.DivineWorldConfig divinemcConfig; // DivineMC - DivineMC config files ++ public final org.bxteam.divinemc.DivineWorldConfig divineConfig; // DivineMC - Configuration public static BlockPos lastPhysicsProblem; // Spigot - private int tileTickPosition; - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions -@@ -896,6 +897,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -898,6 +899,7 @@ 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.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // Purpur - Purpur config files -+ this.divinemcConfig = new org.bxteam.divinemc.configuration.DivineWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // DivineMC - DivineMC configuration ++ this.divineConfig = new org.bxteam.divinemc.DivineWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), env); // DivineMC - Configuration this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); diff --git a/divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch b/divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch deleted file mode 100644 index 01012c1..0000000 --- a/divinemc-server/minecraft-patches/features/0003-Add-missing-purpur-config-options.patch +++ /dev/null @@ -1,183 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 00:14:45 +0300 -Subject: [PATCH] Add missing purpur config options - - -diff --git a/net/minecraft/world/entity/animal/allay/Allay.java b/net/minecraft/world/entity/animal/allay/Allay.java -index 22e0fad86da2e7b932863ef30182355aa41424a1..d4eee24b5ab89f82e4e90f551d6651330d4508cb 100644 ---- a/net/minecraft/world/entity/animal/allay/Allay.java -+++ b/net/minecraft/world/entity/animal/allay/Allay.java -@@ -181,6 +181,18 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS - } - // Purpur end - Configurable entity base attributes - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.allayTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.allayAlwaysDropExp; -+ } -+ // DivineMC end - Add missing purpur config options -+ - @Override - protected Brain.Provider brainProvider() { - 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 1d7ae2a08968860636918e7c66b60139a9d761b4..126af60a694600d40e3ae6bd39e94e55dd9d0b6e 100644 ---- a/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/net/minecraft/world/entity/animal/camel/Camel.java -@@ -97,6 +97,18 @@ public class Camel extends AbstractHorse { - } - // Purpur end - Make entity breeding times configurable - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.camelTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.camelAlwaysDropExp; -+ } -+ // DivineMC end - Add missing purpur config options -+ - @Override - public void addAdditionalSaveData(CompoundTag compound) { - super.addAdditionalSaveData(compound); -diff --git a/net/minecraft/world/entity/animal/frog/Frog.java b/net/minecraft/world/entity/animal/frog/Frog.java -index c4ea9485294b7dec2582c638802f003ad70659b6..d286d4a45b6c8d5c684ad11500d2ad1a10a70c18 100644 ---- a/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/net/minecraft/world/entity/animal/frog/Frog.java -@@ -165,6 +165,23 @@ public class Frog extends Animal implements VariantHolder> { - } - // Purpur end - Ridables - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.frogTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.frogAlwaysDropExp; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(level().purpurConfig.frogMaxHealth); -+ } -+ // DivineMC end -+ - // Purpur start - Make entity breeding times configurable - @Override - public int getPurpurBreedTime() { -diff --git a/net/minecraft/world/entity/animal/frog/Tadpole.java b/net/minecraft/world/entity/animal/frog/Tadpole.java -index e888e606b4b14fa6485de7426bc146b6005962af..a4383a7d41c91f9e478c7e221828ba92437af06c 100644 ---- a/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -107,6 +107,23 @@ public class Tadpole extends AbstractFish { - } - // Purpur end - Ridables - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.tadpoleTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.tadpoleAlwaysDropExp; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(level().purpurConfig.tadpoleMaxHealth); -+ } -+ // DivineMC end - Add missing purpur config options -+ - @Override - 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 11a5da22149a61ca48bbb0a8ed10b71e91a5cc98..ed1ffc1578e50fa6fedc6124fe016a1535c0e968 100644 ---- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -120,6 +120,18 @@ public class Sniffer extends Animal { - } - // Purpur end - Make entity breeding times configurable - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.snifferTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.snifferAlwaysDropExp; -+ } -+ // DivineMC end -+ - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -diff --git a/net/minecraft/world/entity/monster/warden/Warden.java b/net/minecraft/world/entity/monster/warden/Warden.java -index f968e5c99bdb23b268bc34ea1ba5d54ae9ad0ff9..f74c784906208034f51b31bd9aba45733c3ebebe 100644 ---- a/net/minecraft/world/entity/monster/warden/Warden.java -+++ b/net/minecraft/world/entity/monster/warden/Warden.java -@@ -155,6 +155,23 @@ public class Warden extends Monster implements VibrationSystem { - } - // Purpur end - Ridables - -+ // DivineMC start - Add missing purpur config options -+ @Override -+ public boolean isSensitiveToWater() { -+ return level().purpurConfig.wardenTakeDamageFromWater; -+ } -+ -+ @Override -+ public boolean isAlwaysExperienceDropper() { -+ return level().purpurConfig.wardenAlwaysDropExp; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(level().purpurConfig.wardenMaxHealth); -+ } -+ // DivineMC end -+ - @Override - public Packet getAddEntityPacket(ServerEntity entity) { - return new ClientboundAddEntityPacket(this, entity, this.hasPose(Pose.EMERGING) ? 1 : 0); -diff --git a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -index b230955ae880d84fde40b4feffa5caf3c4449eb7..5b88c69427d5915ff547e4caf7b5656e96912e93 100644 ---- a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -+++ b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java -@@ -26,8 +26,8 @@ import net.minecraft.world.level.gameevent.GameEvent; - import net.minecraft.world.level.storage.loot.LootTable; - - 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 static final int CONTAINER_SIZE = org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9; // DivineMC - Add missing purpur config options -+ private NonNullList itemStacks = NonNullList.withSize(org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9, ItemStack.EMPTY); // DivineMC - Add missing purpur config options - @Nullable - private ResourceKey lootTable; - private long lootTableSeed; -@@ -118,7 +118,7 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto - - @Override - public int getContainerSize() { -- return 27; -+ return org.purpurmc.purpur.PurpurConfig.chestBoatRows * 9; // DivineMC - Add missing purpur config options - } - - @Override diff --git a/divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch b/divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch new file mode 100644 index 0000000..0ba337c --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0003-Implement-Secure-Seed.patch @@ -0,0 +1,433 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 00:54:57 +0300 +Subject: [PATCH] Implement Secure Seed + +Original license: GPLv3 +Original project: https://github.com/plasmoapp/matter + +diff --git a/net/minecraft/server/commands/SeedCommand.java b/net/minecraft/server/commands/SeedCommand.java +index a65affc41a4fc299bc2281f0f53f2e075633899d..7284c984f9309d4a862fe6f89df993c9a6a9efa6 100644 +--- a/net/minecraft/server/commands/SeedCommand.java ++++ b/net/minecraft/server/commands/SeedCommand.java +@@ -12,6 +12,17 @@ public class SeedCommand { + long seed = context.getSource().getLevel().getSeed(); + Component component = ComponentUtils.copyOnClickText(String.valueOf(seed)); + context.getSource().sendSuccess(() -> Component.translatable("commands.seed.success", component), false); ++ ++ // DivineMC start - Implement Secure Seed ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ su.plo.matter.Globals.setupGlobals(context.getSource().getLevel()); ++ String seedStr = su.plo.matter.Globals.seedToString(su.plo.matter.Globals.worldSeed); ++ Component featureSeedComponent = ComponentUtils.copyOnClickText(seedStr); ++ ++ context.getSource().sendSuccess(() -> Component.translatable(("Feature seed: %s"), featureSeedComponent), false); ++ } ++ // DivineMC end - Implement Secure Seed ++ + return (int)seed; + })); + } +diff --git a/net/minecraft/server/dedicated/DedicatedServerProperties.java b/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 5748658abf0b90812005ae9d426df92daf5532f0..8b4b91be368b4195326eeb1c714d713a95f28d76 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 6540b2d6a1062d883811ce240c49d30d1925b291..2678bf59d557f085c7265e2f3eb038647723d35e 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -652,6 +652,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + public ChunkGenerator getGenerator() { ++ su.plo.matter.Globals.setupGlobals(level); // DivineMC - Implement Secure Seed + return this.chunkMap.generator(); + } + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 59d03ddc42d53e2b825abe0cf2ab24e85d586a19..dd113d67356177a8d98ea10a8b6d4a4d5159674c 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -634,6 +634,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); // DivineMC - Implement 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..66dffb8a2c6725ea2502b498044b854f043f3c73 100644 +--- a/net/minecraft/world/entity/monster/Slime.java ++++ b/net/minecraft/world/entity/monster/Slime.java +@@ -423,8 +423,13 @@ public class Slime extends Mob implements Enemy { + return false; + } + +- 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 ++ ChunkPos chunkPos = new ChunkPos(pos); ++ // DivineMC start - Implement Secure Seed ++ boolean isSlimeChunk = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? 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; ++ // DivineMC end - Implement 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..80a0f5524e91e55d716e93c29e199d9816b0072a 100644 +--- a/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -82,6 +82,10 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + public final Map blockEntities = new Object2ObjectOpenHashMap<>(); + protected final LevelHeightAccessor levelHeightAccessor; + protected final LevelChunkSection[] sections; ++ // DivineMC start - Implement Secure Seed ++ private boolean slimeChunk; ++ private boolean hasComputedSlimeChunk; ++ // DivineMC end - Implement 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); +@@ -191,6 +195,17 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + return GameEventListenerRegistry.NOOP; + } + ++ // DivineMC start - Implement Secure Seed ++ public boolean isSlimeChunk() { ++ if (!hasComputedSlimeChunk) { ++ hasComputedSlimeChunk = true; ++ slimeChunk = su.plo.matter.WorldgenCryptoRandom.seedSlimeChunk(chunkPos.x, chunkPos.z).nextInt(10) == 0; ++ } ++ ++ return slimeChunk; ++ } ++ // DivineMC end - Implement 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..7e0b602e9fd9e3b3f60014ab179b3a82e3bf5c2a 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())); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(blockPos.getX(), blockPos.getZ(), su.plo.matter.Globals.Salt.UNDEFINED, 0) ++ : new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); ++ // DivineMC end - Implement 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,18 @@ 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); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ 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); ++ } ++ // DivineMC end - Implement 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 619b98e42e254c0c260c171a26a2472ddf59b885..a3093ef1b47544f2eb02d366040526697dafc8db 100644 +--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java ++++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +@@ -205,14 +205,21 @@ public class ChunkGeneratorStructureState { + List> list = new ArrayList<>(count); + int spread = placement.spread(); + HolderSet holderSet = placement.preferredBiomes(); +- RandomSource randomSource = RandomSource.create(); +- // 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); +- } else { +- // Paper end - Add missing structure set seed configs +- randomSource.setSeed(this.concentricRingsSeed); +- } // Paper - Add missing structure set seed configs ++ // DivineMC start - Implement Secure Seed ++ RandomSource randomSource = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.STRONGHOLDS, 0) ++ : RandomSource.create(); ++ ++ if (!org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ // 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); ++ } else { ++ // Paper end - Add missing structure set seed configs ++ randomSource.setSeed(this.concentricRingsSeed); ++ } // Paper - Add missing structure set seed configs ++ } ++ // DivineMC end - Implement 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..9494e559113798fe451a6d0226be3ae0449021dc 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()); // DivineMC - Implement 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..630f6c409db349819fc5fd19a3d78fadb4cfb6d6 100644 +--- a/net/minecraft/world/level/levelgen/WorldOptions.java ++++ b/net/minecraft/world/level/levelgen/WorldOptions.java +@@ -9,17 +9,28 @@ import net.minecraft.util.RandomSource; + import org.apache.commons.lang3.StringUtils; + + public class WorldOptions { ++ // DivineMC start - Implement Secure Seed ++ private static final boolean isSecureSeedEnabled = org.bxteam.divinemc.DivineConfig.enableSecureSeed; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( +- instance -> instance.group( ++ instance -> isSecureSeedEnabled ++ ? instance.group( + Codec.LONG.fieldOf("seed").stable().forGetter(WorldOptions::seed), ++ Codec.LONG_STREAM.fieldOf("feature_seed").stable().forGetter(WorldOptions::featureSeedStream), + 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)) ++ 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), ++ Codec.STRING.lenientOptionalFieldOf("legacy_custom_options").stable().forGetter(worldOptions -> worldOptions.legacyCustomOptions)).apply(instance, instance.stable(WorldOptions::new)) + ); +- public static final WorldOptions DEMO_OPTIONS = new WorldOptions("North Carolina".hashCode(), true, true); ++ public static final WorldOptions DEMO_OPTIONS = isSecureSeedEnabled ++ ? new WorldOptions("North Carolina".hashCode(), su.plo.matter.Globals.createRandomWorldSeed(), true, true) ++ : new WorldOptions("North Carolina".hashCode(), true, true); ++ // DivineMC end - Implement Secure Seed + private final long seed; ++ private long[] featureSeed = su.plo.matter.Globals.createRandomWorldSeed(); // DivineMC - Implement Secure Seed + private final boolean generateStructures; + private final boolean generateBonusChest; + private final Optional legacyCustomOptions; +@@ -28,9 +39,21 @@ public class WorldOptions { + this(seed, generateStructures, generateBonusChest, Optional.empty()); + } + ++ // DivineMC start - Implement Secure Seed ++ public WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest) { ++ this(seed, featureSeed, generateStructures, bonusChest, Optional.empty()); ++ } ++ + public static WorldOptions defaultWithRandomSeed() { +- return new WorldOptions(randomSeed(), true, false); ++ return isSecureSeedEnabled ++ ? new WorldOptions(randomSeed(), su.plo.matter.Globals.createRandomWorldSeed(), true, false) ++ : new WorldOptions(randomSeed(), true, false); ++ } ++ ++ private WorldOptions(long seed, java.util.stream.LongStream featureSeed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { ++ this(seed, featureSeed.toArray(), generateStructures, bonusChest, legacyCustomOptions); + } ++ // DivineMC end - Implement Secure Seed + + public static WorldOptions testWorldWithRandomSeed() { + return new WorldOptions(randomSeed(), false, false); +@@ -43,10 +66,27 @@ public class WorldOptions { + this.legacyCustomOptions = legacyCustomOptions; + } + ++ // DivineMC start - Implement Secure Seed ++ private WorldOptions(long seed, long[] featureSeed, boolean generateStructures, boolean bonusChest, Optional legacyCustomOptions) { ++ this(seed, generateStructures, bonusChest, legacyCustomOptions); ++ this.featureSeed = featureSeed; ++ } ++ // DivineMC end - Implement Secure Seed ++ + public long seed() { + return this.seed; + } + ++ // DivineMC start - Implement Secure Seed ++ public long[] featureSeed() { ++ return this.featureSeed; ++ } ++ ++ public java.util.stream.LongStream featureSeedStream() { ++ return java.util.stream.LongStream.of(this.featureSeed); ++ } ++ // DivineMC end - Implement Secure Seed ++ + public boolean generateStructures() { + return this.generateStructures; + } +@@ -59,17 +99,25 @@ public class WorldOptions { + return this.legacyCustomOptions.isPresent(); + } + ++ // DivineMC start - Implement 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); + } ++ // DivineMC end - Implement 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..6759df026a29810021ddb37f3ddb62382b83a94e 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())); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(0, 0, su.plo.matter.Globals.Salt.GEODE_FEATURE, 0) ++ : new WorldgenRandom(new LegacyRandomSource(worldGenLevel.getSeed())); ++ // DivineMC end - Implement 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..7c35949c9a2f102e9da246fd5bf81d9e14eb36ca 100644 +--- a/net/minecraft/world/level/levelgen/structure/Structure.java ++++ b/net/minecraft/world/level/levelgen/structure/Structure.java +@@ -249,6 +249,14 @@ public abstract class Structure { + } + + private static WorldgenRandom makeRandom(long seed, ChunkPos chunkPos) { ++ // DivineMC start - Implement Secure Seed ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ return new su.plo.matter.WorldgenCryptoRandom( ++ chunkPos.x, chunkPos.z, su.plo.matter.Globals.Salt.GENERATE_FEATURE, seed ++ ); ++ } ++ // DivineMC end - Implement 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..f01d96895ab348971fb31b614026fb3d106e9a3e 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()); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ 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()); ++ } ++ // DivineMC end - Implement 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..7174a9767cbc94544be81c74d6468f3f73386edc 100644 +--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -118,8 +118,17 @@ 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); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom; ++ if (org.bxteam.divinemc.DivineConfig.enableSecureSeed) { ++ 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); ++ } ++ // DivineMC end - Implement 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..63143ceec98f7a84ec4064d05e8f88c11200172f 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(); ++ // DivineMC start - Implement Secure Seed ++ WorldgenRandom worldgenRandom = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new su.plo.matter.WorldgenCryptoRandom(context.chunkPos().x, context.chunkPos().z, su.plo.matter.Globals.Salt.JIGSAW_PLACEMENT, 0) ++ : context.random(); ++ // DivineMC end - Implement Secure Seed + Registry registry = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL); + Rotation random = Rotation.getRandom(worldgenRandom); + StructureTemplatePool structureTemplatePool = startPool.unwrapKey() diff --git a/divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch b/divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch new file mode 100644 index 0000000..dca0f27 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0004-Async-Pathfinding.patch @@ -0,0 +1,810 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 01:04:55 +0300 +Subject: [PATCH] Async Pathfinding + +Original code by Bloom-host, licensed under GPL v3 +You can find the original code on https://github.com/Bloom-host/Petal + +Makes most pathfinding-related work happen asynchronously + +diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +index 67cbf9f5760fae5db6f31e64095cd1b6be6ade8e..aadc05082a029ab41ab2512f5cd5b3d2a361e097 100644 +--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -94,21 +94,54 @@ 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); ++ // DivineMC start - Async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ // await on path async ++ Path possiblePath = findPathToPois(mob, set); ++ ++ // wait on the path to be processed ++ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ // read canReach check ++ if (path == null || !path.canReach()) { ++ for (Pair, BlockPos> pair : set) { ++ map.computeIfAbsent( ++ pair.getSecond().asLong(), ++ m -> new JitteredLinearRetry(mob.level().random, time) ++ ); ++ } ++ return; ++ } ++ BlockPos blockPos = path.getTarget(); ++ poiManager.getType(blockPos).ifPresent(poiType -> { ++ poiManager.take(acquirablePois, ++ (holder, blockPos2) -> blockPos2.equals(blockPos), ++ blockPos, ++ 1 ++ ); ++ memoryAccessor.set(GlobalPos.of(level.dimension(), blockPos)); ++ entityEventId.ifPresent(status -> level.broadcastEntityEvent(mob, status)); ++ map.clear(); ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ }); + }); + } else { +- for (Pair, BlockPos> pair : set) { +- map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); ++ 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); ++ }); ++ } else { ++ for (Pair, BlockPos> pair : set) { ++ map.computeIfAbsent(pair.getSecond().asLong(), l -> new AcquirePoi.JitteredLinearRetry(level.random, time)); ++ } + } + } ++ // DivineMC end - Async path processing + + return true; + } +diff --git a/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +index 621ba76784f2b92790eca62be4d0688834335ab6..3f676309af0d5529413fa047a7f3cac99260b9ad 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; // DivineMC - async path processing + @Nullable + private BlockPos lastTargetPos; + private float speedModifier; +@@ -53,9 +54,11 @@ 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.bxteam.divinemc.DivineConfig.asyncPathfinding && !flag && this.tryComputePath(owner, walkTarget, level.getGameTime())) { // DivineMC - async path processing + this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); + return true; ++ } else if (org.bxteam.divinemc.DivineConfig.asyncPathfinding && !flag) { // DivineMC - async pathfinding ++ return true; + } else { + brain.eraseMemory(MemoryModuleType.WALK_TARGET); + if (flag) { +@@ -69,6 +72,7 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected boolean canStillUse(ServerLevel level, Mob entity, long gameTime) { ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding && !this.finishedProcessing) return true; // DivineMC - 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,27 +99,98 @@ public class MoveToTargetSink extends Behavior { + + @Override + protected void start(ServerLevel level, Mob entity, long gameTime) { ++ // DivineMC start - start processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ 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; ++ } ++ // DivineMC end - start processing + 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) { +- Path path = owner.getNavigation().getPath(); +- Brain brain = owner.getBrain(); +- if (this.path != path) { +- this.path = path; +- brain.setMemory(MemoryModuleType.PATH, path); +- } ++ // DivineMC start - Async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ if (this.path != null && !this.path.isProcessed()) return; // wait for processing + +- if (path != null && this.lastTargetPos != null) { +- WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); +- if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0 && this.tryComputePath(owner, walkTarget, level.getGameTime())) { +- this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); +- this.start(level, owner, gameTime); ++ 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 { ++ Path path = owner.getNavigation().getPath(); ++ Brain brain = owner.getBrain(); ++ if (this.path != path) { ++ this.path = path; ++ brain.setMemory(MemoryModuleType.PATH, path); ++ } ++ ++ if (path != null && this.lastTargetPos != null) { ++ WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get(); ++ if (walkTarget.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) > 4.0 ++ && this.tryComputePath(owner, walkTarget, level.getGameTime())) { ++ this.lastTargetPos = walkTarget.getTarget().currentBlockPosition(); ++ this.start(level, owner, gameTime); ++ } ++ } ++ } ++ // DivineMC end - Async path processing ++ } ++ ++ // DivineMC start - Async path processing ++ @Nullable ++ private Path computePath(Mob entity, WalkTarget walkTarget) { ++ BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); ++ 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); + } ++ // DivineMC end - Async path processing + + 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..5e4e4ab9b50fcddfd022dbab15620c9c0cb53645 100644 +--- a/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java ++++ b/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java +@@ -60,17 +60,38 @@ public class SetClosestHomeAsWalkTarget { + poi -> poi.is(PoiTypes.HOME), predicate, mob.blockPosition(), 48, PoiManager.Occupancy.ANY + ) + .collect(Collectors.toSet()); +- 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); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ // await on path async ++ Path possiblePath = AcquirePoi.findPathToPois(mob, set); ++ ++ // wait on the path to be processed ++ org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor.awaitProcessing(possiblePath, path -> { ++ if (path == null || !path.canReach() || mutableInt.getValue() < 5) { // read canReach check ++ map.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, speedModifier, 1)); ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ } ++ }); ++ } else { ++ 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()); + } +- } else if (mutableInt.getValue() < 5) { +- map.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue()); + } ++ // DivineMC end - async path processing + + return true; + } else { +diff --git a/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java b/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java +index d8f532c5e68ff4dff933556c4f981e9474c044e6..37f3d3888ea2a862d006cf2b201f9715bcb8ce1e 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()) { // DivineMC - async path processing + 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..0cd0d1059ac3612ba34f1a47a5023a8d07ee612f 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); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.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; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new AmphibiousNodeEvaluator(false); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + 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..0e680d3954d5847125484c2a93d9cd8e133ad8c3 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); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ FlyNodeEvaluator nodeEvaluator = new FlyNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new FlyNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + 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; // DivineMC - 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..f544bf28ac6531061da08c726684687416b9426c 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); + } + ++ // DivineMC start - async path processing ++ protected static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + 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 b44f2c49509d847817a78e9c4fb1499fb378054b..c0b61c38a82fd3de046b550a49d8cde6afa9d9af 100644 +--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -169,6 +169,10 @@ public abstract class PathNavigation { + return null; + } else if (!this.canUpdatePath()) { + return null; ++ // DivineMC start - catch early if it's still processing these positions let it keep processing ++ } else if (this.path instanceof org.bxteam.divinemc.entity.pathfinding.AsyncPath asyncPath && !asyncPath.isProcessed() && asyncPath.hasSameProcessingPositions(targets)) { ++ return this.path; ++ // DivineMC end - catch early if it's still processing these positions let it keep processing + } else if (this.path != null && !this.path.isDone() && targets.contains(this.targetPos)) { + return this.path; + } else { +@@ -195,12 +199,30 @@ 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); +- profilerFiller.pop(); +- if (path != null && path.getTarget() != null) { +- this.targetPos = path.getTarget(); +- this.reachRange = accuracy; +- this.resetStuckTimeout(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ // assign early a target position. most calls will only have 1 position ++ if (!targets.isEmpty()) this.targetPos = targets.iterator().next(); ++ ++ org.bxteam.divinemc.entity.pathfinding.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 { ++ profilerFiller.pop(); ++ if (path != null && path.getTarget() != null) { ++ this.targetPos = path.getTarget(); ++ this.reachRange = accuracy; ++ this.resetStuckTimeout(); ++ } + } ++ // DivineMC end - async path processing + + return path; + } +@@ -251,8 +273,8 @@ public abstract class PathNavigation { + if (this.isDone()) { + return false; + } else { +- this.trimPath(); +- if (this.path.getNodeCount() <= 0) { ++ if (path.isProcessed()) this.trimPath(); // DivineMC - only trim if processed ++ if (path.isProcessed() && this.path.getNodeCount() <= 0) { // DivineMC - only check node count if processed + return false; + } else { + this.speedModifier = speed; +@@ -275,6 +297,7 @@ public abstract class PathNavigation { + if (this.hasDelayedRecomputation) { + this.recomputePath(); + } ++ if (this.path != null && !this.path.isProcessed()) return; // DivineMC - skip pathfinding if we're still processing + + if (!this.isDone()) { + if (this.canUpdatePath()) { +@@ -304,6 +327,7 @@ public abstract class PathNavigation { + } + + protected void followThePath() { ++ if (!this.path.isProcessed()) return; // DivineMC - 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(); +@@ -460,7 +484,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) { // DivineMC - 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..504911c14aad5c53aeea2c71bda3978f022601dd 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); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.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; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.allowBreaching = this.mob.getType() == EntityType.DOLPHIN; + this.nodeEvaluator = new SwimNodeEvaluator(this.allowBreaching); + this.nodeEvaluator.setCanPassDoors(false); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + 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..3a8b3c71f6fd0c0f57a3190dc8a0f808c6d1002b 100644 +--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +@@ -57,17 +57,37 @@ 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); ++ // DivineMC start - async pathfinding ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ Path possiblePath = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ org.bxteam.divinemc.entity.pathfinding.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 { ++ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ 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); + } +- } else if (this.triedCount < 5) { +- this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate); + } ++ // DivineMC end - async pathfinding + } + } + } +diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java +index d5727999eb67ff30dbf47865d59452483338e170..ddbee0f0f42fae0a26321bb324d22f5e7520ae72 100644 +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -936,7 +936,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()) { // DivineMC - check processing + boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos); + if (!flag) { + this.dropAndBlacklistHive(); +@@ -990,7 +990,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(); // DivineMC - 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 143a740ce2e7f9d384b71b4d64e8b8218f60cba7..3c657179644750846180ab1c45250a9e17dda396 100644 +--- a/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/net/minecraft/world/entity/animal/frog/Frog.java +@@ -480,6 +480,17 @@ public class Frog extends Animal implements VariantHolder> { + super(mob, level); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.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; ++ }; ++ // DivineMC end - async path processing ++ + @Override + public boolean canCutCorner(PathType pathType) { + return pathType != PathType.WATER_BORDER && super.canCutCorner(pathType); +@@ -488,6 +499,11 @@ public class Frog extends Animal implements VariantHolder> { + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new Frog.FrogNodeEvaluator(true); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC end - async path processing + 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..2686df57d9d48db1438278d0d053bdbd3c65c0a7 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()) { // DivineMC - 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..2329973f735e1ae7fbb76798dc113f4261c906b7 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); + } + ++ // DivineMC start - async path processing ++ private static final org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator = (org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorFeatures nodeEvaluatorFeatures) -> { ++ WalkNodeEvaluator nodeEvaluator = new WalkNodeEvaluator(); ++ nodeEvaluator.setCanPassDoors(nodeEvaluatorFeatures.canPassDoors()); ++ nodeEvaluator.setCanFloat(nodeEvaluatorFeatures.canFloat()); ++ nodeEvaluator.setCanWalkOverFences(nodeEvaluatorFeatures.canWalkOverFences()); ++ nodeEvaluator.setCanOpenDoors(nodeEvaluatorFeatures.canOpenDoors()); ++ return nodeEvaluator; ++ }; ++ // DivineMC end - async path processing ++ + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, nodeEvaluatorGenerator); ++ } ++ // DivineMC 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 f968e5c99bdb23b268bc34ea1ba5d54ae9ad0ff9..7b185f07f099c2704ecd5c983a11f4085a4f13c2 100644 +--- a/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/net/minecraft/world/entity/monster/warden/Warden.java +@@ -602,6 +602,16 @@ public class Warden extends Monster implements VibrationSystem { + @Override + protected PathFinder createPathFinder(int maxVisitedNodes) { + this.nodeEvaluator = new WalkNodeEvaluator(); ++ // DivineMC start - async path processing ++ if (org.bxteam.divinemc.DivineConfig.asyncPathfinding) { ++ return new PathFinder(this.nodeEvaluator, maxVisitedNodes, GroundPathNavigation.nodeEvaluatorGenerator) { ++ @Override ++ protected float distance(Node first, Node second) { ++ return first.distanceToXZ(second); ++ } ++ }; ++ } ++ // DivineMC end - async path processing + return new PathFinder(this.nodeEvaluator, maxVisitedNodes) { + @Override + protected float distance(Node first, Node second) { +diff --git a/net/minecraft/world/level/pathfinder/Path.java b/net/minecraft/world/level/pathfinder/Path.java +index d6d3c8f5e5dd4a8cab0d3fcc131c3a59f06130c6..839653a997f1e10970fa2956fadaf493808cb206 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; + } + ++ // DivineMC start - 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; ++ } ++ // DivineMC end - async path processing ++ + public void advance() { + this.nextNodeIndex++; + } +@@ -99,6 +110,7 @@ public class Path { + } + + public boolean sameAs(@Nullable Path pathentity) { ++ if (pathentity == this) return true; // DivineMC - async path processing + 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 81de6c1bbef1cafd3036e736dd305fbedc8368c6..0796343f89c8424d0f7553c60dde952ab06b9f3a 100644 +--- a/net/minecraft/world/level/pathfinder/PathFinder.java ++++ b/net/minecraft/world/level/pathfinder/PathFinder.java +@@ -25,11 +25,19 @@ public class PathFinder { + public final NodeEvaluator nodeEvaluator; + private static final boolean DEBUG = false; + private final BinaryHeap openSet = new BinaryHeap(); ++ private final @Nullable org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator; // DivineMC - we use this later to generate an evaluator + +- public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes) { ++ // DivineMC start - support nodeEvaluatorgenerators ++ public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes, @Nullable org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorGenerator nodeEvaluatorGenerator) { // DivineMC - add nodeEvaluatorGenerator + this.nodeEvaluator = nodeEvaluator; + this.maxVisitedNodes = maxVisitedNodes; ++ this.nodeEvaluatorGenerator = nodeEvaluatorGenerator; ++ } ++ ++ public PathFinder(NodeEvaluator nodeEvaluator, int maxVisitedNodes) { ++ this(nodeEvaluator, maxVisitedNodes, null); + } ++ // DivineMC end - support nodeEvaluatorgenerators + + public void setMaxVisitedNodes(int maxVisitedNodes) { + this.maxVisitedNodes = maxVisitedNodes; +@@ -37,26 +45,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(); ++ // DivineMC start - use a generated evaluator if we have one otherwise run sync ++ if (!org.bxteam.divinemc.DivineConfig.asyncPathfinding) ++ this.openSet.clear(); // it's always cleared in processPath ++ NodeEvaluator nodeEvaluator = this.nodeEvaluatorGenerator == null ++ ? this.nodeEvaluator ++ : org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.takeNodeEvaluator(this.nodeEvaluatorGenerator, this.nodeEvaluator); ++ nodeEvaluator.prepare(region, mob); ++ Node start = nodeEvaluator.getStart(); ++ // DivineMC end - use a generated evaluator if we have one otherwise run sync + if (start == null) { ++ org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); // DivineMC - 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)); // DivineMC - handle nodeEvaluatorGenerator + } + // Paper end - Perf: remove streams and optimize collection +- Path path = this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier); +- this.nodeEvaluator.done(); +- return path; ++ // DivineMC start - async path processing ++ if (this.nodeEvaluatorGenerator == null) { ++ // run sync :( ++ org.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); ++ return this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier); ++ } ++ ++ return new org.bxteam.divinemc.entity.pathfinding.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.bxteam.divinemc.entity.pathfinding.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator); ++ } ++ }); ++ // DivineMC end - async path processing + } + } + + @Nullable + private Path findPath(Node node, List> positions, float maxRange, int accuracy, float searchDepthMultiplier) { // Paper - optimize collection ++ // DivineMC start - 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 ++ // DivineMC end - split pathfinding into the original sync method for compat and processing for delaying + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("find_path"); + profilerFiller.markForCharting(MetricCategory.PATH_FINDING); +@@ -95,7 +140,7 @@ public class PathFinder { + } + + if (!(node1.distanceTo(node) >= maxRange)) { +- int neighbors = this.nodeEvaluator.getNeighbors(this.neighbors, node1); ++ int neighbors = nodeEvaluator.getNeighbors(this.neighbors, node1); // DivineMC - use provided nodeEvaluator + + for (int i2 = 0; i2 < neighbors; i2++) { + Node node2 = this.neighbors[i2]; diff --git a/divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch b/divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch new file mode 100644 index 0000000..114bd44 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0005-Threaded-light-engine.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 01:14:58 +0300 +Subject: [PATCH] Threaded light engine + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index b3f498558614243cf633dcd71e3c49c2c55e6e0f..da2921268a9aa4545a12c155a33bed9fccb4f19c 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -212,7 +212,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen"); + this.progressListener = progressListener; + this.chunkStatusListener = chunkStatusListener; +- ConsecutiveExecutor consecutiveExecutor1 = new ConsecutiveExecutor(dispatcher, "light"); ++ ConsecutiveExecutor consecutiveExecutor1 = onLightExecutorInit(ConsecutiveExecutor::new); // DivineMC - Threaded light engine + // Paper - rewrite chunk system + this.lightEngine = new ThreadedLevelLightEngine( + lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor1, null // Paper - rewrite chunk system +@@ -232,6 +232,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system + } + ++ // DivineMC start - Threaded light engine ++ private java.util.concurrent.ExecutorService lightThread = null; ++ ++ private ConsecutiveExecutor onLightExecutorInit(java.util.function.BiFunction original) { ++ lightThread = new java.util.concurrent.ThreadPoolExecutor( ++ 1, 1, ++ 0, java.util.concurrent.TimeUnit.SECONDS, ++ new java.util.concurrent.LinkedBlockingQueue<>(), ++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setPriority(Thread.NORM_PRIORITY - 1).setDaemon(true).setNameFormat(String.format("%s - Light", level.dimension().location().toDebugFileName())).build() ++ ); ++ return original.apply(lightThread, "light"); ++ } ++ // DivineMC end - Threaded light engine ++ + private void setChunkUnsaved(ChunkPos chunkPos) { + // Paper - rewrite chunk system + } diff --git a/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch b/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch new file mode 100644 index 0000000..d1078a5 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch @@ -0,0 +1,333 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 01:18:49 +0300 +Subject: [PATCH] Multithreaded Tracker + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index da2921268a9aa4545a12c155a33bed9fccb4f19c..e3427e7aecfc4e1fafb38316824aa1ee50c9901a 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -264,9 +264,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); +- for (int i = 0, len = inRange.size(); i < len; i++) { +- ++(backingSet[i].mobCounts[index]); ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ final ServerPlayer player = backingSet[i]; ++ if (player == null) continue; ++ ++(player.mobCounts[index]); ++ } ++ } else { ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ ++(backingSet[i].mobCounts[index]); ++ } + } ++ // DivineMC end - Multithreaded tracker + } + + // Paper start - per player mob count backoff +@@ -950,6 +960,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker + } + ++ // DivineMC start - Multithreaded tracker ++ private final java.util.concurrent.ConcurrentLinkedQueue trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private boolean tracking = false; ++ ++ public void runOnTrackerMainThread(final Runnable runnable) { ++ if (false && this.tracking) { // TODO: check here ++ this.trackerMainThreadTasks.add(runnable); ++ } else { ++ runnable.run(); ++ } ++ } ++ // DivineMC end - Multithreaded tracker ++ + // Paper start - optimise entity tracker + private void newTrackerTick() { + 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)this.level).moonrise$getEntityLookup();; +@@ -972,6 +995,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - optimise entity tracker + + protected void tick() { ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { ++ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level; ++ org.bxteam.divinemc.entity.tracking.MultithreadedTracker.tick(level); ++ return; ++ } ++ // DivineMC end - Multithreaded tracker + // Paper start - optimise entity tracker + if (true) { + this.newTrackerTick(); +@@ -1094,7 +1124,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ // DivineMC start - Multithreaded tracker ++ public final Set seenBy = org.bxteam.divinemc.DivineConfig.multithreadedEnabled ++ ? com.google.common.collect.Sets.newConcurrentHashSet() ++ : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ // DivineMC end - Multithreaded tracker + + // Paper start - optimise entity tracker + private long lastChunkUpdate = -1L; +@@ -1121,21 +1155,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lastTrackedChunk = chunk; + + final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); ++ final int playersLen = players.size(); // Ensure length won't change in the future tasks ++ ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && org.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled) { ++ final boolean isServerPlayer = this.entity instanceof ServerPlayer; ++ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); ++ Runnable updatePlayerTasks = () -> { ++ for (int i = 0; i < playersLen; ++i) { ++ final ServerPlayer player = playersRaw[i]; ++ this.updatePlayer(player); ++ } + +- for (int i = 0, len = players.size(); i < len; ++i) { +- final ServerPlayer player = playersRaw[i]; +- this.updatePlayer(player); +- } ++ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { ++ // need to purge any players possible not in the chunk list ++ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ final ServerPlayer player = conn.getPlayer(); ++ if (!players.contains(player)) { ++ this.removePlayer(player); ++ } ++ } ++ } ++ }; ++ ++ // Only update asynchronously for real player, and sync update for fake players ++ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens ++ // To prevent visible issue with player type NPCs ++ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. ++ if (isRealPlayer || !isServerPlayer) { ++ org.bxteam.divinemc.entity.tracking.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); ++ } else { ++ updatePlayerTasks.run(); ++ } ++ } else { ++ for (int i = 0, len = players.size(); i < len; ++i) { ++ final ServerPlayer player = playersRaw[i]; ++ this.updatePlayer(player); ++ } + +- if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { +- // need to purge any players possible not in the chunk list +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { +- final ServerPlayer player = conn.getPlayer(); +- if (!players.contains(player)) { +- this.removePlayer(player); ++ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { ++ // need to purge any players possible not in the chunk list ++ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ final ServerPlayer player = conn.getPlayer(); ++ if (!players.contains(player)) { ++ this.removePlayer(player); ++ } + } + } + } ++ // DivineMC end - Multithreaded tracker + } + + @Override +@@ -1197,9 +1265,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcast(Packet packet) { +- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { ++ // DivineMC start - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { + serverPlayerConnection.send(packet); + } ++ // DivineMC end - Multithreaded tracker + } + + public void broadcastAndSend(Packet packet) { +@@ -1210,21 +1280,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcastRemoved() { +- for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { ++ // DivineMC start - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { + this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); + } ++ // DivineMC end - Multithreaded tracker + } + + public void removePlayer(ServerPlayer player) { +- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + } + } + + public void updatePlayer(ServerPlayer player) { +- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && player == null) return; // DivineMC - Multithreaded tracker + // Paper start - remove allocation of Vec3D here + // Vec3 vec3 = player.position().subtract(this.entity.position()); + double vec3_dx = player.getX() - this.entity.getX(); +diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java +index f106373ef3ac4a8685c2939c9e8361688a285913..3b4dff8867e91884b5720ca8a9cb64af655f8475 100644 +--- a/net/minecraft/server/level/ServerBossEvent.java ++++ b/net/minecraft/server/level/ServerBossEvent.java +@@ -13,7 +13,11 @@ import net.minecraft.util.Mth; + import net.minecraft.world.BossEvent; + + public class ServerBossEvent extends BossEvent { +- private final Set players = Sets.newHashSet(); ++ // DivineMC start - Multithreaded tracker - players can be removed in async tracking ++ private final Set players = org.bxteam.divinemc.DivineConfig.multithreadedEnabled ++ ? Sets.newConcurrentHashSet() ++ : Sets.newHashSet(); ++ // DivineMC end - Multithreaded tracker + private final Set unmodifiablePlayers = Collections.unmodifiableSet(this.players); + public boolean visible = true; + +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java +index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e 100644 +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -110,8 +110,13 @@ public class ServerEntity { + .forEach( + removedPassenger -> { + if (removedPassenger instanceof ServerPlayer serverPlayer1) { +- serverPlayer1.connection +- .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled && Thread.currentThread() instanceof org.bxteam.divinemc.entity.tracking.MultithreadedTracker.MultithreadedTrackerThread) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot())); ++ } else { ++ serverPlayer1.connection.teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot()); ++ } ++ // DivineMC end - Multithreaded tracker + } + } + ); +@@ -304,7 +309,11 @@ public class ServerEntity { + + public void removePairing(ServerPlayer player) { + this.entity.stopSeenByPlayer(player); +- player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())); ++ // DivineMC start - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())) ++ ); ++ // DivineMC end - Multithreaded tracker + } + + public void addPairing(ServerPlayer player) { +@@ -404,18 +413,27 @@ public class ServerEntity { + List> list = entityData.packDirty(); + if (list != null) { + this.trackedDataValues = entityData.getNonDefaultValues(); +- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); ++ // DivineMC start - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)) ++ ); ++ // DivineMC end - Multithreaded tracker + } + + if (this.entity instanceof LivingEntity) { + Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); + if (!attributesToSync.isEmpty()) { +- // CraftBukkit start - Send scaled max health +- if (this.entity instanceof ServerPlayer serverPlayer) { +- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); +- } +- // CraftBukkit end +- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); ++ // DivineMC start - Multithreaded tracker ++ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> { ++ // CraftBukkit start - Send scaled max health ++ if (this.entity instanceof ServerPlayer serverPlayer) { ++ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); ++ } ++ // CraftBukkit end ++ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); ++ }); ++ // DivineMC end - Multithreaded tracker + } + + attributesToSync.clear(); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index dd113d67356177a8d98ea10a8b6d4a4d5159674c..5d2483b2d59ae246b822d73b9996c2aa86623ab1 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -2514,7 +2514,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public LevelEntityGetter getEntities() { +- org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot ++ //org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // DivineMC - Multithreaded tracker + return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system + } + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b45b37fcdfe0d3877b368444f8f6a376d6373f59..cfd3698dc575669456136da9cbbb100fd557e5ac 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1813,7 +1813,7 @@ public class ServerGamePacketListenerImpl + } + + public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { +- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper ++ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // DivineMC - Multithreaded tracker + // Paper start - Prevent teleporting dead entities + if (this.player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +index 8013594bb4844e7a8abf28123958e7f632d39341..72593629324ccd4d70b8ed86a90fb69785d57f5f 100644 +--- 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 + ); +- private final Map modifierById = new Object2ObjectArrayMap<>(); +- private final Map permanentModifiers = new Object2ObjectArrayMap<>(); ++ // DivineMC start - Multithreaded tracker ++ private final boolean multiThreadedTrackingEnabled = org.bxteam.divinemc.DivineConfig.multithreadedEnabled; ++ private final Map modifierById = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); ++ private final Map permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); ++ // DivineMC end - Multithreaded tracker + private double baseValue; + private boolean dirty = true; + private double cachedValue; +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index a25d74592e89e3d6339479c6dc2b6f45d1932cfc..621b183211b8148bb8db256d2119c82f8a2c626b 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -19,9 +19,12 @@ import org.slf4j.Logger; + + public class AttributeMap { + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Map, AttributeInstance> attributes = new Object2ObjectOpenHashMap<>(); +- private final Set attributesToSync = new ObjectOpenHashSet<>(); +- private final Set attributesToUpdate = new ObjectOpenHashSet<>(); ++ // DivineMC start - Multithreaded tracker ++ private final boolean multiThreadedTrackingEnabled = org.bxteam.divinemc.DivineConfig.multithreadedEnabled; ++ private final Map, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); ++ private final Set attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ private final Set attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ // DivineMC end - Multithreaded tracker + private final AttributeSupplier supplier; + private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + diff --git a/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch b/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch new file mode 100644 index 0000000..d8757d2 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Jan 2025 00:54:19 +0300 +Subject: [PATCH] Async locate command + + +diff --git a/net/minecraft/server/commands/LocateCommand.java b/net/minecraft/server/commands/LocateCommand.java +index 13bcd8653d766cd0b754a22e9aab261fbc62b0a5..0bc2d17e9bad62f62ab171feb2a92b87e1c3ba6a 100644 +--- a/net/minecraft/server/commands/LocateCommand.java ++++ b/net/minecraft/server/commands/LocateCommand.java +@@ -103,44 +103,77 @@ public class LocateCommand { + } + + private static int locateStructure(CommandSourceStack source, ResourceOrTagKeyArgument.Result structure) throws CommandSyntaxException { +- Registry registry = source.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE); +- HolderSet holderSet = (HolderSet)getHolders(structure, registry) +- .orElseThrow(() -> ERROR_STRUCTURE_INVALID.create(structure.asPrintable())); +- BlockPos blockPos = BlockPos.containing(source.getPosition()); +- ServerLevel level = source.getLevel(); +- Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); +- Pair> pair = level.getChunkSource().getGenerator().findNearestMapStructure(level, holderSet, blockPos, 100, false); +- stopwatch.stop(); +- if (pair == null) { +- throw ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable()); +- } else { +- return showLocateResult(source, structure, blockPos, pair, "commands.locate.structure.success", false, stopwatch.elapsed()); +- } ++ // DivineMC start - Async structure locate ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(() -> { ++ Registry registry = source.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE); ++ HolderSet holderSet; ++ try { ++ holderSet = getHolders(structure, registry) ++ .orElseThrow(() -> ERROR_STRUCTURE_INVALID.create(structure.asPrintable())); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ return; ++ } ++ BlockPos blockPos = BlockPos.containing(source.getPosition()); ++ ServerLevel level = source.getLevel(); ++ Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); ++ Pair> pair = level.getChunkSource().getGenerator().findNearestMapStructure(level, holderSet, blockPos, 100, false); ++ stopwatch.stop(); ++ if (pair == null) { ++ try { ++ throw ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable()); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ } ++ } else { ++ showLocateResult(source, structure, blockPos, pair, "commands.locate.structure.success", false, stopwatch.elapsed()); ++ } ++ }); ++ return 0; ++ // DivineMC end - Async structure locate + } + + private static int locateBiome(CommandSourceStack source, ResourceOrTagArgument.Result biome) throws CommandSyntaxException { +- BlockPos blockPos = BlockPos.containing(source.getPosition()); +- Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); +- Pair> pair = source.getLevel().findClosestBiome3d(biome, blockPos, 6400, 32, 64); +- stopwatch.stop(); +- if (pair == null) { +- throw ERROR_BIOME_NOT_FOUND.create(biome.asPrintable()); +- } else { +- return showLocateResult(source, biome, blockPos, pair, "commands.locate.biome.success", true, stopwatch.elapsed()); +- } ++ // DivineMC start - Async biome locate ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(() -> { ++ BlockPos blockPos = BlockPos.containing(source.getPosition()); ++ Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); ++ Pair> pair = source.getLevel().findClosestBiome3d(biome, blockPos, 6400, 32, 64); ++ stopwatch.stop(); ++ if (pair == null) { ++ try { ++ throw ERROR_BIOME_NOT_FOUND.create(biome.asPrintable()); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ } ++ } else { ++ showLocateResult(source, biome, blockPos, pair, "commands.locate.biome.success", true, stopwatch.elapsed()); ++ } ++ }); ++ return 0; ++ // DivineMC end - Async biome locate + } + + private static int locatePoi(CommandSourceStack source, ResourceOrTagArgument.Result poiType) throws CommandSyntaxException { +- BlockPos blockPos = BlockPos.containing(source.getPosition()); +- ServerLevel level = source.getLevel(); +- Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); +- Optional, BlockPos>> optional = level.getPoiManager().findClosestWithType(poiType, blockPos, 256, PoiManager.Occupancy.ANY); +- stopwatch.stop(); +- if (optional.isEmpty()) { +- throw ERROR_POI_NOT_FOUND.create(poiType.asPrintable()); +- } else { +- return showLocateResult(source, poiType, blockPos, optional.get().swap(), "commands.locate.poi.success", false, stopwatch.elapsed()); +- } ++ // DivineMC start - Async poi locate ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(() -> { ++ BlockPos blockPos = BlockPos.containing(source.getPosition()); ++ ServerLevel level = source.getLevel(); ++ Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); ++ Optional, BlockPos>> optional = level.getPoiManager().findClosestWithType(poiType, blockPos, 256, PoiManager.Occupancy.ANY); ++ stopwatch.stop(); ++ if (optional.isEmpty()) { ++ try { ++ throw ERROR_POI_NOT_FOUND.create(poiType.asPrintable()); ++ } catch (CommandSyntaxException e) { ++ source.sendFailure(Component.literal(e.getMessage())); ++ } ++ } else { ++ showLocateResult(source, poiType, blockPos, optional.get().swap(), "commands.locate.poi.success", false, stopwatch.elapsed()); ++ } ++ }); ++ return 0; ++ // DivineMC end - Async poi locate + } + + public static int showLocateResult( +@@ -195,7 +228,7 @@ public class LocateCommand { + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("chat.coordinates.tooltip"))) + ); + source.sendSuccess(() -> Component.translatable(translationKey, elementName, component, i), false); +- LOGGER.info("Locating element " + elementName + " took " + duration.toMillis() + " ms"); ++ LOGGER.info("Locating element {} on Thread:{} took {} ms", elementName, Thread.currentThread().getName(), duration.toMillis()); // DivineMC - Log thread name + return i; + } + diff --git a/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch b/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch new file mode 100644 index 0000000..027468b --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch @@ -0,0 +1,988 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Jan 2025 00:59:03 +0300 +Subject: [PATCH] Parallel world ticking + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index b5817aa8f537593f6d9fc6b612c82ccccb250ac7..0c99bffa769d53562a10d23c4a9b37dc59c7f478 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -1031,7 +1031,7 @@ public final class ChunkHolderManager { + if (changedFullStatus.isEmpty()) { + return; + } +- if (!TickThread.isTickThread()) { ++ if (!TickThread.isTickThreadFor(world)) { // DivineMC - parallel world ticking + this.taskScheduler.scheduleChunkTask(() -> { + final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; + for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { +@@ -1057,7 +1057,7 @@ public final class ChunkHolderManager { + + // note: never call while inside the chunk system, this will absolutely break everything + public void processUnloads() { +- TickThread.ensureTickThread("Cannot unload chunks off-main"); ++ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // DivineMC - parallel world ticking + + if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { + throw new IllegalStateException("Cannot unload chunks recursively"); +@@ -1339,7 +1339,7 @@ public final class ChunkHolderManager { + + List changedFullStatus = null; + +- final boolean isTickThread = TickThread.isTickThread(); ++ final boolean isTickThread = TickThread.isTickThreadFor(world); // DivineMC - parallel world ticking + + boolean ret = false; + final boolean canProcessFullUpdates = processFullUpdates & isTickThread; +diff --git a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +index 4a881636ba21fae9e50950bbba2b4321b71d35ab..d019e6cd603c918b576b950a3c678862b2248c93 100644 +--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -46,7 +46,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..10ab4be4b8d7e488148bab395e344fca0d09fbb0 100644 +--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -78,7 +78,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java +index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c072546ad329c6 100644 +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -89,7 +89,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -147,7 +147,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -201,7 +201,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + world.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -251,7 +251,7 @@ public interface DispenseItemBehavior { + org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos()); + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy); + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + world.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -329,7 +329,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -389,7 +389,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event); + } + +@@ -425,7 +425,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -482,7 +482,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -510,8 +510,10 @@ public interface DispenseItemBehavior { + // CraftBukkit start + level.captureTreeGeneration = false; + if (level.capturedBlockStates.size() > 0) { +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; ++ // DivineMC start - parallel world ticking ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); ++ // DivineMC end - parallel world ticking + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld()); + List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); + level.capturedBlockStates.clear(); +@@ -548,7 +550,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -591,7 +593,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -644,7 +646,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -702,7 +704,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - only single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -783,7 +785,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +index b91b2f5ea6a1da0477541dc65fdfbfa57b9af475..e2b7ee10569812c94a5ff6d6e731941f24527c55 100644 +--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -39,7 +39,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack); + + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + world.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +index 116395b6c00a0814922516707544a9ff26d68835..37f710bfa8a9388351d1d32f6f2eeac7c8bfd4bd 100644 +--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +@@ -62,7 +62,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +index 449d9b72ff4650961daa9d1bd25940f3914a6b12..097528f85e5c0393c8b20a68aede99b4b949cb24 100644 +--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -32,7 +32,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..6fad185f34c4614f16012ec008add241f188d462 100644 +--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -25,7 +25,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos()); + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +index 5ab2c8333178335515e619b87ae420f948c83bd1..9d2bc41befd0f73b6a0f097d45fbe771e13be86f 100644 +--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -27,7 +27,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + blockSource.level().getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 781030cb2e0316151c20351f04347c8db63f43e1..527547b98b70429830a3cf82fddba202e0ba8131 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -306,6 +306,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1757,35 +1758,49 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent +- serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 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 +- profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); +- /* Drop global time updates +- if (this.tickCount % 20 == 0) { +- profilerFiller.push("timeSync"); +- this.synchronizeTime(serverLevel); ++ // DivineMC start - parallel world ticking ++ java.util.ArrayDeque> tasks = new java.util.ArrayDeque<>(); ++ try { ++ for (ServerLevel serverLevel : this.getAllLevels()) { ++ serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent ++ serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 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 ++ profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); ++ profilerFiller.push("tick"); ++ ++ serverLevelTickingSemaphore.acquire(); ++ tasks.add( ++ serverLevel.tickExecutor.submit(() -> { ++ try { ++ ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread(); ++ currentThread.currentlyTickingServerLevel = serverLevel; ++ ++ serverLevel.tick(hasTimeLeft); ++ } catch (Throwable throwable) { ++ CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); ++ ++ serverLevel.fillReportDetails(crashreport); ++ throw new ReportedException(crashreport); ++ } finally { ++ serverLevelTickingSemaphore.release(); ++ } ++ }, serverLevel) ++ ); ++ ++ profilerFiller.pop(); + profilerFiller.pop(); ++ serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions + } +- // CraftBukkit end */ + +- profilerFiller.push("tick"); +- +- try { +- serverLevel.tick(hasTimeLeft); +- } catch (Throwable var7) { +- CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world"); +- serverLevel.fillReportDetails(crashReport); +- throw new ReportedException(crashReport); ++ while (!tasks.isEmpty()) { ++ tasks.pop().get(); + } +- +- profilerFiller.pop(); +- profilerFiller.pop(); +- serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions ++ } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) { ++ throw new RuntimeException(e); // Propagate exception + } ++ // DivineMC end - parallel world ticking + this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked + + profilerFiller.popPush("connection"); +@@ -1876,6 +1891,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; + Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); + newLevels.remove(level.dimension()); ++ level.tickExecutor.shutdown(); // DivineMC - parallel world ticking + this.levels = Collections.unmodifiableMap(newLevels); + } + // CraftBukkit end +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 959d87f4cd1efe8cf591e98c7d32728067f7117c..697f690305db56ae5a05483aae37994d4e8f9f83 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -234,6 +234,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics ++ // DivineMC start - parallel world ticking ++ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(org.bxteam.divinemc.DivineConfig.parallelThreadCount); ++ DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); ++ // DivineMC end - parallel world ticking + /*// Purpur start - Purpur config files // Purpur - Configurable void damage height and damage + try { + org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 5d2483b2d59ae246b822d73b9996c2aa86623ab1..92f3e5d929997a974c367ec3ce02cda4acdb5183 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -184,7 +184,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type + private int lastSpawnChunkRadius; +- final EntityTickList entityTickList = new EntityTickList(); ++ final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - parallel world ticking + // Paper - rewrite chunk system + private final GameEventDispatcher gameEventDispatcher; + public boolean noSave; +@@ -210,6 +210,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private double preciseTime; // Purpur - Configurable daylight cycle + private boolean forceTime; // Purpur - Configurable daylight cycle + private final RandomSequences randomSequences; ++ public java.util.concurrent.ExecutorService tickExecutor; // DivineMC - parallel world ticking + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess levelStorageAccess; +@@ -702,6 +703,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.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.bxteam.divinemc.server.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // DivineMC - parallel world ticking + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle + } + +@@ -1292,7 +1294,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper start - rewrite chunk system + if ((++this.tickedBlocksOrFluids & 7L) != 0L) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); ++ // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // DivineMC - remove + } + // Paper end - rewrite chunk system + +@@ -1305,7 +1307,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper start - rewrite chunk system + if ((++this.tickedBlocksOrFluids & 7L) != 0L) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); ++ // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // DivineMC - remove + } + // Paper end - rewrite chunk system + +@@ -1565,6 +1567,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + private void addPlayer(ServerPlayer player) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // DivineMC - parallel world ticking (additional concurrency issues logs) + Entity entity = this.getEntities().get(player.getUUID()); + if (entity != null) { + LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); +@@ -1577,7 +1580,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + // CraftBukkit start + private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { +- org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // DivineMC - parallel world ticking (additional concurrency issues logs) + entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 515b2178e71a3723eace26c89032e45678145224..a33071d4b9b5ab75bcef93411e67d4f7c47d5c62 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -428,6 +428,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + // Paper end - rewrite chunk system + ++ public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - parallel world ticking ++ + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { + super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); + this.textFilter = server.createTextFilterForPlayer(this); +@@ -803,6 +805,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + + @Override + public void tick() { ++ hasTickedAtLeastOnceInNewWorld = true; // DivineMC - parallel world ticking + // CraftBukkit start + if (this.joining) { + this.joining = false; +@@ -1448,6 +1451,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return this; + } else { + // CraftBukkit start ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - parallel world ticking (additional concurrency issues logs) + /* + this.isChangingDimension = true; + LevelData levelData = level.getLevelData(); +@@ -1815,6 +1819,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return OptionalInt.empty(); + } else { + // CraftBukkit start ++ // DivineMC start - parallel world ticking ++ if (!hasTickedAtLeastOnceInNewWorld) { ++ MinecraftServer.LOGGER.warn("Ignoring request to open container " + abstractContainerMenu + " because we haven't ticked in the current world yet!", new Throwable()); ++ return OptionalInt.empty(); ++ } ++ // DivineMC end - parallel world ticking + this.containerMenu = abstractContainerMenu; // Moved up + if (!this.isImmobile()) + this.connection +@@ -1879,6 +1889,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + @Override + public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // DivineMC start - parallel world ticking (debugging) ++ if (org.bxteam.divinemc.DivineConfig.logContainerCreationStacktraces) { ++ MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace); ++ } ++ // DivineMC end - parallel world ticking (debugging) + org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit + // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cfd3698dc575669456136da9cbbb100fd557e5ac..aabce23007006fe2dca1e4ac7c0657d2a1cae30e 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -572,7 +572,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.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement && (d7 - d6 > Math.max(100.0, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner())) { // DivineMC - stop weird movement + // CraftBukkit end + LOGGER.warn( + "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5 +@@ -602,7 +602,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.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement && (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold)) { // Spigot // DivineMC - stop weird movement + 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)); + } +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index e8ff6e79ce7ba0ec8b2a90bcb81283f52106c535..6b23cf5122fe65b2ad253ed8536658441297e953 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -150,6 +150,7 @@ public abstract class PlayerList { + abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // DivineMC - parallel world ticking + player.isRealPlayer = true; // Paper + player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + GameProfile gameProfile = player.getGameProfile(); +@@ -716,6 +717,13 @@ public abstract class PlayerList { + return this.respawn(player, keepInventory, reason, eventReason, null); + } + public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) { ++ // DivineMC start - parallel world ticking (additional concurrency issues logs) ++ System.out.println("respawning player - current player container is " + player.containerMenu + " but their inventory is " + player.inventoryMenu); ++ if (location != null) // TODO: Is this really never null, or is IntelliJ IDEA tripping? Because I'm pretty sure that this can be null and there isn't any @NotNull annotations ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName()); ++ else ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + player.serverLevel().getWorld().getName()); ++ // DivineMC end - parallel world ticking (additional concurrency issues logs) + player.stopRiding(); // CraftBukkit + this.players.remove(player); + this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +@@ -726,6 +734,7 @@ public abstract class PlayerList { + ServerPlayer serverPlayer = player; + Level fromWorld = player.level(); + player.wonGame = false; ++ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // DivineMC - parallel world ticking + // CraftBukkit end + serverPlayer.connection = player.connection; + serverPlayer.restoreFrom(player, keepInventory); +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 80f2d38449f1db1d9b6926e4552d3061cb88b4af..356a1bfc610214912f58c4126cdd5694ffecfcb8 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -850,7 +850,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle +- if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities ++ if (false && !(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities // DivineMC - parallel world ticking + this.handlePortal(); + } + } +@@ -3870,6 +3870,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, "Cannot teleport entity to another world off-main, from world " + this.level.getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - parallel world ticking + List passengers = this.getPassengers(); + List list = new ArrayList<>(passengers.size()); + this.ejectPassengers(); +diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java +index 3dcd8df0b395a8fed8bc0cbe0ff78f4ae0056fd3..228c63bf506548666ce87fae694e2270c97c6770 100644 +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -93,7 +93,14 @@ public abstract class AbstractContainerMenu { + public void startOpen() {} + // CraftBukkit end + ++ public Throwable containerCreationStacktrace; // DivineMC - parallel world ticking ++ + protected AbstractContainerMenu(@Nullable MenuType menuType, int containerId) { ++ // DivineMC start - parallel world ticking (debugging) ++ if (org.bxteam.divinemc.DivineConfig.logContainerCreationStacktraces) { ++ this.containerCreationStacktrace = new Throwable(); ++ } ++ // DivineMC start - parallel world ticking (debugging) + this.menuType = menuType; + this.containerId = containerId; + } +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index 264b713e8b7c3d5f7d8e1facc90a60349f2cf414..f461b060e03edf4102290a424ab008b88d80bdc2 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -407,8 +407,8 @@ public final class ItemStack implements DataComponentHolder { + if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { + serverLevel.captureTreeGeneration = false; + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // DivineMC - parallel world ticking ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); // DivineMC - parallel world ticking + List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); + serverLevel.capturedBlockStates.clear(); + org.bukkit.event.world.StructureGrowEvent structureEvent = null; +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 3856bbe579ef6df2f220c46bc69461cab026a131..8f37c27bba829733fb8db5f35470092a76c83e98 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -172,6 +172,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files + public final org.bxteam.divinemc.DivineWorldConfig divineConfig; // DivineMC - Configuration ++ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // DivineMC - parallel world ticking (moved to world) + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -1146,6 +1147,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { + // Paper start - Protect Bedrock and End Portal/Frames from being destroyed +@@ -1530,7 +1532,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + tickingBlockEntity.tick(); + // Paper start - rewrite chunk system + if ((++tickedEntities & 7) == 0) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); ++ // ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); + } + // Paper end - rewrite chunk system + } +@@ -1553,7 +1555,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + // Paper end - Prevent block entity and entity crashes + } +- this.moonrise$midTickTasks(); // Paper - rewrite chunk system ++ // this.moonrise$midTickTasks(); // Paper - rewrite chunk system + } + + // Paper start - Option to prevent armor stands from doing entity lookups +@@ -1696,6 +1698,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, boolean validate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // DivineMC - parallel world ticking + // Paper start - Perf: Optimize capturedTileEntities lookup + net.minecraft.world.level.block.entity.BlockEntity blockEntity; + if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { +@@ -1713,6 +1716,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + + public void setBlockEntity(BlockEntity blockEntity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // DivineMC - parallel world ticking + BlockPos blockPos = blockEntity.getBlockPos(); + if (!this.isOutsideBuildHeight(blockPos)) { + // CraftBukkit start +@@ -1797,6 +1801,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + Profiler.get().incrementCounter("getEntities"); + List list = Lists.newArrayList(); + +@@ -2109,8 +2114,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public abstract RecipeAccess recipeAccess(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { +- this.randValue = this.randValue * 3 + 1013904223; +- int i = this.randValue >> 2; ++ int i = this.random.nextInt() >> 2; // DivineMC - parallel world ticking + return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15)); + } + +diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java +index e0a4d41e5bcf144ea4c10d6f633c3a95ed2c5aec..274f36581e7040c67bf8649258660228a3e8cce0 100644 +--- a/net/minecraft/world/level/block/DispenserBlock.java ++++ b/net/minecraft/world/level/block/DispenserBlock.java +@@ -50,7 +50,7 @@ public class DispenserBlock extends BaseEntityBlock { + private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); + public static final Map DISPENSER_REGISTRY = new IdentityHashMap<>(); + private static final int TRIGGER_DURATION = 4; +- public static boolean eventFired = false; // CraftBukkit ++ public static ThreadLocal eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // DivineMC - parallel world ticking + + @Override + public MapCodec codec() { +@@ -96,7 +96,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item); + if (dispenseMethod != DispenseItemBehavior.NOOP) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent +- DispenserBlock.eventFired = false; // CraftBukkit - reset event status ++ DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // DivineMC - parallel world ticking + dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item)); + } + } +diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java +index 85f0eac75784565c658c5178c544f969db3d6f54..22c527a7b44a434b66a2217ed88eca79703b2036 100644 +--- a/net/minecraft/world/level/block/FungusBlock.java ++++ b/net/minecraft/world/level/block/FungusBlock.java +@@ -76,9 +76,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock { + // CraftBukkit start + .map((value) -> { + if (this == Blocks.WARPED_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; ++ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // DivineMC - parallel world ticking + } else if (this == Blocks.CRIMSON_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; ++ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // DivineMC - parallel world ticking + } + return value; + }) +diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java +index 904369f4d7db41026183f2de7c96c2f0f4dc204d..1a3f07834fd1483aa77f7733512908b62583edda 100644 +--- a/net/minecraft/world/level/block/MushroomBlock.java ++++ b/net/minecraft/world/level/block/MushroomBlock.java +@@ -94,7 +94,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + return false; + } else { + level.removeBlock(pos, false); +- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit ++ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // DivineMC - parallel world ticking + if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { +diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java +index 12c9d60314c99fb65e640d255a2d0c6b7790ad4d..0f56ea9dd444148b134a3d97f0b7c4563df73e39 100644 +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -292,7 +292,7 @@ public class RedStoneWireBlock extends Block { + + // Paper start - Optimize redstone (Eigencraft) + // The bulk of the new functionality is found in RedstoneWireTurbo.java +- io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); ++ // io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); + + /* + * Modified version of pre-existing updateSurroundingRedstone, which is called from +@@ -308,7 +308,7 @@ public class RedStoneWireBlock extends Block { + if (orientation != null) { + source = pos.relative(orientation.getFront().getOpposite()); + } +- turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); // DivineMC - parallel world ticking + return; + } + updatePowerStrength(worldIn, pos, state, orientation, blockAdded); +@@ -336,7 +336,7 @@ public class RedStoneWireBlock extends Block { + // [Space Walker] suppress shape updates and emit those manually to + // bypass the new neighbor update stack. + if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { +- turbo.updateNeighborShapes(level, pos, state); ++ level.turbo.updateNeighborShapes(level, pos, state); // DivineMC - parallel world ticking + } + } + } +diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java +index e014f052e9b0f5ca6b28044e2389782b7d0e0cb8..d10f8909fcfa930c726f2620e3ffc912baa40be7 100644 +--- a/net/minecraft/world/level/block/SaplingBlock.java ++++ b/net/minecraft/world/level/block/SaplingBlock.java +@@ -26,7 +26,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + protected static final float AABB_OFFSET = 6.0F; + protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0); + protected final TreeGrower treeGrower; +- public static org.bukkit.TreeType treeType; // CraftBukkit ++ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // CraftBukkit // DivineMC - parallel world ticking (from Folia) + + @Override + public MapCodec codec() { +@@ -63,8 +63,10 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); + level.captureTreeGeneration = false; + if (!level.capturedBlockStates.isEmpty()) { +- org.bukkit.TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; ++ // DivineMC start - parallel world ticking ++ org.bukkit.TreeType treeType = SaplingBlock.treeTypeRT.get(); ++ SaplingBlock.treeTypeRT.set(null); ++ // DivineMC end - parallel world ticking + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); + java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); + level.capturedBlockStates.clear(); +diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index 26db603ed681a6c302596627d4dd5bf8a9bafc4e..3b56375f705a0d65901f702e53e1fc609eeaf1a6 100644 +--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -77,6 +77,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + return canUnlock(player, code, displayName, null); + } + public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) { ++ // DivineMC start - parallel world ticking ++ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.serverLevel()) { ++ net.minecraft.server.MinecraftServer.LOGGER.warn("Player " + serverPlayer.getScoreboardName() + " (" + serverPlayer.getStringUUID() + ") attempted to open a BlockEntity @ " + blockEntity.getLevel().getWorld().getName() + " " + blockEntity.getBlockPos().getX() + ", " + blockEntity.getBlockPos().getY() + ", " + blockEntity.getBlockPos().getZ() + " while they were in a different world " + serverPlayer.level().getWorld().getName() + " than the block themselves!"); ++ return false; ++ } ++ // DivineMC end - parallel world ticking + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { + final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); + net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(displayName)); +diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +index 1638eccef431fb68775af624110f1968f0c6dabd..8f3eb265adcacf6d4f71db9a298ba8a7ce99c941 100644 +--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + // Paper end - Fix NPE in SculkBloomEvent world access + + public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(pos); // DivineMC - parallel world ticking // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. + sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // DivineMC - parallel world ticking // CraftBukkit + } + + @Override +diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java +index cf7311c507de09a8f89934e430b2201e8bdffe51..30bd72ad2f66616a89b78fb0677109b8341eb132 100644 +--- a/net/minecraft/world/level/block/grower/TreeGrower.java ++++ b/net/minecraft/world/level/block/grower/TreeGrower.java +@@ -204,55 +204,59 @@ public final class TreeGrower { + // CraftBukkit start + private void setTreeType(Holder> holder) { + ResourceKey> treeFeature = holder.unwrapKey().get(); ++ // DivineMC start - parallel world ticking ++ org.bukkit.TreeType treeType; + if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; ++ treeType = org.bukkit.TreeType.TREE; + } else if (treeFeature == TreeFeatures.HUGE_RED_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; ++ treeType = org.bukkit.TreeType.RED_MUSHROOM; + } else if (treeFeature == TreeFeatures.HUGE_BROWN_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; ++ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; + } else if (treeFeature == TreeFeatures.JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; ++ treeType = org.bukkit.TreeType.COCOA_TREE; + } else if (treeFeature == TreeFeatures.JUNGLE_TREE_NO_VINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; ++ treeType = org.bukkit.TreeType.SMALL_JUNGLE; + } else if (treeFeature == TreeFeatures.PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; ++ treeType = org.bukkit.TreeType.TALL_REDWOOD; + } else if (treeFeature == TreeFeatures.SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; ++ treeType = org.bukkit.TreeType.REDWOOD; + } else if (treeFeature == TreeFeatures.ACACIA) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; ++ treeType = org.bukkit.TreeType.ACACIA; + } else if (treeFeature == TreeFeatures.BIRCH || treeFeature == TreeFeatures.BIRCH_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; ++ treeType = org.bukkit.TreeType.BIRCH; + } else if (treeFeature == TreeFeatures.SUPER_BIRCH_BEES_0002) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; ++ treeType = org.bukkit.TreeType.TALL_BIRCH; + } else if (treeFeature == TreeFeatures.SWAMP_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; ++ treeType = org.bukkit.TreeType.SWAMP; + } else if (treeFeature == TreeFeatures.FANCY_OAK || treeFeature == TreeFeatures.FANCY_OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; ++ treeType = org.bukkit.TreeType.BIG_TREE; + } else if (treeFeature == TreeFeatures.JUNGLE_BUSH) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; ++ treeType = org.bukkit.TreeType.JUNGLE_BUSH; + } else if (treeFeature == TreeFeatures.DARK_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; ++ treeType = org.bukkit.TreeType.DARK_OAK; + } else if (treeFeature == TreeFeatures.MEGA_SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; ++ treeType = org.bukkit.TreeType.MEGA_REDWOOD; + } else if (treeFeature == TreeFeatures.MEGA_PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; ++ treeType = org.bukkit.TreeType.MEGA_PINE; + } else if (treeFeature == TreeFeatures.MEGA_JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; ++ treeType = org.bukkit.TreeType.JUNGLE; + } else if (treeFeature == TreeFeatures.AZALEA_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; ++ treeType = org.bukkit.TreeType.AZALEA; + } else if (treeFeature == TreeFeatures.MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; ++ treeType = org.bukkit.TreeType.MANGROVE; + } else if (treeFeature == TreeFeatures.TALL_MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; ++ treeType = org.bukkit.TreeType.TALL_MANGROVE; + } else if (treeFeature == TreeFeatures.CHERRY || treeFeature == TreeFeatures.CHERRY_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; ++ treeType = org.bukkit.TreeType.CHERRY; + } else if (treeFeature == TreeFeatures.PALE_OAK || treeFeature == TreeFeatures.PALE_OAK_BONEMEAL) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; ++ treeType = org.bukkit.TreeType.PALE_OAK; + } else if (treeFeature == TreeFeatures.PALE_OAK_CREAKING) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; ++ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; + } else { + throw new IllegalArgumentException("Unknown tree generator " + treeFeature); + } ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(treeType); ++ // DivineMC end - parallel world ticking + } + // CraftBukkit end + } +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 5652ed5fb03a4a1a94c348109cb499196f909712..6167f72d1e374a7093f9880ab50e27eda603a680 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -367,6 +367,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + + @Nullable + public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // DivineMC - parallel world ticking + // CraftBukkit end + int y = pos.getY(); + LevelChunkSection section = this.getSection(this.getSectionIndex(y)); +diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java +index 423779a2b690f387a4f0bd07b97b50e0baefda76..f43f318577efff0c920029d467a54608614f410c 100644 +--- a/net/minecraft/world/level/entity/EntityTickList.java ++++ b/net/minecraft/world/level/entity/EntityTickList.java +@@ -11,16 +11,27 @@ 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 + ++ // DivineMC start - parallel world ticking ++ // Used to track async entity additions/removals/loops ++ private final net.minecraft.server.level.ServerLevel serverLevel; ++ ++ public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) { ++ this.serverLevel = serverLevel; ++ } ++ // DivineMC end - parallel world ticking ++ + private void ensureActiveIsNotIterated() { + // Paper - rewrite chunk system + } + + public void add(Entity entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // DivineMC - parallel world ticking + this.ensureActiveIsNotIterated(); + this.entities.add(entity); // Paper - rewrite chunk system + } + + public void remove(Entity entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // DivineMC - parallel world ticking + this.ensureActiveIsNotIterated(); + this.entities.remove(entity); // Paper - rewrite chunk system + } +@@ -30,6 +41,7 @@ public class EntityTickList { + } + + public void forEach(Consumer entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // DivineMC - parallel world ticking + // Paper start - rewrite chunk system + // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... + // (by dfl iterator() is configured to not iterate over new entries) +diff --git a/net/minecraft/world/level/saveddata/maps/MapIndex.java b/net/minecraft/world/level/saveddata/maps/MapIndex.java +index ffe604f8397a002800e6ecc2f878d0f6f1c98703..8f6436d2e87e7e11d98dcf20f4a62a4b3c7f6d92 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapIndex.java ++++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java +@@ -34,17 +34,24 @@ public class MapIndex extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { +- for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { +- tag.putInt(entry.getKey(), entry.getIntValue()); ++ // DivineMC start - make map data thread-safe ++ synchronized (this.usedAuxIds) { ++ for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { ++ tag.putInt(entry.getKey(), entry.getIntValue()); ++ } + } +- ++ // DivineMC end - make map data thread-safe + return tag; + } + + public MapId getFreeAuxValueForMap() { +- int i = this.usedAuxIds.getInt("map") + 1; +- this.usedAuxIds.put("map", i); +- this.setDirty(); +- return new MapId(i); ++ // DivineMC start - make map data thread-safe ++ synchronized (this.usedAuxIds) { ++ int i = this.usedAuxIds.getInt("map") + 1; ++ this.usedAuxIds.put("map", i); ++ this.setDirty(); ++ return new MapId(i); ++ } ++ // DivineMC end - make map data thread-safe + } + } diff --git a/divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch b/divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch new file mode 100644 index 0000000..075f081 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0009-C2ME-opts_native_math.patch @@ -0,0 +1,401 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 31 Jan 2025 21:50:46 +0300 +Subject: [PATCH] C2ME: opts_native_math + + +diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java +index 73962e79a0f3d892e3155443a1b84508b0f4042e..1d7c8c2196afb8515802734ad756abec1d5ceaf2 100644 +--- a/net/minecraft/world/level/biome/BiomeManager.java ++++ b/net/minecraft/world/level/biome/BiomeManager.java +@@ -29,39 +29,64 @@ public class BiomeManager { + } + + public Holder getBiome(BlockPos pos) { +- int i = pos.getX() - 2; +- 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; ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ int mask = org.bxteam.divinemc.math.Bindings.c2me_natives_biome_access_sample(this.biomeZoomSeed, pos.getX(), pos.getY(), pos.getZ()); + +- 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; ++ return this.noiseBiomeSource.getNoiseBiome( ++ ((pos.getX() - 2) >> 2) + ((mask & 4) != 0 ? 1 : 0), ++ ((pos.getY() - 2) >> 2) + ((mask & 2) != 0 ? 1 : 0), ++ ((pos.getZ() - 2) >> 2) + ((mask & 1) != 0 ? 1 : 0) ++ ); ++ } else { ++ final int var0 = pos.getX() - 2; ++ final int var1 = pos.getY() - 2; ++ final int var2 = pos.getZ() - 2; ++ final int var3 = var0 >> 2; ++ final int var4 = var1 >> 2; ++ final int var5 = var2 >> 2; ++ final double var6 = (double) (var0 & 3) / 4.0; ++ final double var7 = (double) (var1 & 3) / 4.0; ++ final double var8 = (double) (var2 & 3) / 4.0; ++ int var9 = 0; ++ double var10 = Double.POSITIVE_INFINITY; ++ for (int var11 = 0; var11 < 8; ++var11) { ++ boolean var12 = (var11 & 4) == 0; ++ boolean var13 = (var11 & 2) == 0; ++ boolean var14 = (var11 & 1) == 0; ++ long var15 = var12 ? var3 : var3 + 1; ++ long var16 = var13 ? var4 : var4 + 1; ++ long var17 = var14 ? var5 : var5 + 1; ++ double var18 = var12 ? var6 : var6 - 1.0; ++ double var19 = var13 ? var7 : var7 - 1.0; ++ double var20 = var14 ? var8 : var8 - 1.0; ++ long var21 = this.biomeZoomSeed * (this.biomeZoomSeed * 6364136223846793005L + 1442695040888963407L) + var15; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var15; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; ++ double var22 = (double) ((var21 >> 24) & 1023) / 1024.0; ++ double var23 = (var22 - 0.5) * 0.9; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + this.biomeZoomSeed; ++ double var24 = (double) ((var21 >> 24) & 1023) / 1024.0; ++ double var25 = (var24 - 0.5) * 0.9; ++ var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + this.biomeZoomSeed; ++ double var26 = (double) ((var21 >> 24) & 1023) / 1024.0; ++ double var27 = (var26 - 0.5) * 0.9; ++ double var28 = Mth.square(var20 + var27) + Mth.square(var19 + var25) + Mth.square(var18 + var23); ++ if (var10 > var28) { ++ var9 = var11; ++ var10 = var28; ++ } + } +- } + +- 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); ++ int resX = (var9 & 4) == 0 ? var3 : var3 + 1; ++ int resY = (var9 & 2) == 0 ? var4 : var4 + 1; ++ int resZ = (var9 & 1) == 0 ? var5 : var5 + 1; ++ return this.noiseBiomeSource.getNoiseBiome(resX, resY, resZ); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public Holder getNoiseBiomeAtPosition(double x, double y, double z) { +diff --git a/net/minecraft/world/level/levelgen/DensityFunctions.java b/net/minecraft/world/level/levelgen/DensityFunctions.java +index fa08f06be03b2e6120ddc105563f68d551da741c..7178013421233d7dab36eb07a768907ce40e8745 100644 +--- a/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -501,6 +501,11 @@ public final class DensityFunctions { + } + + protected static final class EndIslandDensityFunction implements DensityFunction.SimpleFunction { ++ // DivineMC start - C2ME: opts_native_math ++ private final java.lang.foreign.Arena c2me$arena = java.lang.foreign.Arena.ofAuto(); ++ private java.lang.foreign.MemorySegment c2me$samplerData = null; ++ private long c2me$samplerDataPtr; ++ // DivineMC end - C2ME: opts_native_math + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of( + MapCodec.unit(new DensityFunctions.EndIslandDensityFunction(0L)) + ); +@@ -521,6 +526,16 @@ public final class DensityFunctions { + RandomSource randomSource = new LegacyRandomSource(seed); + randomSource.consumeCount(17292); + this.islandNoise = new SimplexNoise(randomSource); ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ int[] permutation = (this.islandNoise).p; ++ java.lang.foreign.MemorySegment segment = this.c2me$arena.allocate(permutation.length * 4L, 64); ++ java.lang.foreign.MemorySegment.copy(java.lang.foreign.MemorySegment.ofArray(permutation), 0L, segment, 0L, permutation.length * 4L); ++ java.lang.invoke.VarHandle.fullFence(); ++ this.c2me$samplerData = segment; ++ this.c2me$samplerDataPtr = segment.address(); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + private static float getHeightValue(SimplexNoise noise, int x, int z) { +@@ -567,7 +582,13 @@ public final class DensityFunctions { + + @Override + public double compute(DensityFunction.FunctionContext context) { +- return (getHeightValue(this.islandNoise, context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0; ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled && this.c2me$samplerDataPtr != 0L) { ++ return ((double) org.bxteam.divinemc.math.Bindings.c2me_natives_end_islands_sample(this.c2me$samplerDataPtr, context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0; ++ } else { ++ return (getHeightValue(this.islandNoise, context.blockX() / 8, context.blockZ() / 8) - 8.0) / 128.0; ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + @Override +@@ -814,10 +835,42 @@ public final class DensityFunctions { + return this.noise.getValue(context.blockX() * this.xzScale, context.blockY() * this.yScale, context.blockZ() * this.xzScale); + } + ++ // DivineMC start - C2ME: opts_native_math + @Override +- public void fillArray(double[] array, DensityFunction.ContextProvider contextProvider) { +- contextProvider.fillAllDirectly(array, this); ++ public void fillArray(double[] densities, DensityFunction.ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.noise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockX() * this.xzScale(); ++ y[i] = pos.blockY() * this.yScale(); ++ z[i] = pos.blockZ() * this.xzScale(); ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); + } ++ // DivineMC end - C2ME: opts_native_math + + @Override + public DensityFunction mapAll(DensityFunction.Visitor visitor) { +@@ -938,6 +991,46 @@ public final class DensityFunctions { + public KeyDispatchDataCodec codec() { + return CODEC; + } ++ ++ // DivineMC start - C2ME: opts_native_math ++ @Override ++ public void fillArray(final double[] densities, final ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.offsetNoise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockX() * 0.25; ++ y[i] = pos.blockY() * 0.25; ++ z[i] = pos.blockZ() * 0.25; ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); ++ for (int i = 0; i < densities.length; i++) { ++ densities[i] *= 4.0; ++ } ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public record ShiftA(@Override DensityFunction.NoiseHolder offsetNoise) implements DensityFunctions.ShiftNoise { +@@ -959,6 +1052,46 @@ public final class DensityFunctions { + public KeyDispatchDataCodec codec() { + return CODEC; + } ++ ++ // DivineMC start - C2ME: opts_native_math ++ @Override ++ public void fillArray(final double[] densities, final ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.offsetNoise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockX() * 0.25; ++ y[i] = 0; ++ z[i] = pos.blockZ() * 0.25; ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); ++ for (int i = 0; i < densities.length; i++) { ++ densities[i] *= 4.0; ++ } ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public record ShiftB(@Override DensityFunction.NoiseHolder offsetNoise) implements DensityFunctions.ShiftNoise { +@@ -980,6 +1113,46 @@ public final class DensityFunctions { + public KeyDispatchDataCodec codec() { + return CODEC; + } ++ ++ // DivineMC start - C2ME: opts_native_math ++ @Override ++ public void fillArray(final double[] densities, final ContextProvider applier) { ++ if (!org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ NormalNoise noise = this.offsetNoise.noise(); ++ if (noise == null) { ++ Arrays.fill(densities, 0.0); ++ return; ++ } ++ long ptr = noise.c2me$getPointer(); ++ if (ptr == 0L) { ++ applier.fillAllDirectly(densities, this); ++ return; ++ } ++ double[] x = new double[densities.length]; ++ double[] y = new double[densities.length]; ++ double[] z = new double[densities.length]; ++ for (int i = 0; i < densities.length; i++) { ++ FunctionContext pos = applier.forIndex(i); ++ x[i] = pos.blockZ() * 0.25; ++ y[i] = pos.blockX() * 0.25; ++ z[i] = 0.0; ++ } ++ org.bxteam.divinemc.math.Bindings.c2me_natives_noise_perlin_double_batch( ++ ptr, ++ java.lang.foreign.MemorySegment.ofArray(densities), ++ java.lang.foreign.MemorySegment.ofArray(x), ++ java.lang.foreign.MemorySegment.ofArray(y), ++ java.lang.foreign.MemorySegment.ofArray(z), ++ densities.length ++ ); ++ for (int i = 0; i < densities.length; i++) { ++ densities[i] *= 4.0; ++ } ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + interface ShiftNoise extends DensityFunction { +diff --git a/net/minecraft/world/level/levelgen/synth/BlendedNoise.java b/net/minecraft/world/level/levelgen/synth/BlendedNoise.java +index af5f714c285aad5ef844b17a266e06b5092d33aa..173c9f4024d085e0591f9eb5502a6f15168673e7 100644 +--- a/net/minecraft/world/level/levelgen/synth/BlendedNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/BlendedNoise.java +@@ -36,6 +36,11 @@ public class BlendedNoise implements DensityFunction.SimpleFunction { + private final double maxValue; + public final double xzScale; + public final double yScale; ++ // DivineMC start - C2ME: opts_native_math ++ private final java.lang.foreign.Arena c2me$arena = java.lang.foreign.Arena.ofAuto(); ++ private java.lang.foreign.MemorySegment c2me$samplerData = null; ++ private long c2me$samplerDataPtr; ++ // DivineMC end - C2ME: opts_native_math + + public static BlendedNoise createUnseeded(double xzScale, double yScale, double xzFactor, double yFactor, double smearScaleMultiplier) { + return new BlendedNoise(new XoroshiroRandomSource(0L), xzScale, yScale, xzFactor, yFactor, smearScaleMultiplier); +@@ -62,6 +67,12 @@ public class BlendedNoise implements DensityFunction.SimpleFunction { + this.xzMultiplier = 684.412 * this.xzScale; + this.yMultiplier = 684.412 * this.yScale; + this.maxValue = minLimitNoise.maxBrokenValue(this.yMultiplier); ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ this.c2me$samplerData = org.bxteam.divinemc.math.BindingsTemplate.interpolated_noise_sampler$create(this.c2me$arena, this); ++ this.c2me$samplerDataPtr = this.c2me$samplerData.address(); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + @VisibleForTesting +diff --git a/net/minecraft/world/level/levelgen/synth/NormalNoise.java b/net/minecraft/world/level/levelgen/synth/NormalNoise.java +index 45060882654217eeb9a07357c5149b12fbff02c1..75e3641b40841622a7545bc371197ff1a28968d2 100644 +--- a/net/minecraft/world/level/levelgen/synth/NormalNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/NormalNoise.java +@@ -21,6 +21,15 @@ public class NormalNoise { + private final PerlinNoise second; + private final double maxValue; + private final NormalNoise.NoiseParameters parameters; ++ // DivineMC start - C2ME: opts_native_math ++ private final java.lang.foreign.Arena c2me$arena = java.lang.foreign.Arena.ofAuto(); ++ private java.lang.foreign.MemorySegment c2me$samplerData = null; ++ private long c2me$samplerDataPtr; ++ ++ public long c2me$getPointer() { ++ return this.c2me$samplerDataPtr; ++ } ++ // DivineMC end - C2ME: opts_native_math + + @Deprecated + public static NormalNoise createLegacyNetherBiome(RandomSource random, NormalNoise.NoiseParameters parameters) { +@@ -62,6 +71,12 @@ public class NormalNoise { + + this.valueFactor = 0.16666666666666666 / expectedDeviation(i2 - i1); + this.maxValue = (this.first.maxValue() + this.second.maxValue()) * this.valueFactor; ++ // DivineMC start - C2ME: opts_native_math ++ if (org.bxteam.divinemc.DivineConfig.nativeAccelerationEnabled) { ++ this.c2me$samplerData = org.bxteam.divinemc.math.BindingsTemplate.double_octave_sampler_data$create(this.c2me$arena, this.first, this.second, this.valueFactor); ++ this.c2me$samplerDataPtr = this.c2me$samplerData.address(); ++ } ++ // DivineMC end - C2ME: opts_native_math + } + + public double maxValue() { diff --git a/divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch b/divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch new file mode 100644 index 0000000..9cb6893 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0010-Optimize-Fluids.patch @@ -0,0 +1,145 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Fri, 31 Jan 2025 22:40:54 +0300 +Subject: [PATCH] Optimize Fluids + + +diff --git a/net/minecraft/world/level/block/LiquidBlock.java b/net/minecraft/world/level/block/LiquidBlock.java +index 47a7ce88bf4d26408545dcc061aa763311af0dc9..13877d2bd4289652a9627780839b8d879a66d753 100644 +--- a/net/minecraft/world/level/block/LiquidBlock.java ++++ b/net/minecraft/world/level/block/LiquidBlock.java +@@ -193,6 +193,7 @@ public class LiquidBlock extends Block implements BucketPickup { + Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) { ++ level.setBlock(pos, block.defaultBlockState(), 3); // DivineMC - Optimize Fluids + this.fizz(level, pos); + } + // CraftBukkit end +diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java +index 44bc0823e163bb7edee27889201ec76e93e095cf..974e1e5dcb2613c5aaedd3f2f66483c9dcd6cd23 100644 +--- a/net/minecraft/world/level/material/FlowingFluid.java ++++ b/net/minecraft/world/level/material/FlowingFluid.java +@@ -199,6 +199,7 @@ public abstract class FlowingFluid extends Fluid { + BlockPos blockPos = pos.relative(direction); + final BlockState blockStateIfLoaded = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing + if (blockStateIfLoaded == null) continue; // Paper - Prevent chunk loading from fluid flowing ++ if (!shouldSpreadLiquid(level, blockPos, blockStateIfLoaded)) continue; // DivineMC - Optimize Fluids + // CraftBukkit start + org.bukkit.block.Block source = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos); + org.bukkit.event.block.BlockFromToEvent event = new org.bukkit.event.block.BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction)); +@@ -213,6 +214,39 @@ public abstract class FlowingFluid extends Fluid { + } + } + ++ // DivineMC start - Optimize Fluids ++ private boolean shouldSpreadLiquid(Level level, BlockPos pos, BlockState state) { ++ if (state.is(Blocks.LAVA)) { ++ boolean isSoulSoil = level.getBlockState(pos.below()).is(Blocks.SOUL_SOIL); ++ ++ for (Direction direction : net.minecraft.world.level.block.LiquidBlock.POSSIBLE_FLOW_DIRECTIONS) { ++ BlockPos blockPos = pos.relative(direction.getOpposite()); ++ if (level.getFluidState(blockPos).is(net.minecraft.tags.FluidTags.WATER)) { ++ Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) { ++ this.fizz(level, pos); ++ level.setBlock(pos, block.defaultBlockState(), 3); ++ } ++ return false; ++ } ++ ++ if (isSoulSoil && level.getBlockState(blockPos).is(Blocks.BLUE_ICE)) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, Blocks.BASALT.defaultBlockState())) { ++ this.fizz(level, pos); ++ } ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ private void fizz(LevelAccessor level, BlockPos pos) { ++ level.levelEvent(1501, pos, 0); ++ } ++ // DivineMC end - Optimize Fluids ++ + protected FluidState getNewLiquid(ServerLevel level, BlockPos pos, BlockState state) { + int i = 0; + int i1 = 0; +@@ -341,33 +375,46 @@ public abstract class FlowingFluid extends Fluid { + protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(level, pos, state); } // Paper - Add BlockBreakBlockEvent + protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state); + ++ // DivineMC start - Optimize Fluids + protected int getSlopeDistance(LevelReader level, BlockPos pos, int depth, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadContext) { +- int i = 1000; ++ int slopeFindDistance = this.getSlopeFindDistance(level); ++ int minDistance = slopeFindDistance; + +- for (Direction direction1 : Direction.Plane.HORIZONTAL) { +- if (direction1 != direction) { +- BlockPos blockPos = pos.relative(direction1); +- BlockState blockState = spreadContext.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing +- if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing +- FluidState fluidState = blockState.getFluidState(); +- if (this.canPassThrough(level, this.getFlowing(), pos, state, direction1, blockPos, blockState, fluidState)) { +- if (spreadContext.isHole(blockPos)) { +- return depth; ++ java.util.Deque stack = new java.util.ArrayDeque<>(); ++ stack.push(new Node(pos, depth, direction)); ++ ++ while (!stack.isEmpty()) { ++ Node current = stack.pop(); ++ BlockPos currentPos = current.pos; ++ int currentDepth = current.depth; ++ Direction fromDirection = current.direction; ++ ++ for (Direction dir : Direction.Plane.HORIZONTAL) { ++ if (dir == fromDirection) continue; ++ ++ BlockPos neighborPos = currentPos.relative(dir); ++ BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos); ++ if (neighborState == null) continue; // Prevent chunk loading ++ ++ FluidState fluidState = neighborState.getFluidState(); ++ if (this.canPassThrough(level, this.getFlowing(), currentPos, state, dir, neighborPos, neighborState, fluidState)) { ++ if (spreadContext.isHole(neighborPos)) { ++ return currentDepth; + } + +- if (depth < this.getSlopeFindDistance(level)) { +- int slopeDistance = this.getSlopeDistance(level, blockPos, depth + 1, direction1.getOpposite(), blockState, spreadContext); +- if (slopeDistance < i) { +- i = slopeDistance; +- } ++ if (currentDepth + 1 < slopeFindDistance && currentDepth + 1 < minDistance) { ++ stack.push(new Node(neighborPos, currentDepth + 1, dir.getOpposite())); + } + } + } + } + +- return i; ++ return minDistance; + } + ++ private record Node(BlockPos pos, int depth, Direction direction) { } ++ // DivineMC end - Optimize Fluids ++ + boolean isWaterHole(BlockGetter level, BlockPos pos, BlockState state, BlockPos belowPos, BlockState belowState) { + return canPassThroughWall(Direction.DOWN, level, pos, state, belowPos, belowState) + && (belowState.getFluidState().getType().isSame(this) || canHoldFluid(level, belowPos, belowState, this.getFlowing())); +diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java +index 85629a43f5469a89dd6078d879f475e8212438ec..35b5a33c79c883f28c99c992695b188524593b55 100644 +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -224,6 +224,7 @@ public abstract class LavaFluid extends FlowingFluid { + // CraftBukkit end + } + ++ level.setBlock(pos, Blocks.STONE.defaultBlockState(), 3); // DivineMC - Optimize Fluids + this.fizz(level, pos); + return; + } diff --git a/divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch b/divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch new file mode 100644 index 0000000..3bc3c6a --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0011-World-and-Noise-gen-optimizations.patch @@ -0,0 +1,918 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:09:39 +0300 +Subject: [PATCH] World and Noise gen optimizations + + +diff --git a/net/minecraft/world/level/ChunkPos.java b/net/minecraft/world/level/ChunkPos.java +index 55ce935a2fab7e32904d9ff599867269035d703f..4fa84743ba7f570f11a4979b7e5381478c844aef 100644 +--- a/net/minecraft/world/level/ChunkPos.java ++++ b/net/minecraft/world/level/ChunkPos.java +@@ -110,7 +110,12 @@ public class ChunkPos { + + @Override + public boolean equals(Object other) { +- return this == other || other instanceof ChunkPos chunkPos && this.x == chunkPos.x && this.z == chunkPos.z; ++ // DivineMC start - Use standard equals ++ if (other == this) return true; ++ if (other == null || other.getClass() != this.getClass()) return false; ++ ChunkPos thatPos = (ChunkPos) other; ++ return this.x == thatPos.x && this.z == thatPos.z; ++ // DivineMC end - Use standard equals + } + + public int getMiddleBlockX() { +diff --git a/net/minecraft/world/level/biome/TheEndBiomeSource.java b/net/minecraft/world/level/biome/TheEndBiomeSource.java +index cf3172be76fa4c7987ed569138439ff42f92fa7f..bfc65a4d8d1e64f42ff13508020e5e0260e83b98 100644 +--- a/net/minecraft/world/level/biome/TheEndBiomeSource.java ++++ b/net/minecraft/world/level/biome/TheEndBiomeSource.java +@@ -27,6 +27,33 @@ public class TheEndBiomeSource extends BiomeSource { + private final Holder islands; + private final Holder barrens; + ++ // DivineMC start - World gen optimizations ++ private Holder getBiomeForNoiseGenVanilla(int x, int y, int z, Climate.Sampler noise) { ++ int i = QuartPos.toBlock(x); ++ int j = QuartPos.toBlock(y); ++ int k = QuartPos.toBlock(z); ++ int l = SectionPos.blockToSectionCoord(i); ++ int m = SectionPos.blockToSectionCoord(k); ++ if ((long)l * (long)l + (long)m * (long)m <= 4096L) { ++ return this.end; ++ } else { ++ int n = (SectionPos.blockToSectionCoord(i) * 2 + 1) * 8; ++ int o = (SectionPos.blockToSectionCoord(k) * 2 + 1) * 8; ++ double d = noise.erosion().compute(new DensityFunction.SinglePointContext(n, j, o)); ++ if (d > 0.25D) { ++ return this.highlands; ++ } else if (d >= -0.0625D) { ++ return this.midlands; ++ } else { ++ return d < -0.21875D ? this.islands : this.barrens; ++ } ++ } ++ } ++ ++ private final ThreadLocal>> cache = ThreadLocal.withInitial(it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap::new); ++ private final int cacheCapacity = 1024; ++ // DivineMC end - World gen optimizations ++ + public static TheEndBiomeSource create(HolderGetter biomeGetter) { + return new TheEndBiomeSource( + biomeGetter.getOrThrow(Biomes.THE_END), +@@ -55,26 +82,24 @@ public class TheEndBiomeSource extends BiomeSource { + return CODEC; + } + ++ // DivineMC start - World gen optimizations + @Override +- public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { +- int blockPosX = QuartPos.toBlock(x); +- int blockPosY = QuartPos.toBlock(y); +- int blockPosZ = QuartPos.toBlock(z); +- int sectionPosX = SectionPos.blockToSectionCoord(blockPosX); +- int sectionPosZ = SectionPos.blockToSectionCoord(blockPosZ); +- if ((long)sectionPosX * sectionPosX + (long)sectionPosZ * sectionPosZ <= 4096L) { +- return this.end; ++ public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler multiNoiseSampler) { ++ final long key = net.minecraft.world.level.ChunkPos.asLong(biomeX, biomeZ); ++ final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap> cacheThreadLocal = cache.get(); ++ final Holder biome = cacheThreadLocal.get(key); ++ if (biome != null) { ++ return biome; + } else { +- int i = (SectionPos.blockToSectionCoord(blockPosX) * 2 + 1) * 8; +- int i1 = (SectionPos.blockToSectionCoord(blockPosZ) * 2 + 1) * 8; +- double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(i, blockPosY, i1)); +- if (d > 0.25) { +- return this.highlands; +- } else if (d >= -0.0625) { +- return this.midlands; +- } else { +- return d < -0.21875 ? this.islands : this.barrens; ++ final Holder gennedBiome = getBiomeForNoiseGenVanilla(biomeX, biomeY, biomeZ, multiNoiseSampler); ++ cacheThreadLocal.put(key, gennedBiome); ++ if (cacheThreadLocal.size() > cacheCapacity) { ++ for (int i = 0; i < cacheCapacity / 16; i ++) { ++ cacheThreadLocal.removeFirst(); ++ } + } ++ return gennedBiome; + } + } ++ // DivineMC end - World gen optimizations + } +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java +index c83d0667b19830304f22319a46a23422a8766790..5bc74d860923d6485593cacb67d4c18e20db2634 100644 +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -23,6 +23,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + public short tickingFluidCount; + public final PalettedContainer states; + private PalettedContainer> biomes; // CraftBukkit - read/write ++ private static final int sliceSize = 4; // DivineMC - World and Noise gen optimizations + + // Paper start - block counting + private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); +@@ -312,13 +313,15 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + PalettedContainer> palettedContainer = this.biomes.recreate(); + int i = 4; + +- for (int i1 = 0; i1 < 4; i1++) { +- for (int i2 = 0; i2 < 4; i2++) { +- for (int i3 = 0; i3 < 4; i3++) { +- palettedContainer.getAndSetUnchecked(i1, i2, i3, biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler)); ++ // DivineMC start - World and Noise gen optimizations ++ for (int posY = 0; posY < sliceSize; ++posY) { ++ for (int posZ = 0; posZ < sliceSize; ++posZ) { ++ for (int posX = 0; posX < sliceSize; ++posX) { ++ palettedContainer.getAndSetUnchecked(posX, posY, posZ, biomeResolver.getNoiseBiome(x + posX, y + posY, z + posZ, climateSampler)); + } + } + } ++ // DivineMC end - World and Noise gen optimizations + + this.biomes = palettedContainer; + } +diff --git a/net/minecraft/world/level/levelgen/Aquifer.java b/net/minecraft/world/level/levelgen/Aquifer.java +index c62a15ea4a1bb22e7bcc2fc544acf8a601892029..43dd5f63fe7834d41874ea30651f3fb738d88ba6 100644 +--- a/net/minecraft/world/level/levelgen/Aquifer.java ++++ b/net/minecraft/world/level/levelgen/Aquifer.java +@@ -85,6 +85,15 @@ public interface Aquifer { + private final int minGridZ; + private final int gridSizeX; + private final int gridSizeZ; ++ // DivineMC start - World gen optimizations ++ private int c2me$dist1; ++ private int c2me$dist2; ++ private int c2me$dist3; ++ private long c2me$pos1; ++ private long c2me$pos2; ++ private long c2me$pos3; ++ private double c2me$mutableDoubleThingy; ++ // DivineMC end - World gen optimizations + private static final int[][] SURFACE_SAMPLING_OFFSETS_IN_CHUNKS = new int[][]{ + {0, 0}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1} + }; +@@ -120,6 +129,36 @@ public interface Aquifer { + this.aquiferCache = new Aquifer.FluidStatus[i4]; + this.aquiferLocationCache = new long[i4]; + Arrays.fill(this.aquiferLocationCache, Long.MAX_VALUE); ++ // DivineMC start - World gen optimizations ++ if (this.aquiferLocationCache.length % (this.gridSizeX * this.gridSizeZ) != 0) { ++ throw new AssertionError("Array length"); ++ } ++ ++ int sizeY = this.aquiferLocationCache.length / (this.gridSizeX * this.gridSizeZ); ++ ++ final RandomSource random = org.bxteam.divinemc.util.RandomUtil.getRandom(this.positionalRandomFactory); ++ // index: y, z, x ++ for (int y = 0; y < sizeY; y++) { ++ for (int z = 0; z < this.gridSizeZ; z++) { ++ for (int x = 0; x < this.gridSizeX; x++) { ++ final int x1 = x + this.minGridX; ++ final int y1 = y + this.minGridY; ++ final int z1 = z + this.minGridZ; ++ org.bxteam.divinemc.util.RandomUtil.derive(this.positionalRandomFactory, random, x1, y1, z1); ++ int x2 = x1 * 16 + random.nextInt(10); ++ int y2 = y1 * 12 + random.nextInt(9); ++ int z2 = z1 * 16 + random.nextInt(10); ++ int index = this.getIndex(x1, y1, z1); ++ this.aquiferLocationCache[index] = BlockPos.asLong(x2, y2, z2); ++ } ++ } ++ } ++ for (long blockPosition : this.aquiferLocationCache) { ++ if (blockPosition == Long.MAX_VALUE) { ++ throw new AssertionError("Array initialization"); ++ } ++ } ++ // DivineMC end - World gen optimizations + } + + private int getIndex(int gridX, int gridY, int gridZ) { +@@ -132,140 +171,24 @@ public interface Aquifer { + @Nullable + @Override + public BlockState computeSubstance(DensityFunction.FunctionContext context, double substance) { ++ // DivineMC start - World gen optimizations + int i = context.blockX(); +- int i1 = context.blockY(); +- int i2 = context.blockZ(); ++ int j = context.blockY(); ++ int k = context.blockZ(); + if (substance > 0.0) { + this.shouldScheduleFluidUpdate = false; + return null; + } else { +- Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, i1, i2); +- if (fluidStatus.at(i1).is(Blocks.LAVA)) { ++ Aquifer.FluidStatus fluidLevel = this.globalFluidPicker.computeFluid(i, j, k); ++ if (fluidLevel.at(j).is(Blocks.LAVA)) { + this.shouldScheduleFluidUpdate = false; + return Blocks.LAVA.defaultBlockState(); + } else { +- int i3 = Math.floorDiv(i - 5, 16); +- int i4 = Math.floorDiv(i1 + 1, 12); +- int i5 = Math.floorDiv(i2 - 5, 16); +- int i6 = Integer.MAX_VALUE; +- int i7 = Integer.MAX_VALUE; +- int i8 = Integer.MAX_VALUE; +- int i9 = Integer.MAX_VALUE; +- long l = 0L; +- long l1 = 0L; +- long l2 = 0L; +- long l3 = 0L; +- +- for (int i10 = 0; i10 <= 1; i10++) { +- for (int i11 = -1; i11 <= 1; i11++) { +- for (int i12 = 0; i12 <= 1; i12++) { +- int i13 = i3 + i10; +- int i14 = i4 + i11; +- int i15 = i5 + i12; +- int index = this.getIndex(i13, i14, i15); +- long l4 = this.aquiferLocationCache[index]; +- long l5; +- if (l4 != Long.MAX_VALUE) { +- l5 = l4; +- } else { +- RandomSource randomSource = this.positionalRandomFactory.at(i13, i14, i15); +- l5 = BlockPos.asLong( +- i13 * 16 + randomSource.nextInt(10), i14 * 12 + randomSource.nextInt(9), i15 * 16 + randomSource.nextInt(10) +- ); +- this.aquiferLocationCache[index] = l5; +- } +- +- int i16 = BlockPos.getX(l5) - i; +- int i17 = BlockPos.getY(l5) - i1; +- int i18 = BlockPos.getZ(l5) - i2; +- int i19 = i16 * i16 + i17 * i17 + i18 * i18; +- if (i6 >= i19) { +- l3 = l2; +- l2 = l1; +- l1 = l; +- l = l5; +- i9 = i8; +- i8 = i7; +- i7 = i6; +- i6 = i19; +- } else if (i7 >= i19) { +- l3 = l2; +- l2 = l1; +- l1 = l5; +- i9 = i8; +- i8 = i7; +- i7 = i19; +- } else if (i8 >= i19) { +- l3 = l2; +- l2 = l5; +- i9 = i8; +- i8 = i19; +- } else if (i9 >= i19) { +- l3 = l5; +- i9 = i19; +- } +- } +- } +- } +- +- Aquifer.FluidStatus aquiferStatus = this.getAquiferStatus(l); +- double d = similarity(i6, i7); +- BlockState blockState = aquiferStatus.at(i1); +- if (d <= 0.0) { +- if (d >= FLOWING_UPDATE_SIMULARITY) { +- Aquifer.FluidStatus aquiferStatus1 = this.getAquiferStatus(l1); +- this.shouldScheduleFluidUpdate = !aquiferStatus.equals(aquiferStatus1); +- } else { +- this.shouldScheduleFluidUpdate = false; +- } +- +- return blockState; +- } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, i1 - 1, i2).at(i1 - 1).is(Blocks.LAVA)) { +- this.shouldScheduleFluidUpdate = true; +- return blockState; +- } else { +- MutableDouble mutableDouble = new MutableDouble(Double.NaN); +- Aquifer.FluidStatus aquiferStatus2 = this.getAquiferStatus(l1); +- double d1 = d * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus2); +- if (substance + d1 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } else { +- Aquifer.FluidStatus aquiferStatus3 = this.getAquiferStatus(l2); +- double d2 = similarity(i6, i8); +- if (d2 > 0.0) { +- double d3 = d * d2 * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus3); +- if (substance + d3 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } +- } +- +- double d3 = similarity(i7, i8); +- if (d3 > 0.0) { +- double d4 = d * d3 * this.calculatePressure(context, mutableDouble, aquiferStatus2, aquiferStatus3); +- if (substance + d4 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } +- } +- +- boolean flag = !aquiferStatus.equals(aquiferStatus2); +- boolean flag1 = d3 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus2.equals(aquiferStatus3); +- boolean flag2 = d2 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus.equals(aquiferStatus3); +- if (!flag && !flag1 && !flag2) { +- this.shouldScheduleFluidUpdate = d2 >= FLOWING_UPDATE_SIMULARITY +- && similarity(i6, i9) >= FLOWING_UPDATE_SIMULARITY +- && !aquiferStatus.equals(this.getAquiferStatus(l3)); +- } else { +- this.shouldScheduleFluidUpdate = true; +- } +- +- return blockState; +- } +- } ++ aquiferExtracted$refreshDistPosIdx(i, j, k); ++ return aquiferExtracted$applyPost(context, substance, j, i, k); + } + } ++ // DivineMC end - World gen optimizations + } + + @Override +@@ -278,65 +201,28 @@ public interface Aquifer { + return 1.0 - Math.abs(secondDistance - firstDistance) / 25.0; + } + ++ // DivineMC start - World gen optimizations + private double calculatePressure( +- DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus firstFluid, Aquifer.FluidStatus secondFluid ++ DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 // DivineMC - rename args + ) { + int i = context.blockY(); +- BlockState blockState = firstFluid.at(i); +- BlockState blockState1 = secondFluid.at(i); +- if ((!blockState.is(Blocks.LAVA) || !blockState1.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState1.is(Blocks.LAVA))) { +- int abs = Math.abs(firstFluid.fluidLevel - secondFluid.fluidLevel); ++ BlockState blockState = fluidLevel.at(i); ++ BlockState blockState2 = fluidLevel2.at(i); ++ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { ++ int abs = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); + if (abs == 0) { + return 0.0; + } else { +- double d = 0.5 * (firstFluid.fluidLevel + secondFluid.fluidLevel); +- double d1 = i + 0.5 - d; +- double d2 = abs / 2.0; +- double d3 = 0.0; +- double d4 = 2.5; +- double d5 = 1.5; +- double d6 = 3.0; +- double d7 = 10.0; +- double d8 = 3.0; +- double d9 = d2 - Math.abs(d1); +- double d11; +- if (d1 > 0.0) { +- double d10 = 0.0 + d9; +- if (d10 > 0.0) { +- d11 = d10 / 1.5; +- } else { +- d11 = d10 / 2.5; +- } +- } else { +- double d10 = 3.0 + d9; +- if (d10 > 0.0) { +- d11 = d10 / 3.0; +- } else { +- d11 = d10 / 10.0; +- } +- } +- +- double d10x = 2.0; +- double d12; +- if (!(d11 < -2.0) && !(d11 > 2.0)) { +- double value = substance.getValue(); +- if (Double.isNaN(value)) { +- double d13 = this.barrierNoise.compute(context); +- substance.setValue(d13); +- d12 = d13; +- } else { +- d12 = value; +- } +- } else { +- d12 = 0.0; +- } ++ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); ++ final double q = aquiferExtracted$getQ(i, d, abs); + +- return 2.0 * (d12 + d11); ++ return aquiferExtracted$postCalculateDensity(context, substance, q); + } + } else { + return 2.0; + } + } ++ // DivineMC end - World gen optimizations + + private int gridX(int x) { + return Math.floorDiv(x, 16); +@@ -350,23 +236,25 @@ public interface Aquifer { + return Math.floorDiv(z, 16); + } + +- private Aquifer.FluidStatus getAquiferStatus(long packedPos) { +- int x = BlockPos.getX(packedPos); +- int y = BlockPos.getY(packedPos); +- int z = BlockPos.getZ(packedPos); +- int i = this.gridX(x); +- int i1 = this.gridY(y); +- int i2 = this.gridZ(z); +- int index = this.getIndex(i, i1, i2); +- Aquifer.FluidStatus fluidStatus = this.aquiferCache[index]; +- if (fluidStatus != null) { +- return fluidStatus; ++ // DivineMC start - World gen optimizations ++ private Aquifer.FluidStatus getAquiferStatus(long pos) { ++ int i = BlockPos.getX(pos); ++ int j = BlockPos.getY(pos); ++ int k = BlockPos.getZ(pos); ++ int l = i >> 4; // C2ME - inline: floorDiv(i, 16) ++ int m = Math.floorDiv(j, 12); // C2ME - inline ++ int n = k >> 4; // C2ME - inline: floorDiv(k, 16) ++ int o = this.getIndex(l, m, n); ++ Aquifer.FluidStatus fluidLevel = this.aquiferCache[o]; ++ if (fluidLevel != null) { ++ return fluidLevel; + } else { +- Aquifer.FluidStatus fluidStatus1 = this.computeFluid(x, y, z); +- this.aquiferCache[index] = fluidStatus1; +- return fluidStatus1; ++ Aquifer.FluidStatus fluidLevel2 = this.computeFluid(i, j, k); ++ this.aquiferCache[o] = fluidLevel2; ++ return fluidLevel2; + } + } ++ // DivineMC end - World gen optimizations + + private Aquifer.FluidStatus computeFluid(int x, int y, int z) { + Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(x, y, z); +@@ -406,23 +294,22 @@ public interface Aquifer { + return new Aquifer.FluidStatus(i7, this.computeFluidType(x, y, z, fluidStatus, i7)); + } + ++ // DivineMC start - World gen optimizations + private int computeSurfaceLevel(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int maxSurfaceLevel, boolean fluidPresent) { +- DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(x, y, z); ++ DensityFunction.SinglePointContext unblendedNoisePos = new DensityFunction.SinglePointContext(x, y, z); + double d; + double d1; +- if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, singlePointContext)) { ++ if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, unblendedNoisePos)) { + d = -1.0; + d1 = -1.0; + } else { + int i = maxSurfaceLevel + 8 - y; +- int i1 = 64; +- double d2 = fluidPresent ? Mth.clampedMap((double)i, 0.0, 64.0, 1.0, 0.0) : 0.0; +- double d3 = Mth.clamp(this.fluidLevelFloodednessNoise.compute(singlePointContext), -1.0, 1.0); +- double d4 = Mth.map(d2, 1.0, 0.0, -0.3, 0.8); +- double d5 = Mth.map(d2, 1.0, 0.0, -0.8, 0.4); +- d = d3 - d5; +- d1 = d3 - d4; ++ double f = fluidPresent ? Mth.clampedLerp(1.0, 0.0, ((double) i) / 64.0) : 0.0; // inline ++ double g = Mth.clamp(this.fluidLevelFloodednessNoise.compute(unblendedNoisePos), -1.0, 1.0); ++ d = g + 0.8 + (f - 1.0) * 1.2; // inline ++ d1 = g + 0.3 + (f - 1.0) * 1.1; // inline + } ++ // DivineMC end - World gen optimizations + + int i; + if (d1 > 0.0) { +@@ -466,5 +353,183 @@ public interface Aquifer { + + return blockState; + } ++ ++ // DivineMC start - World gen optimizations ++ private @org.jetbrains.annotations.Nullable BlockState aquiferExtracted$applyPost(DensityFunction.FunctionContext pos, double density, int j, int i, int k) { ++ Aquifer.FluidStatus fluidLevel2 = this.getAquiferStatus(this.c2me$pos1); ++ double d = similarity(this.c2me$dist1, this.c2me$dist2); ++ BlockState blockState = fluidLevel2.at(j); ++ if (d <= 0.0) { ++ this.shouldScheduleFluidUpdate = d >= FLOWING_UPDATE_SIMULARITY; ++ return blockState; ++ } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, j - 1, k).at(j - 1).is(Blocks.LAVA)) { ++ this.shouldScheduleFluidUpdate = true; ++ return blockState; ++ } else { ++ this.c2me$mutableDoubleThingy = Double.NaN; ++ Aquifer.FluidStatus fluidLevel3 = this.getAquiferStatus(this.c2me$pos2); ++ double e = d * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel3); ++ if (density + e > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return null; ++ } else { ++ return aquiferExtracted$getFinalBlockState(pos, density, d, fluidLevel2, fluidLevel3, blockState); ++ } ++ } ++ } ++ ++ private BlockState aquiferExtracted$getFinalBlockState(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, Aquifer.FluidStatus fluidLevel3, BlockState blockState) { ++ Aquifer.FluidStatus fluidLevel4 = this.getAquiferStatus(this.c2me$pos3); ++ double f = similarity(this.c2me$dist1, this.c2me$dist3); ++ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel2, f, fluidLevel4)) return null; ++ ++ double g = similarity(this.c2me$dist2, this.c2me$dist3); ++ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel3, g, fluidLevel4)) return null; ++ ++ this.shouldScheduleFluidUpdate = true; ++ return blockState; ++ } ++ ++ private boolean aquiferExtracted$extractedCheckFG(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, double f, Aquifer.FluidStatus fluidLevel4) { ++ if (f > 0.0) { ++ double g = d * f * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel4); ++ if (density + g > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private void aquiferExtracted$refreshDistPosIdx(int x, int y, int z) { ++ int gx = (x - 5) >> 4; ++ int gy = Math.floorDiv(y + 1, 12); ++ int gz = (z - 5) >> 4; ++ int dist1 = Integer.MAX_VALUE; ++ int dist2 = Integer.MAX_VALUE; ++ int dist3 = Integer.MAX_VALUE; ++ long pos1 = 0; ++ long pos2 = 0; ++ long pos3 = 0; ++ ++ for (int offY = -1; offY <= 1; ++offY) { ++ for (int offZ = 0; offZ <= 1; ++offZ) { ++ for (int offX = 0; offX <= 1; ++offX) { ++ int posIdx = this.getIndex(gx + offX, gy + offY, gz + offZ); ++ ++ long position = this.aquiferLocationCache[posIdx]; ++ ++ int dx = BlockPos.getX(position) - x; ++ int dy = BlockPos.getY(position) - y; ++ int dz = BlockPos.getZ(position) - z; ++ int dist = dx * dx + dy * dy + dz * dz; ++ ++ if (dist3 >= dist) { ++ pos3 = position; ++ dist3 = dist; ++ } ++ if (dist2 >= dist) { ++ pos3 = pos2; ++ dist3 = dist2; ++ pos2 = position; ++ dist2 = dist; ++ } ++ if (dist1 >= dist) { ++ pos2 = pos1; ++ dist2 = dist1; ++ pos1 = position; ++ dist1 = dist; ++ } ++ } ++ } ++ } ++ ++ this.c2me$dist1 = dist1; ++ this.c2me$dist2 = dist2; ++ this.c2me$dist3 = dist3; ++ this.c2me$pos1 = pos1; ++ this.c2me$pos2 = pos2; ++ this.c2me$pos3 = pos3; ++ } ++ ++ private double c2me$calculateDensityModified( ++ DensityFunction.FunctionContext pos, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 ++ ) { ++ int i = pos.blockY(); ++ BlockState blockState = fluidLevel.at(i); ++ BlockState blockState2 = fluidLevel2.at(i); ++ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { ++ int j = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); ++ if (j == 0) { ++ return 0.0; ++ } else { ++ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); ++ final double q = aquiferExtracted$getQ(i, d, j); ++ ++ return aquiferExtracted$postCalculateDensityModified(pos, q); ++ } ++ } else { ++ return 2.0; ++ } ++ } ++ ++ private double aquiferExtracted$postCalculateDensity(DensityFunction.FunctionContext pos, MutableDouble mutableDouble, double q) { ++ double r; ++ if (!(q < -2.0) && !(q > 2.0)) { ++ double s = mutableDouble.getValue(); ++ if (Double.isNaN(s)) { ++ double t = this.barrierNoise.compute(pos); ++ mutableDouble.setValue(t); ++ r = t; ++ } else { ++ r = s; ++ } ++ } else { ++ r = 0.0; ++ } ++ ++ return 2.0 * (r + q); ++ } ++ ++ private double aquiferExtracted$postCalculateDensityModified(DensityFunction.FunctionContext pos, double q) { ++ double r; ++ if (!(q < -2.0) && !(q > 2.0)) { ++ double s = this.c2me$mutableDoubleThingy; ++ if (Double.isNaN(s)) { ++ double t = this.barrierNoise.compute(pos); ++ this.c2me$mutableDoubleThingy = t; ++ r = t; ++ } else { ++ r = s; ++ } ++ } else { ++ r = 0.0; ++ } ++ ++ return 2.0 * (r + q); ++ } ++ ++ private static double aquiferExtracted$getQ(double i, double d, double j) { ++ double e = i + 0.5 - d; ++ double f = j / 2.0; ++ double o = f - Math.abs(e); ++ double q; ++ if (e > 0.0) { ++ if (o > 0.0) { ++ q = o / 1.5; ++ } else { ++ q = o / 2.5; ++ } ++ } else { ++ double p = 3.0 + o; ++ if (p > 0.0) { ++ q = p / 3.0; ++ } else { ++ q = p / 10.0; ++ } ++ } ++ return q; ++ } ++ // DivineMC end - World gen optimizations + } + } +diff --git a/net/minecraft/world/level/levelgen/Beardifier.java b/net/minecraft/world/level/levelgen/Beardifier.java +index 131923282c9ecbcb1d7f45a826da907c02bd2716..36dd3eb0cb29d546531aec91a9c486be09975797 100644 +--- a/net/minecraft/world/level/levelgen/Beardifier.java ++++ b/net/minecraft/world/level/levelgen/Beardifier.java +@@ -29,6 +29,17 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + }); + private final ObjectListIterator pieceIterator; + private final ObjectListIterator junctionIterator; ++ // DivineMC start - World gen optimizations ++ private Beardifier.Rigid[] c2me$pieceArray; ++ private JigsawJunction[] c2me$junctionArray; ++ ++ private void c2me$initArrays() { ++ this.c2me$pieceArray = com.google.common.collect.Iterators.toArray(this.pieceIterator, Beardifier.Rigid.class); ++ this.pieceIterator.back(Integer.MAX_VALUE); ++ this.c2me$junctionArray = com.google.common.collect.Iterators.toArray(this.junctionIterator, JigsawJunction.class); ++ this.junctionIterator.back(Integer.MAX_VALUE); ++ } ++ // DivineMC end - World gen optimizations + + public static Beardifier forStructuresInChunk(StructureManager structureManager, ChunkPos chunkPos) { + int minBlockX = chunkPos.getMinBlockX(); +@@ -76,50 +87,44 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + this.junctionIterator = junctionIterator; + } + ++ // DivineMC start - World gen optimizations + @Override + public double compute(DensityFunction.FunctionContext context) { ++ if (this.c2me$pieceArray == null || this.c2me$junctionArray == null) { ++ this.c2me$initArrays(); ++ } + int i = context.blockX(); +- int i1 = context.blockY(); +- int i2 = context.blockZ(); ++ int j = context.blockY(); ++ int k = context.blockZ(); + double d = 0.0; + +- while (this.pieceIterator.hasNext()) { +- Beardifier.Rigid rigid = this.pieceIterator.next(); +- BoundingBox boundingBox = rigid.box(); +- int groundLevelDelta = rigid.groundLevelDelta(); +- int max = Math.max(0, Math.max(boundingBox.minX() - i, i - boundingBox.maxX())); +- int max1 = Math.max(0, Math.max(boundingBox.minZ() - i2, i2 - boundingBox.maxZ())); +- int i3 = boundingBox.minY() + groundLevelDelta; +- int i4 = i1 - i3; +- +- int i5 = switch (rigid.terrainAdjustment()) { +- case NONE -> 0; +- case BURY, BEARD_THIN -> i4; +- case BEARD_BOX -> Math.max(0, Math.max(i3 - i1, i1 - boundingBox.maxY())); +- case ENCAPSULATE -> Math.max(0, Math.max(boundingBox.minY() - i1, i1 - boundingBox.maxY())); +- }; ++ for (Beardifier.Rigid piece : this.c2me$pieceArray) { ++ BoundingBox blockBox = piece.box(); ++ int l = piece.groundLevelDelta(); ++ int m = Math.max(0, Math.max(blockBox.minX() - i, i - blockBox.maxX())); ++ int n = Math.max(0, Math.max(blockBox.minZ() - k, k - blockBox.maxZ())); ++ int o = blockBox.minY() + l; ++ int p = j - o; + +- d += switch (rigid.terrainAdjustment()) { ++ d += switch (piece.terrainAdjustment()) { // 2 switch statement merged + case NONE -> 0.0; +- case BURY -> getBuryContribution(max, i5 / 2.0, max1); +- case BEARD_THIN, BEARD_BOX -> getBeardContribution(max, i5, max1, i4) * 0.8; +- case ENCAPSULATE -> getBuryContribution(max / 2.0, i5 / 2.0, max1 / 2.0) * 0.8; ++ case BURY -> getBuryContribution(m, (double)p / 2.0, n); ++ case BEARD_THIN -> getBeardContribution(m, p, n, p) * 0.8; ++ case BEARD_BOX -> getBeardContribution(m, Math.max(0, Math.max(o - j, j - blockBox.maxY())), n, p) * 0.8; ++ case ENCAPSULATE -> getBuryContribution((double)m / 2.0, (double)Math.max(0, Math.max(blockBox.minY() - j, j - blockBox.maxY())) / 2.0, (double)n / 2.0) * 0.8; + }; + } + +- this.pieceIterator.back(Integer.MAX_VALUE); +- +- while (this.junctionIterator.hasNext()) { +- JigsawJunction jigsawJunction = this.junctionIterator.next(); +- int i6 = i - jigsawJunction.getSourceX(); +- int groundLevelDelta = i1 - jigsawJunction.getSourceGroundY(); +- int max = i2 - jigsawJunction.getSourceZ(); +- d += getBeardContribution(i6, groundLevelDelta, max, groundLevelDelta) * 0.4; ++ for (JigsawJunction jigsawJunction : this.c2me$junctionArray) { ++ int r = i - jigsawJunction.getSourceX(); ++ int l = j - jigsawJunction.getSourceGroundY(); ++ int m = k - jigsawJunction.getSourceZ(); ++ d += getBeardContribution(r, l, m, l) * 0.4; + } + +- this.junctionIterator.back(Integer.MAX_VALUE); + return d; + } ++ // DivineMC end - World gen optimizations + + @Override + public double minValue() { +@@ -132,8 +137,14 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + } + + private static double getBuryContribution(double x, double y, double z) { +- double len = Mth.length(x, y, z); +- return Mth.clampedMap(len, 0.0, 6.0, 1.0, 0.0); ++ // DivineMC start - World gen optimizations ++ double d = Math.sqrt(x * x + y * y + z * z); ++ if (d > 6.0) { ++ return 0.0; ++ } else { ++ return 1.0 - d / 6.0; ++ } ++ // DivineMC end - World gen optimizations + } + + private static double getBeardContribution(int x, int y, int z, int height) { +diff --git a/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/net/minecraft/world/level/levelgen/LegacyRandomSource.java +index c67168517774a0ad9ca43422a79ef14a8ea0c2e8..026dfbbb6c3fd5cd274dcbf721e5cf3af889e3d9 100644 +--- a/net/minecraft/world/level/levelgen/LegacyRandomSource.java ++++ b/net/minecraft/world/level/levelgen/LegacyRandomSource.java +@@ -53,13 +53,7 @@ public class LegacyRandomSource implements BitRandomSource { + return this.gaussianSource.nextGaussian(); + } + +- public static class LegacyPositionalRandomFactory implements PositionalRandomFactory { +- private final long seed; +- +- public LegacyPositionalRandomFactory(long seed) { +- this.seed = seed; +- } +- ++ public record LegacyPositionalRandomFactory(long seed) implements PositionalRandomFactory { // DivineMC - make record + @Override + public RandomSource at(int x, int y, int z) { + long seed = Mth.getSeed(x, y, z); +diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index 65728ef17e63d71833677fdcbd5bb90794b4822b..6ef91bd952d4a0c1ffa6f534e4fcdd5c0a9db40b 100644 +--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -65,11 +65,13 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + } + + private static Aquifer.FluidPicker createFluidPicker(NoiseGeneratorSettings settings) { +- Aquifer.FluidStatus fluidStatus = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); +- int seaLevel = settings.seaLevel(); +- Aquifer.FluidStatus fluidStatus1 = new Aquifer.FluidStatus(seaLevel, settings.defaultFluid()); +- Aquifer.FluidStatus fluidStatus2 = new Aquifer.FluidStatus(DimensionType.MIN_Y * 2, Blocks.AIR.defaultBlockState()); +- return (x, y, z) -> y < Math.min(-54, seaLevel) ? fluidStatus : fluidStatus1; ++ // DivineMC start - World gen optimizations ++ Aquifer.FluidStatus fluidLevel = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); ++ int i = settings.seaLevel(); ++ Aquifer.FluidStatus fluidLevel2 = new Aquifer.FluidStatus(i, settings.defaultFluid()); ++ final int min = Math.min(-54, i); ++ return (j, k, lx) -> k < min ? fluidLevel : fluidLevel2; ++ // DivineMC end - World gen optimizations + } + + @Override +diff --git a/net/minecraft/world/level/levelgen/NoiseSettings.java b/net/minecraft/world/level/levelgen/NoiseSettings.java +index 4cf3a364595ba5f81f741295695cb9a449bdf672..44df2ac0bd972c4d97fc89cd0c2d2d83480ca3e1 100644 +--- a/net/minecraft/world/level/levelgen/NoiseSettings.java ++++ b/net/minecraft/world/level/levelgen/NoiseSettings.java +@@ -8,7 +8,7 @@ import net.minecraft.core.QuartPos; + import net.minecraft.world.level.LevelHeightAccessor; + import net.minecraft.world.level.dimension.DimensionType; + +-public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { ++public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical, int horizontalCellBlockCount, int verticalCellBlockCount) { // DivineMC - NoiseSettings optimizations + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance.group( + Codec.intRange(DimensionType.MIN_Y, DimensionType.MAX_Y).fieldOf("min_y").forGetter(NoiseSettings::minY), +@@ -16,7 +16,10 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + Codec.intRange(1, 4).fieldOf("size_horizontal").forGetter(NoiseSettings::noiseSizeHorizontal), + Codec.intRange(1, 4).fieldOf("size_vertical").forGetter(NoiseSettings::noiseSizeVertical) + ) +- .apply(instance, NoiseSettings::new) ++ // DivineMC start - NoiseSettings optimizations ++ .apply(instance, (Integer minY1, Integer height1, Integer noiseSizeHorizontal1, Integer noiseSizeVertical1) -> new NoiseSettings(minY1, height1, noiseSizeHorizontal1, noiseSizeVertical1, ++ QuartPos.toBlock(noiseSizeHorizontal1), QuartPos.toBlock(noiseSizeVertical1))) ++ // DivineMC end - NoiseSettings optimizations + ) + .comapFlatMap(NoiseSettings::guardY, Function.identity()); + protected static final NoiseSettings OVERWORLD_NOISE_SETTINGS = create(-64, 384, 1, 2); +@@ -36,7 +39,7 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + } + + public static NoiseSettings create(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { +- NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical); ++ NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical, QuartPos.toBlock(noiseSizeHorizontal), QuartPos.toBlock(noiseSizeVertical)); // DivineMC - NoiseSettings optimizations + guardY(noiseSettings).error().ifPresent(error -> { + throw new IllegalStateException(error.message()); + }); +@@ -44,16 +47,16 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + } + + public int getCellHeight() { +- return QuartPos.toBlock(this.noiseSizeVertical()); ++ return verticalCellBlockCount(); // DivineMC - NoiseSettings optimizations + } + + public int getCellWidth() { +- return QuartPos.toBlock(this.noiseSizeHorizontal()); ++ return horizontalCellBlockCount(); // DivineMC - NoiseSettings optimizations + } + + public NoiseSettings clampToHeightAccessor(LevelHeightAccessor heightAccessor) { + int max = Math.max(this.minY, heightAccessor.getMinY()); + int i = Math.min(this.minY + this.height, heightAccessor.getMaxY() + 1) - max; +- return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical); ++ return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical, QuartPos.toBlock(this.noiseSizeHorizontal), QuartPos.toBlock(this.noiseSizeVertical)); // DivineMC - NoiseSettings optimizations + } + } +diff --git a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +index 9d3a9ca1e13cd80f468f1352bbb74345f03903dd..d97b9b43686bda0a95fc02f6ca31b2d07d603a32 100644 +--- a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java ++++ b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +@@ -106,15 +106,7 @@ public class XoroshiroRandomSource implements RandomSource { + return this.randomNumberGenerator.nextLong() >>> 64 - bits; + } + +- public static class XoroshiroPositionalRandomFactory implements PositionalRandomFactory { +- private final long seedLo; +- private final long seedHi; +- +- public XoroshiroPositionalRandomFactory(long seedLo, long seedHi) { +- this.seedLo = seedLo; +- this.seedHi = seedHi; +- } +- ++ public record XoroshiroPositionalRandomFactory(long seedLo, long seedHi) implements PositionalRandomFactory { // DivineMC - make record + @Override + public RandomSource at(int x, int y, int z) { + long seed = Mth.getSeed(x, y, z); +diff --git a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +index ffac5b7b1eb1364ab8442d7145a7b4ebde68ee10..ef28df96ed569113a9d61f6ac4b4d84d578c02da 100644 +--- a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java ++++ b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +@@ -187,7 +187,7 @@ public class PerlinNoise { + } + + public static double wrap(double value) { +- return value - Mth.lfloor(value / 3.3554432E7 + 0.5) * 3.3554432E7; ++ return value - Math.floor(value / 3.3554432E7 + 0.5) * 3.3554432E7; // DivineMC - avoid casting + } + + protected int firstOctave() { diff --git a/divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch b/divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch new file mode 100644 index 0000000..83eb750 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0012-Chunk-System-optimization.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:33:03 +0300 +Subject: [PATCH] Chunk System optimization + + +diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java +index 26c8c1e5598daf3550aef05b12218c47bda6618b..94c824ab1457939c425e1f99929d3222ee2c18a0 100644 +--- a/net/minecraft/world/level/LevelReader.java ++++ b/net/minecraft/world/level/LevelReader.java +@@ -70,10 +70,27 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste + + @Override + default Holder getNoiseBiome(int x, int y, int z) { +- ChunkAccess chunk = this.getChunk(QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); ++ ChunkAccess chunk = this.fasterChunkAccess(this, QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); // DivineMC - Chunk System optimization + return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); + } + ++ // DivineMC start - Chunk System optimization ++ private @Nullable ChunkAccess fasterChunkAccess(LevelReader instance, int x, int z, ChunkStatus chunkStatus, boolean create) { ++ if (!create && instance instanceof net.minecraft.server.level.ServerLevel world) { ++ final net.minecraft.server.level.ChunkHolder holder = (world.getChunkSource().chunkMap).getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); ++ if (holder != null) { ++ final java.util.concurrent.CompletableFuture> future = holder.getFullChunkFuture(); ++ final net.minecraft.server.level.ChunkResult either = future.getNow(null); ++ if (either != null) { ++ final net.minecraft.world.level.chunk.LevelChunk chunk = either.orElse(null); ++ if (chunk != null) return chunk; ++ } ++ } ++ } ++ return instance.getChunk(x, z, chunkStatus, create); ++ } ++ // DivineMC end - Chunk System optimization ++ + Holder getUncachedNoiseBiome(int x, int y, int z); + + boolean isClientSide(); +diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java +index 2199a9e2a0141c646d108f2687a27f1d165453c5..c28c2583b257f92207b822a1fdde8f5b7e480992 100644 +--- a/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -212,7 +212,38 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable { + }); + } + ++ // DivineMC start - Chunk System optimization ++ private void checkHardLimit() { ++ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit) { ++ LOGGER.warn("Chunk data cache size exceeded hard limit ({} >= {}), forcing writes to disk (you can increase chunkDataCacheLimit in c2me.toml)", this.pendingWrites.size(), org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit); ++ while (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit * 0.75) { ++ writeResult0(); ++ } ++ } ++ } ++ ++ private void writeResult0() { ++ java.util.Iterator> iterator = this.pendingWrites.entrySet().iterator(); ++ if (iterator.hasNext()) { ++ java.util.Map.Entry entry = iterator.next(); ++ iterator.remove(); ++ this.runStore(entry.getKey(), entry.getValue()); ++ } ++ } ++ // DivineMC end - Chunk System optimization ++ + private void storePendingChunk() { ++ // DivineMC start - Chunk System optimization ++ if (!this.pendingWrites.isEmpty()) { ++ checkHardLimit(); ++ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) { ++ int writeFrequency = Math.min(1, (this.pendingWrites.size() - (int) org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) / 16); ++ for (int i = 0; i < writeFrequency; i++) { ++ writeResult0(); ++ } ++ } ++ } ++ // DivineMC end - Chunk System optimization + Entry entry = this.pendingWrites.pollFirstEntry(); + if (entry != null) { + this.runStore(entry.getKey(), entry.getValue()); +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 6ebd1300c2561116b83cb2472ac7939ead36d576..16cd10ab8de69ca3d29c84cf93715645322fd72a 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -244,7 +244,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected + this.folder = folder; +- this.sync = sync; ++ this.sync = Boolean.parseBoolean(System.getProperty("com.ishland.c2me.chunkio.syncDiskWrites", String.valueOf(sync))); // DivineMC - C2ME: sync disk writes + this.info = info; + this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers + } diff --git a/divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch b/divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch new file mode 100644 index 0000000..a62fe05 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0013-Optimize-hoppers.patch @@ -0,0 +1,682 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:55:34 +0300 +Subject: [PATCH] Optimize hoppers + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 527547b98b70429830a3cf82fddba202e0ba8131..ee45df82c3328d5cf91cb3e56786aec2d5263641 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1765,7 +1765,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent + serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 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 + profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); + profilerFiller.push("tick"); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 92f3e5d929997a974c367ec3ce02cda4acdb5183..5dfab22692947e0e372044c6dca181f6847fc58a 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -195,7 +195,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); + private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); + final Set navigatingMobs = new ObjectOpenHashSet<>(); +- volatile boolean isUpdatingNavigations; ++ final java.util.concurrent.atomic.AtomicBoolean isUpdatingNavigations = new java.util.concurrent.atomic.AtomicBoolean(false); // DivineMC - Optimize Hoppers + protected final Raids raids; + private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); + private final List blockEventsToReschedule = new ArrayList<>(64); +@@ -1771,7 +1771,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { +- if (this.isUpdatingNavigations) { ++ if (this.isUpdatingNavigations.get() && false) { // DivineMC + String string = "recursive call to sendBlockUpdated"; + Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); + } +@@ -1802,13 +1802,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - catch CME see below why + + try { +- this.isUpdatingNavigations = true; ++ this.isUpdatingNavigations.set(true); // DivineMC + + for (PathNavigation pathNavigation : list) { + pathNavigation.recomputePath(); + } + } finally { +- this.isUpdatingNavigations = false; ++ this.isUpdatingNavigations.set(false); // DivineMC + } + } + } // Paper - option to disable pathfinding updates +@@ -2699,7 +2699,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (entity instanceof Mob mob) { +- if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning ++ if (false && ServerLevel.this.isUpdatingNavigations.get()) { // Paper - Remove unnecessary onTrackingStart during navigation warning // DivineMC + String string = "onTrackingStart called during navigation iteration"; + Util.logAndPauseIfInIde( + "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") +@@ -2769,7 +2769,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (entity instanceof Mob mob) { +- if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning ++ if (false && ServerLevel.this.isUpdatingNavigations.get()) { // Paper - Remove unnecessary onTrackingStart during navigation warning // DivineMC + String string = "onTrackingStart called during navigation iteration"; + Util.logAndPauseIfInIde( + "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 5cd1326ad5d046c88b2b3449d610a78fa880b4cd..a0ee6ad6e7a6791605191d20d742e16cc9857a60 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -139,56 +139,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- // Paper start - Perf: Optimize Hoppers +- private static final int HOPPER_EMPTY = 0; +- private static final int HOPPER_HAS_ITEMS = 1; +- private static final int HOPPER_IS_FULL = 2; +- +- private static int getFullState(final HopperBlockEntity hopper) { +- hopper.unpackLootTable(null); +- +- final List hopperItems = hopper.items; +- +- boolean empty = true; +- boolean full = true; +- +- for (int i = 0, len = hopperItems.size(); i < len; ++i) { +- final ItemStack stack = hopperItems.get(i); +- if (stack.isEmpty()) { +- full = false; +- continue; +- } +- +- if (!full) { +- // can't be full +- return HOPPER_HAS_ITEMS; +- } +- +- empty = false; +- +- if (stack.getCount() != stack.getMaxStackSize()) { +- // can't be full or empty +- return HOPPER_HAS_ITEMS; +- } +- } +- +- return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); +- } +- // Paper end - Perf: Optimize Hoppers +- + private static boolean tryMoveItems(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier validator) { + if (level.isClientSide) { + return false; + } else { + if (!blockEntity.isOnCooldown() && state.getValue(HopperBlock.ENABLED)) { + boolean flag = false; +- final int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers +- if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hoppers ++ if (!blockEntity.isEmpty()) { // DivineMC - Optimize hoppers + flag = ejectItems(level, pos, blockEntity); + } + +- if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers +- flag |= validator.getAsBoolean(); // Paper - note: this is not a validator, it's what adds/sucks in items ++ if (!blockEntity.inventoryFull()) { // DivineMC - Optimize hoppers ++ flag |= validator.getAsBoolean(); // DivineMC - Optimize hoppers + } + + if (flag) { +@@ -212,206 +174,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return true; + } + +- // Paper start - Perf: Optimize Hoppers +- public static boolean skipHopperEvents; +- private static boolean skipPullModeEventFire; +- private static boolean skipPushModeEventFire; +- +- private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { +- skipPushModeEventFire = skipHopperEvents; +- boolean foundItem = false; +- for (int i = 0; i < hopper.getContainerSize(); ++i) { +- final ItemStack item = hopper.getItem(i); +- if (!item.isEmpty()) { +- foundItem = true; +- ItemStack origItemStack = item; +- ItemStack movedItem = origItemStack; +- +- final int originalItemCount = origItemStack.getCount(); +- final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); +- origItemStack.setCount(movedItemCount); +- +- // We only need to fire the event once to give protection plugins a chance to cancel this event +- // Because nothing uses getItem, every event call should end up the same result. +- if (!skipPushModeEventFire) { +- movedItem = callPushMoveEvent(destination, movedItem, hopper); +- if (movedItem == null) { // cancelled +- origItemStack.setCount(originalItemCount); +- return false; +- } +- } +- +- final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); +- final int remainingItemCount = remainingItem.getCount(); +- if (remainingItemCount != movedItemCount) { +- origItemStack = origItemStack.copy(true); +- origItemStack.setCount(originalItemCount); +- if (!origItemStack.isEmpty()) { +- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); +- } +- hopper.setItem(i, origItemStack); +- destination.setChanged(); +- return true; +- } +- origItemStack.setCount(originalItemCount); +- } +- } +- if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown +- hopper.setCooldown(level.spigotConfig.hopperTransfer); +- } +- return false; +- } +- +- private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { +- ItemStack movedItem = origItemStack; +- final int originalItemCount = origItemStack.getCount(); +- final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); +- container.setChanged(); // original logic always marks source inv as changed even if no move happens. +- movedItem.setCount(movedItemCount); +- +- if (!skipPullModeEventFire) { +- movedItem = callPullMoveEvent(hopper, container, movedItem); +- if (movedItem == null) { // cancelled +- origItemStack.setCount(originalItemCount); +- // Drastically improve performance by returning true. +- // No plugin could have relied on the behavior of false as the other call +- // site for IMIE did not exhibit the same behavior +- return true; +- } +- } +- +- final ItemStack remainingItem = addItem(container, hopper, movedItem, null); +- final int remainingItemCount = remainingItem.getCount(); +- if (remainingItemCount != movedItemCount) { +- origItemStack = origItemStack.copy(true); +- origItemStack.setCount(originalItemCount); +- if (!origItemStack.isEmpty()) { +- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); +- } +- +- ignoreBlockEntityUpdates = true; +- container.setItem(i, origItemStack); +- ignoreBlockEntityUpdates = false; +- container.setChanged(); +- return true; +- } +- origItemStack.setCount(originalItemCount); +- +- if (level.paperConfig().hopper.cooldownWhenFull) { +- applyCooldown(hopper); +- } +- +- return false; +- } +- +- @Nullable +- private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) { +- final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination); +- final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent( +- hopper.getOwner(false).getInventory(), +- org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), +- destinationInventory, +- true +- ); +- final boolean result = event.callEvent(); +- if (!event.calledGetItem && !event.calledSetItem) { +- skipPushModeEventFire = true; +- } +- if (!result) { +- applyCooldown(hopper); +- return null; +- } +- +- if (event.calledSetItem) { +- return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); +- } else { +- return itemStack; +- } +- } +- +- @Nullable +- private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { +- final org.bukkit.inventory.Inventory sourceInventory = getInventory(container); +- final org.bukkit.inventory.Inventory destination = getInventory(hopper); +- +- // Mirror is safe as no plugins ever use this item +- final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false); +- final boolean result = event.callEvent(); +- if (!event.calledGetItem && !event.calledSetItem) { +- skipPullModeEventFire = true; +- } +- if (!result) { +- applyCooldown(hopper); +- return null; +- } +- +- if (event.calledSetItem) { +- return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); +- } else { +- return itemstack; +- } +- } +- +- private static org.bukkit.inventory.Inventory getInventory(final Container container) { +- final org.bukkit.inventory.Inventory sourceInventory; +- if (container instanceof net.minecraft.world.CompoundContainer compoundContainer) { +- // Have to special-case large chests as they work oddly +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- } else if (container instanceof BlockEntity blockEntity) { +- sourceInventory = blockEntity.getOwner(false).getInventory(); +- } else if (container.getOwner() != null) { +- sourceInventory = container.getOwner().getInventory(); +- } else { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); +- } +- return sourceInventory; +- } +- +- private static void applyCooldown(final Hopper hopper) { +- if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { +- blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); +- } +- } +- +- private static boolean allMatch(Container container, Direction direction, java.util.function.BiPredicate test) { +- if (container instanceof WorldlyContainer) { +- for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { +- if (!test.test(container.getItem(slot), slot)) { +- return false; +- } +- } +- } else { +- int size = container.getContainerSize(); +- for (int slot = 0; slot < size; slot++) { +- if (!test.test(container.getItem(slot), slot)) { +- return false; +- } +- } +- } +- return true; +- } +- +- private static boolean anyMatch(Container container, Direction direction, java.util.function.BiPredicate test) { +- if (container instanceof WorldlyContainer) { +- for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { +- if (test.test(container.getItem(slot), slot)) { +- return true; +- } +- } +- } else { +- int size = container.getContainerSize(); +- for (int slot = 0; slot < size; slot++) { +- if (test.test(container.getItem(slot), slot)) { +- return true; +- } +- } +- } +- return true; +- } +- private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemStack, i) -> itemStack.getCount() >= itemStack.getMaxStackSize(); +- private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemStack, i) -> itemStack.isEmpty(); +- // Paper end - Perf: Optimize Hoppers +- + private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) { + Container attachedContainer = getAttachedContainer(level, pos, blockEntity); + if (attachedContainer == null) { +@@ -421,60 +183,59 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (isFullContainer(attachedContainer, opposite)) { + return false; + } else { +- // Paper start - Perf: Optimize Hoppers +- return hopperPush(level, attachedContainer, opposite, blockEntity); +- //for (int i = 0; i < blockEntity.getContainerSize(); i++) { +- // ItemStack item = blockEntity.getItem(i); +- // if (!item.isEmpty()) { +- // int count = item.getCount(); +- // // CraftBukkit start - Call event when pushing items into other inventories +- // ItemStack original = item.copy(); +- // org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( +- // blockEntity.removeItem(i, level.spigotConfig.hopperAmount) +- // ); // Spigot +- +- // org.bukkit.inventory.Inventory destinationInventory; +- // // Have to special case large chests as they work oddly +- // if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { +- // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- // } else if (attachedContainer.getOwner() != null) { +- // destinationInventory = attachedContainer.getOwner().getInventory(); +- // } else { +- // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); +- // } +- +- // org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( +- // blockEntity.getOwner().getInventory(), +- // oitemstack, +- // destinationInventory, +- // true +- // ); +- // if (!event.callEvent()) { +- // blockEntity.setItem(i, original); +- // blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot +- // return false; +- // } +- // int origCount = event.getItem().getAmount(); // Spigot +- // ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); +- // // CraftBukkit end +- +- // if (itemStack.isEmpty()) { +- // attachedContainer.setChanged(); +- // return true; +- // } +- +- // item.setCount(count); +- // // Spigot start +- // item.shrink(origCount - itemStack.getCount()); +- // if (count <= level.spigotConfig.hopperAmount) { +- // // Spigot end +- // blockEntity.setItem(i, item); +- // } +- // } +- //} +- +- //return false; +- // Paper end - Perf: Optimize Hoppers ++ // DivineMC start - Optimize hoppers ++ for (int i = 0; i < blockEntity.getContainerSize(); i++) { ++ ItemStack item = blockEntity.getItem(i); ++ if (!item.isEmpty()) { ++ int count = item.getCount(); ++ // CraftBukkit start - Call event when pushing items into other inventories ++ ItemStack original = item.copy(); ++ org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( ++ blockEntity.removeItem(i, level.spigotConfig.hopperAmount) ++ ); // Spigot ++ ++ org.bukkit.inventory.Inventory destinationInventory; ++ // Have to special case large chests as they work oddly ++ if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ } else if (attachedContainer.getOwner() != null) { ++ destinationInventory = attachedContainer.getOwner().getInventory(); ++ } else { ++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); ++ } ++ ++ org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( ++ blockEntity.getOwner().getInventory(), ++ oitemstack, ++ destinationInventory, ++ true ++ ); ++ if (!event.callEvent()) { ++ blockEntity.setItem(i, original); ++ blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); ++ // CraftBukkit end ++ ++ if (itemStack.isEmpty()) { ++ attachedContainer.setChanged(); ++ return true; ++ } ++ ++ item.setCount(count); ++ // Spigot start ++ item.shrink(origCount - itemStack.getCount()); ++ if (count <= level.spigotConfig.hopperAmount) { ++ // Spigot end ++ blockEntity.setItem(i, item); ++ } ++ } ++ } ++ ++ return false; ++ // DivineMC end - Optimize hoppers + } + } + } +@@ -529,7 +290,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); + if (sourceContainer != null) { + Direction direction = Direction.DOWN; +- skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers + + for (int i : getSlots(sourceContainer, direction)) { + if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot +@@ -555,59 +315,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction, Level level) { // Spigot + ItemStack item = container.getItem(slot); + if (!item.isEmpty() && canTakeItemFromContainer(hopper, container, item, slot, direction)) { +- // Paper start - Perf: Optimize Hoppers +- return hopperPull(level, hopper, container, item, slot); +- //int count = item.getCount(); +- //// CraftBukkit start - Call event on collection of items from inventories into the hopper +- //ItemStack original = item.copy(); +- //org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( +- // container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot +- //); +- +- //org.bukkit.inventory.Inventory sourceInventory; +- //// Have to special case large chests as they work oddly +- //if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { +- // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- //} else if (container.getOwner() != null) { +- // sourceInventory = container.getOwner().getInventory(); +- //} else { +- // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); +- //} +- +- //org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( +- // sourceInventory, +- // oitemstack, +- // hopper.getOwner().getInventory(), +- // false +- //); +- +- //if (!event.callEvent()) { +- // container.setItem(slot, original); +- +- // if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { +- // hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot +- // } +- +- // return false; +- //} +- //int origCount = event.getItem().getAmount(); // Spigot +- //ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); +- //// CraftBukkit end +- +- //if (itemStack.isEmpty()) { +- // container.setChanged(); +- // return true; +- //} +- +- //item.setCount(count); +- //// Spigot start +- //item.shrink(origCount - itemStack.getCount()); +- //if (count <= level.spigotConfig.hopperAmount) { +- // // Spigot end +- // container.setItem(slot, item); +- //} +- // Paper end - Perf: Optimize Hoppers ++ // DivineMC start - Optimize hoppers ++ int count = item.getCount(); ++ // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ ItemStack original = item.copy(); ++ org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( ++ container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot ++ ); ++ ++ org.bukkit.inventory.Inventory sourceInventory; ++ // Have to special case large chests as they work oddly ++ if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ } else if (container.getOwner() != null) { ++ sourceInventory = container.getOwner().getInventory(); ++ } else { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); ++ } ++ ++ org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( ++ sourceInventory, ++ oitemstack, ++ hopper.getOwner().getInventory(), ++ false ++ ); ++ ++ if (!event.callEvent()) { ++ container.setItem(slot, original); ++ ++ if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { ++ hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot ++ } ++ ++ return false; ++ } ++ int origCount = event.getItem().getAmount(); // Spigot ++ ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); ++ // CraftBukkit end ++ ++ if (itemStack.isEmpty()) { ++ container.setChanged(); ++ return true; ++ } ++ ++ item.setCount(count); ++ // Spigot start ++ item.shrink(origCount - itemStack.getCount()); ++ if (count <= level.spigotConfig.hopperAmount) { ++ // Spigot end ++ container.setItem(slot, item); ++ } + } ++ // DivineMC end - Optimize hoppers + + return false; + } +@@ -615,15 +374,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container container, ItemEntity item) { + boolean flag = false; + // CraftBukkit start +- if (org.bukkit.event.inventory.InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers + org.bukkit.event.inventory.InventoryPickupItemEvent event = new org.bukkit.event.inventory.InventoryPickupItemEvent( +- getInventory(container), (org.bukkit.entity.Item) item.getBukkitEntity() // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation ++ container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity() // DivineMC - Optimize hoppers + ); + if (!event.callEvent()) { + return false; + } + // CraftBukkit end +- } // Paper - Perf: Optimize Hoppers + ItemStack itemStack = item.getItem().copy(); + ItemStack itemStack1 = addItem(null, container, itemStack, null); + if (itemStack1.isEmpty()) { +@@ -678,9 +435,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(destination.getMaxStackSize()); + } + // Spigot end +- ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers + destination.setItem(slot, stack); +- ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers + stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (canMergeItems(item, stack)) { +@@ -768,19 +523,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + public static Container getContainerAt(Level level, BlockPos pos) { +- return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, true); // Paper - Optimize hoppers ++ return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); // DivineMC - Optimize hoppers + } + + @Nullable + private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z) { +- // Paper start - Perf: Optimize Hoppers +- return HopperBlockEntity.getContainerAt(level, pos, state, x, y, z, false); +- } +- @Nullable +- private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z, final boolean optimizeEntities) { +- // Paper end - Perf: Optimize Hoppers + Container blockContainer = getBlockContainer(level, pos, state); +- if (blockContainer == null && (!optimizeEntities || !level.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers ++ if (blockContainer == null) { // DivineMC - Optimize hoppers + blockContainer = getEntityContainer(level, x, y, z); + } + +@@ -806,14 +555,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static Container getEntityContainer(Level level, double x, double y, double z) { +- List entities = level.getEntitiesOfClass( +- (Class) Container.class, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR // Paper - Perf: Optimize hoppers +- ); ++ List entities = level.getEntities( ++ (Entity)null, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR ++ ); // DivineMC - Optimize hoppers + return !entities.isEmpty() ? (Container)entities.get(level.random.nextInt(entities.size())) : null; + } + + private static boolean canMergeItems(ItemStack stack1, ItemStack stack2) { +- return stack1.getCount() < stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! ++ return stack1.getCount() <= stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // DivineMC - Optimize hoppers + } + + @Override +diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java +index faf45ac459f7c25309d6ef6dce371d484a0dae7b..6f0d1b28a45b93c51c5476283f1629a86e3420d1 100644 +--- a/net/minecraft/world/ticks/LevelChunkTicks.java ++++ b/net/minecraft/world/ticks/LevelChunkTicks.java +@@ -17,7 +17,8 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.ListTag; + import net.minecraft.world.level.ChunkPos; + +-public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // Paper - rewrite chunk system ++public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // DivineMC ++ private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LevelChunkTicks.class); // Paper - rewrite chunk system + private final Queue> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); + @Nullable + private List> pendingTicks; +@@ -71,10 +72,18 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + @Nullable + public ScheduledTick poll() { +- ScheduledTick scheduledTick = this.tickQueue.poll(); +- if (scheduledTick != null) { +- this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system ++ // DivineMC start - catch exceptions when polling chunk ticks ++ ScheduledTick scheduledTick = null; ++ try { ++ scheduledTick = this.tickQueue.poll(); ++ if (scheduledTick != null) { ++ this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system ++ } ++ } catch (Exception e) { ++ log.error("Encountered caught exception when polling chunk ticks, blocking and returning null.", e); ++ return null; + } ++ // DivineMC end - catch exceptions when polling chunk ticks + + return scheduledTick; + } diff --git a/divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch b/divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch new file mode 100644 index 0000000..57d83c9 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0014-Some-optimizations.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 15:59:29 +0300 +Subject: [PATCH] Some optimizations + + +diff --git a/net/minecraft/server/level/ChunkTrackingView.java b/net/minecraft/server/level/ChunkTrackingView.java +index bee90335677f7d8b01589ce5cfd81a40fd422886..a5e488d14fd2016ee188b114d0e681562b5b09cc 100644 +--- a/net/minecraft/server/level/ChunkTrackingView.java ++++ b/net/minecraft/server/level/ChunkTrackingView.java +@@ -73,12 +73,12 @@ public interface ChunkTrackingView { + } + + static boolean isWithinDistance(int centerX, int centerZ, int viewDistance, int x, int z, boolean includeOuterChunksAdjacentToViewBorder) { +- int i = includeOuterChunksAdjacentToViewBorder ? 2 : 1; +- long l = Math.max(0, Math.abs(x - centerX) - i); +- long l1 = Math.max(0, Math.abs(z - centerZ) - i); +- long l2 = l * l + l1 * l1; +- int i1 = viewDistance * viewDistance; +- return l2 < i1; ++ // DivineMC start - Some optimizations ++ int actualViewDistance = viewDistance + (includeOuterChunksAdjacentToViewBorder ? 1 : 0); ++ int xDistance = Math.abs(centerX - x); ++ int zDistance = Math.abs(centerZ - z); ++ return xDistance <= actualViewDistance && zDistance <= actualViewDistance; ++ // DivineMC end - Some optimizations + } + + public record Positioned(ChunkPos center, int viewDistance) implements ChunkTrackingView { +diff --git a/net/minecraft/util/ClassInstanceMultiMap.java b/net/minecraft/util/ClassInstanceMultiMap.java +index 2a708ae0d5bb209650b525e3c56051f8b5655074..762cba15597623f95a242bdd44742d9b892ad042 100644 +--- a/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/net/minecraft/util/ClassInstanceMultiMap.java +@@ -14,9 +14,9 @@ import java.util.Map.Entry; + import net.minecraft.Util; + + public class ClassInstanceMultiMap extends AbstractCollection { +- private final Map, List> byClass = Maps.newHashMap(); ++ private final Map, List> byClass = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // DivineMC - Some optimizations + private final Class baseClass; +- private final List allInstances = Lists.newArrayList(); ++ private final List allInstances = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // DivineMC - Some optimizations + + public ClassInstanceMultiMap(Class baseClass) { + this.baseClass = baseClass; +@@ -56,13 +56,27 @@ public class ClassInstanceMultiMap extends AbstractCollection { + } + + public Collection find(Class type) { ++ // DivineMC start - Some optimizations ++ List cached = this.byClass.get(type); ++ if (cached != null) return (Collection) cached; ++ + if (!this.baseClass.isAssignableFrom(type)) { + throw new IllegalArgumentException("Don't know how to search for " + type); + } else { +- List list = this.byClass +- .computeIfAbsent(type, clazz -> this.allInstances.stream().filter(clazz::isInstance).collect(Util.toMutableList())); +- return (Collection)Collections.unmodifiableCollection(list); ++ List list = this.byClass.computeIfAbsent(type, ++ typeClass -> { ++ it.unimi.dsi.fastutil.objects.ObjectArrayList ts = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(this.allInstances.size()); ++ for (Object _allElement : ((it.unimi.dsi.fastutil.objects.ObjectArrayList) this.allInstances).elements()) { ++ if (typeClass.isInstance(_allElement)) { ++ ts.add((T) _allElement); ++ } ++ } ++ return ts; ++ } ++ ); ++ return (Collection) list; + } ++ // DivineMC end - Some optimizations + } + + @Override +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 70ee86993d381445855ac7e7290da384d6675987..532d71cc1eaee799c193eb43085beb8c5892eac7 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -841,7 +841,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { + this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Entity nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API ++ Entity nearestPlayer = this.divinemc$findNearbyPlayer(this.level(), this, -1.0); // Paper - Affects Spawning API // DivineMC - faster player lookup + if (nearestPlayer != null) { + // Paper start - Configurable despawn distances + final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()); +@@ -870,6 +870,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } + } + ++ // DivineMC start - faster player lookup ++ private Player divinemc$findNearbyPlayer(Level instance, Entity entity, double maxDistance) { ++ final Player closestPlayer = instance.getNearestPlayer(entity, this.getType().getCategory().getDespawnDistance()); ++ if (closestPlayer != null) { ++ return closestPlayer; ++ } else { ++ final List players = this.level().players(); ++ if (players.isEmpty()) return null; ++ return players.get(0); ++ } ++ } ++ // DivineMC end - faster player lookup ++ + @Override + protected final void serverAiStep() { + this.noActionTime++; +diff --git a/net/minecraft/world/level/LocalMobCapCalculator.java b/net/minecraft/world/level/LocalMobCapCalculator.java +index 9641219c190261dea0db5f95f040a705ba0a3ff9..7ba64e71cfed16f07a9e1283145653745adb6388 100644 +--- a/net/minecraft/world/level/LocalMobCapCalculator.java ++++ b/net/minecraft/world/level/LocalMobCapCalculator.java +@@ -42,14 +42,14 @@ public class LocalMobCapCalculator { + } + + static class MobCounts { +- private final Object2IntMap counts = new Object2IntOpenHashMap<>(MobCategory.values().length); ++ private final int[] spawnGroupDensities = new int[MobCategory.values().length]; // DivineMC - Some optimizations + + public void add(MobCategory category) { +- this.counts.computeInt(category, (key, value) -> value == null ? 1 : value + 1); ++ this.spawnGroupDensities[category.ordinal()] ++; // DivineMC - Some optimizations + } + + public boolean canSpawn(MobCategory category) { +- return this.counts.getOrDefault(category, 0) < category.getMaxInstancesPerChunk(); ++ return this.spawnGroupDensities[category.ordinal()] < category.getMaxInstancesPerChunk(); // DivineMC - Some optimizations + } + } + } +diff --git a/net/minecraft/world/level/storage/DimensionDataStorage.java b/net/minecraft/world/level/storage/DimensionDataStorage.java +index d9a3b5a2e6495b7e22c114506c2bd1e406f58f8f..a6e03345afd6d8a38e06a43c59103209618baa14 100644 +--- a/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -34,7 +34,7 @@ import org.slf4j.Logger; + + public class DimensionDataStorage implements AutoCloseable { + private static final Logger LOGGER = LogUtils.getLogger(); +- public final Map> cache = new HashMap<>(); ++ public final Map> cache = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Concurrent HashMap + private final DataFixer fixerUpper; + private final HolderLookup.Provider registries; + private final Path dataFolder; diff --git a/divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch b/divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch new file mode 100644 index 0000000..ddf9734 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0015-Optimize-entity-stupid-brain.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 16:05:57 +0300 +Subject: [PATCH] Optimize entity stupid brain + + +diff --git a/net/minecraft/world/entity/AgeableMob.java b/net/minecraft/world/entity/AgeableMob.java +index 179f4e4b9b1eb57f78bbb2f9fa34b11ea79b7a88..143a4ca51a57934bf545e031b10525dedbe9c3bd 100644 +--- a/net/minecraft/world/entity/AgeableMob.java ++++ b/net/minecraft/world/entity/AgeableMob.java +@@ -121,6 +121,16 @@ public abstract class AgeableMob extends PathfinderMob { + public void onSyncedDataUpdated(EntityDataAccessor key) { + if (DATA_BABY_ID.equals(key)) { + this.refreshDimensions(); ++ // DivineMC start - Optimize entity stupid brain ++ if (isBaby()) { ++ org.bxteam.divinemc.util.entity.SensorHelper.enableSensor(this, net.minecraft.world.entity.ai.sensing.SensorType.NEAREST_ADULT, true); ++ } else { ++ org.bxteam.divinemc.util.entity.SensorHelper.disableSensor(this, net.minecraft.world.entity.ai.sensing.SensorType.NEAREST_ADULT); ++ if (this.getBrain().hasMemoryValue(net.minecraft.world.entity.ai.memory.MemoryModuleType.NEAREST_VISIBLE_ADULT)) { ++ this.getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.NEAREST_VISIBLE_ADULT, java.util.Optional.empty()); ++ } ++ } ++ // DivineMC end - Optimize entity stupid brain + } + + super.onSyncedDataUpdated(key); +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index 8f7efe6b2c191f615dfc8394baec44dc0761ff51..406eb049cb22d0736d8b003a2f547cc25c6f68b6 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java +@@ -45,16 +45,73 @@ public class Brain { + static final Logger LOGGER = LogUtils.getLogger(); + private final Supplier>> codec; + private static final int SCHEDULE_UPDATE_DELAY = 20; +- private final Map, Optional>> memories = Maps.newHashMap(); +- public final Map>, Sensor> sensors = Maps.newLinkedHashMap(); +- private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); ++ private Map, Optional>> memories = Maps.newConcurrentMap(); // DivineMC - concurrent map ++ public Map>, Sensor> sensors = Maps.newLinkedHashMap(); // DivineMC - linked hash map ++ private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); // DivineMC - tree map + private Schedule schedule = Schedule.EMPTY; +- private final Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); ++ private Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); // DivineMC - hash map + private final Map>> activityMemoriesToEraseWhenStopped = Maps.newHashMap(); + private Set coreActivities = Sets.newHashSet(); + private final Set activeActivities = Sets.newHashSet(); + private Activity defaultActivity = Activity.IDLE; + private long lastScheduleUpdate = -9999L; ++ // DivineMC start - Optimize entity stupid brain ++ private java.util.ArrayList> possibleTasks; ++ private org.bxteam.divinemc.util.collections.MaskedList> runningTasks; ++ ++ private void onTasksChanged() { ++ this.runningTasks = null; ++ this.onPossibleActivitiesChanged(); ++ } ++ ++ private void onPossibleActivitiesChanged() { ++ this.possibleTasks = null; ++ } ++ ++ private void initPossibleTasks() { ++ this.possibleTasks = new java.util.ArrayList<>(); ++ for (Map>> map : this.availableBehaviorsByPriority.values()) { ++ for (Map.Entry>> entry : map.entrySet()) { ++ Activity activity = entry.getKey(); ++ if (!this.activeActivities.contains(activity)) { ++ continue; ++ } ++ Set> set = entry.getValue(); ++ for (BehaviorControl task : set) { ++ //noinspection UseBulkOperation ++ this.possibleTasks.add(task); ++ } ++ } ++ } ++ } ++ ++ private java.util.ArrayList> getPossibleTasks() { ++ if (this.possibleTasks == null) { ++ this.initPossibleTasks(); ++ } ++ return this.possibleTasks; ++ } ++ ++ private org.bxteam.divinemc.util.collections.MaskedList> getCurrentlyRunningTasks() { ++ if (this.runningTasks == null) { ++ this.initCurrentlyRunningTasks(); ++ } ++ return this.runningTasks; ++ } ++ ++ private void initCurrentlyRunningTasks() { ++ org.bxteam.divinemc.util.collections.MaskedList> list = new org.bxteam.divinemc.util.collections.MaskedList<>(new ObjectArrayList<>(), false); ++ ++ for (Map>> map : this.availableBehaviorsByPriority.values()) { ++ for (Set> set : map.values()) { ++ for (BehaviorControl task : set) { ++ list.addOrSet(task, task.getStatus() == Behavior.Status.RUNNING); ++ } ++ } ++ } ++ this.runningTasks = list; ++ } ++ // DivineMC end - Optimize entity stupid brain + + public static Brain.Provider provider( + Collection> memoryTypes, Collection>> sensorTypes +@@ -146,6 +203,12 @@ public class Brain { + for (Brain.MemoryValue memoryValue : memoryValues) { + memoryValue.setMemoryInternal(this); + } ++ // DivineMC start - Optimize entity stupid brain ++ this.onTasksChanged(); ++ this.memories = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(this.memories); ++ this.sensors = new it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap<>(this.sensors); ++ this.activityRequirements = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.activityRequirements); ++ // DivineMC end - Optimize entity stupid brain + } + + public DataResult serializeStart(DynamicOps ops) { +@@ -165,6 +228,7 @@ public class Brain { + } + + public void eraseMemory(MemoryModuleType type) { ++ if (!this.memories.containsKey(type)) return; // DivineMC - skip if memory does not contain key + this.setMemory(type, Optional.empty()); + } + +@@ -180,16 +244,33 @@ public class Brain { + this.setMemoryInternal(memoryType, memory.map(ExpirableValue::of)); + } + ++ // DivineMC start - Optimize entity stupid brain + void setMemoryInternal(MemoryModuleType memoryType, Optional> memory) { ++ if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { ++ this.eraseMemory(memoryType); ++ return; ++ } ++ + if (this.memories.containsKey(memoryType)) { +- if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { +- this.eraseMemory(memoryType); +- } else { +- this.memories.put(memoryType, memory); +- } ++ this.increaseMemoryModificationCount(this.memories, memoryType, memory); + } + } + ++ private long memoryModCount = 1; ++ ++ public long getMemoryModCount() { ++ return memoryModCount; ++ } ++ ++ private Object increaseMemoryModificationCount(Map map, T key, A newValue) { ++ Object oldValue = map.put(key, newValue); ++ if (oldValue == null || ((Optional) oldValue).isPresent() != ((Optional) newValue).isPresent()) { ++ this.memoryModCount++; ++ } ++ return oldValue; ++ } ++ // DivineMC end - Optimize entity stupid brain ++ + public Optional getMemory(MemoryModuleType type) { + Optional> optional = this.memories.get(type); + if (optional == null) { +@@ -251,19 +332,7 @@ public class Brain { + @Deprecated + @VisibleForDebug + public List> getRunningBehaviors() { +- List> list = new ObjectArrayList<>(); +- +- for (Map>> map : this.availableBehaviorsByPriority.values()) { +- for (Set> set : map.values()) { +- for (BehaviorControl behaviorControl : set) { +- if (behaviorControl.getStatus() == Behavior.Status.RUNNING) { +- list.add(behaviorControl); +- } +- } +- } +- } +- +- return list; ++ return this.getCurrentlyRunningTasks(); // DivineMC - Optimize entity stupid brain + } + + public void useDefaultActivity() { +@@ -294,6 +363,7 @@ public class Brain { + this.activeActivities.clear(); + this.activeActivities.addAll(this.coreActivities); + this.activeActivities.add(activity); ++ this.onPossibleActivitiesChanged(); // DivineMC - Optimize entity stupid brain + } + } + +@@ -374,11 +444,13 @@ public class Brain { + .computeIfAbsent(activity, activity1 -> Sets.newLinkedHashSet()) + .add((BehaviorControl)pair.getSecond()); + } ++ this.onTasksChanged(); // DivineMC - Optimize entity stupid brain + } + + @VisibleForTesting + public void removeAllBehaviors() { + this.availableBehaviorsByPriority.clear(); ++ this.onTasksChanged(); // DivineMC - Optimize entity stupid brain + } + + public boolean isActive(Activity activity) { +@@ -395,6 +467,7 @@ public class Brain { + } + } + ++ brain.memoryModCount = this.memoryModCount + 1; // DivineMC - Optimize entity stupid brain + return brain; + } + +@@ -429,31 +502,38 @@ public class Brain { + + for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { + behaviorControl.doStop(level, owner, gameTime); ++ // DivineMC start - Optimize entity stupid brain ++ if (this.runningTasks != null) { ++ this.runningTasks.setVisible(behaviorControl, false); ++ } ++ // DivineMC end - Optimize entity stupid brain + } + } + ++ // DivineMC start - Optimize entity stupid brain + private void startEachNonRunningBehavior(ServerLevel level, E entity) { +- long gameTime = level.getGameTime(); +- +- for (Map>> map : this.availableBehaviorsByPriority.values()) { +- for (Entry>> entry : map.entrySet()) { +- Activity activity = entry.getKey(); +- if (this.activeActivities.contains(activity)) { +- for (BehaviorControl behaviorControl : entry.getValue()) { +- if (behaviorControl.getStatus() == Behavior.Status.STOPPED) { +- behaviorControl.tryStart(level, entity, gameTime); +- } +- } ++ long startTime = level.getGameTime(); ++ for (BehaviorControl task : this.getPossibleTasks()) { ++ if (task.getStatus() == Behavior.Status.STOPPED) { ++ task.tryStart(level, entity, startTime); ++ if (this.runningTasks != null && task.getStatus() == Behavior.Status.RUNNING) { ++ this.runningTasks.setVisible(task, true); + } + } + } + } ++ // DivineMC end - Optimize entity stupid brain + + private void tickEachRunningBehavior(ServerLevel level, E entity) { + long gameTime = level.getGameTime(); + + for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { + behaviorControl.tickOrStop(level, entity, gameTime); ++ // DivineMC start - Optimize entity stupid brain ++ if (this.runningTasks != null && behaviorControl.getStatus() != Behavior.Status.RUNNING) { ++ this.runningTasks.setVisible(behaviorControl, false); ++ } ++ // DivineMC end - Optimize entity stupid brain + } + } + +diff --git a/net/minecraft/world/entity/ai/behavior/Behavior.java b/net/minecraft/world/entity/ai/behavior/Behavior.java +index 5b0cadd2544fb2a627822e645ff32fec2e9cfda9..253b9ad671cf0932bb17d468f8b91a15a86ff77a 100644 +--- a/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -14,6 +14,10 @@ public abstract class Behavior implements BehaviorContro + private long endTimestamp; + private final int minDuration; + private final int maxDuration; ++ // DivineMC start - Optimize entity stupid brain ++ private long cachedMemoryModCount = -1; ++ private boolean cachedHasRequiredMemoryState; ++ // DivineMC end - Optimize entity stupid brain + private final String configKey; // Paper - configurable behavior tick rate and timings + + public Behavior(Map, MemoryStatus> entryCondition) { +@@ -27,7 +31,7 @@ public abstract class Behavior implements BehaviorContro + public Behavior(Map, MemoryStatus> entryCondition, int minDuration, int maxDuration) { + this.minDuration = minDuration; + this.maxDuration = maxDuration; +- this.entryCondition = entryCondition; ++ this.entryCondition = new it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap<>(entryCondition); // DivineMC - Optimize entity stupid brain - Use fastutil + // Paper start - configurable behavior tick rate and timings + String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); + int lastSeparator = key.lastIndexOf('.'); +@@ -103,17 +107,26 @@ public abstract class Behavior implements BehaviorContro + return this.getClass().getSimpleName(); + } + +- protected boolean hasRequiredMemories(E owner) { +- for (Entry, MemoryStatus> entry : this.entryCondition.entrySet()) { +- MemoryModuleType memoryModuleType = entry.getKey(); +- MemoryStatus memoryStatus = entry.getValue(); +- if (!owner.getBrain().checkMemory(memoryModuleType, memoryStatus)) { +- return false; ++ // DivineMC start - Optimize entity stupid brain ++ public boolean hasRequiredMemories(E entity) { ++ net.minecraft.world.entity.ai.Brain brain = entity.getBrain(); ++ long modCount = brain.getMemoryModCount(); ++ if (this.cachedMemoryModCount == modCount) { ++ return this.cachedHasRequiredMemoryState; ++ } ++ this.cachedMemoryModCount = modCount; ++ ++ it.unimi.dsi.fastutil.objects.ObjectIterator, net.minecraft.world.entity.ai.memory.MemoryStatus>> fastIterator = ((it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap, net.minecraft.world.entity.ai.memory.MemoryStatus>) this.entryCondition).reference2ObjectEntrySet().fastIterator(); ++ while (fastIterator.hasNext()) { ++ it.unimi.dsi.fastutil.objects.Reference2ObjectMap.Entry, MemoryStatus> entry = fastIterator.next(); ++ if (!brain.checkMemory(entry.getKey(), entry.getValue())) { ++ return this.cachedHasRequiredMemoryState = false; + } + } + +- return true; ++ return this.cachedHasRequiredMemoryState = true; + } ++ // DivineMC end - Optimize entity stupid brain + + public static enum Status { + STOPPED, +diff --git a/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java b/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +index ec90ea4e66c6c38d7ad41805a16c63e006e44be4..0204fe68c97d152a7c3201620b6709a8bebefdf6 100644 +--- a/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java ++++ b/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +@@ -120,6 +120,12 @@ public class LongJumpToRandomPos extends Behavior { + int x = blockPos.getX(); + int y = blockPos.getY(); + int z = blockPos.getZ(); ++ // DivineMC start - Optimize entity stupid brain ++ if (this.maxLongJumpWidth < 128 && this.maxLongJumpHeight < 128) { ++ this.jumpCandidates = org.bxteam.divinemc.util.collections.LongJumpChoiceList.forCenter(blockPos, (byte) this.maxLongJumpWidth, (byte) this.maxLongJumpHeight); ++ return; ++ } ++ // DivineMC end - Optimize entity stupid brain + this.jumpCandidates = BlockPos.betweenClosedStream( + x - this.maxLongJumpWidth, + y - this.maxLongJumpHeight, +@@ -175,11 +181,27 @@ public class LongJumpToRandomPos extends Behavior { + } + } + ++ // DivineMC start - Optimize entity stupid brain + protected Optional getJumpCandidate(ServerLevel level) { +- Optional randomItem = WeightedRandom.getRandomItem(level.random, this.jumpCandidates); +- randomItem.ifPresent(this.jumpCandidates::remove); +- return randomItem; ++ Optional optional = getRandomFast(level.random, this.jumpCandidates); ++ skipRemoveIfAlreadyRemoved(optional, this.jumpCandidates::remove); ++ return optional; ++ } ++ ++ private Optional getRandomFast(net.minecraft.util.RandomSource random, List pool) { ++ if (pool instanceof org.bxteam.divinemc.util.collections.LongJumpChoiceList longJumpChoiceList) { ++ return Optional.ofNullable(longJumpChoiceList.removeRandomWeightedByDistanceSq(random)); ++ } else { ++ return WeightedRandom.getRandomItem(random, pool); ++ } ++ } ++ ++ private void skipRemoveIfAlreadyRemoved(Optional result, java.util.function.Consumer removeAction) { ++ if (!(this.jumpCandidates instanceof org.bxteam.divinemc.util.collections.LongJumpChoiceList)) { ++ result.ifPresent(removeAction); ++ } + } ++ // DivineMC end - Optimize entity stupid brain + + private boolean isAcceptableLandingPosition(ServerLevel level, E entity, BlockPos pos) { + BlockPos blockPos = entity.blockPosition(); +diff --git a/net/minecraft/world/entity/animal/goat/Goat.java b/net/minecraft/world/entity/animal/goat/Goat.java +index 6f106f10466440f8e65e04511f67d48f082d703f..15728d4fbe7a12c7a3b94a9ef88e7141b1225fa3 100644 +--- a/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/net/minecraft/world/entity/animal/goat/Goat.java +@@ -98,6 +98,13 @@ public class Goat extends Animal { + this.getNavigation().setCanFloat(true); + this.setPathfindingMalus(PathType.POWDER_SNOW, -1.0F); + this.setPathfindingMalus(PathType.DANGER_POWDER_SNOW, -1.0F); ++ // DivineMC start - Optimize entity stupid brain ++ if (!this.getBrain().hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)) { ++ org.bxteam.divinemc.util.entity.SensorHelper.disableSensor(this, SensorType.NEAREST_ITEMS); ++ } else if (net.minecraft.SharedConstants.IS_RUNNING_IN_IDE) { ++ throw new IllegalStateException("Goat Entity has a nearest visible wanted item memory module! This patch(Optimize-Brain, Goat.java changes) should probably be removed permanently!"); ++ } ++ // DivineMC end - Optimize entity stupid brain + } + + public ItemStack createHorn() { diff --git a/divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch b/divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch new file mode 100644 index 0000000..63ef6ba --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0016-Clump-experience-orbs.patch @@ -0,0 +1,211 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 16:47:09 +0300 +Subject: [PATCH] Clump experience orbs + + +diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java +index a43e5190c0f9ae14ccecccd5b58dc0e17f18b0a1..06ffba13f211851e8f6d630a72b41474673e8df8 100644 +--- a/net/minecraft/world/entity/ExperienceOrb.java ++++ b/net/minecraft/world/entity/ExperienceOrb.java +@@ -49,6 +49,10 @@ public class ExperienceOrb extends Entity { + @javax.annotation.Nullable + public java.util.UUID triggerEntityId; + public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ // DivineMC start - Clump experience orbs ++ public java.util.Map clumps$clumpedMap; ++ public Optional clumps$currentEntry; ++ // DivineMC end - Clump experience orbs + + private void loadPaperNBT(CompoundTag tag) { + if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) { +@@ -239,6 +243,28 @@ public class ExperienceOrb extends Entity { + } + + private static boolean tryMergeToExisting(ServerLevel level, Vec3 pos, int amount) { ++ // DivineMC start - Clump experience orbs ++ if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ AABB aABB = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D); ++ int id = level.getRandom().nextInt(40); ++ List list = level.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), aABB, (experienceOrbx) -> canMerge(experienceOrbx, id, amount)); ++ if(!list.isEmpty()) { ++ ExperienceOrb experienceOrb = list.getFirst(); ++ java.util.Map clumpedMap = (experienceOrb).clumps$getClumpedMap(); ++ (experienceOrb).clumps$setClumpedMap(java.util.stream.Stream.of(clumpedMap, java.util.Collections.singletonMap(amount, 1)) ++ .flatMap(map -> map.entrySet().stream()) ++ .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue, Integer::sum))); ++ (experienceOrb).count = (clumpedMap.values() ++ .stream() ++ .reduce(Integer::sum) ++ .orElse(1)); ++ (experienceOrb).age = (0); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // DivineMC end - Clump experience orbs + // Paper - TODO some other event for this kind of merge + AABB aabb = AABB.ofSize(pos, 1.0, 1.0, 1.0); + int randomInt = level.getRandom().nextInt(40); +@@ -254,11 +280,11 @@ public class ExperienceOrb extends Entity { + } + + private boolean canMerge(ExperienceOrb orb) { +- return orb != this && canMerge(orb, this.getId(), this.value); ++ return org.bxteam.divinemc.DivineConfig.clumpOrbs ? orb.isAlive() && !this.is(orb) : orb != this && ExperienceOrb.canMerge(orb, this.getId(), this.value); // DivineMC - Clump experience orbs + } + + private static boolean canMerge(ExperienceOrb orb, int amount, int other) { +- return !orb.isRemoved() && (orb.getId() - amount) % 40 == 0 && orb.value == other; ++ return org.bxteam.divinemc.DivineConfig.clumpOrbs ? orb.isAlive() : !orb.isRemoved() && (orb.getId() - amount) % 40 == 0 && orb.value == other; // DivineMC - Clump experience orbs + } + + private void merge(ExperienceOrb orb) { +@@ -267,6 +293,18 @@ public class ExperienceOrb extends Entity { + return; + } + // Paper end - call orb merge event ++ // DivineMC start - Clump experience orbs ++ if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ java.util.Map otherMap = (orb).clumps$getClumpedMap(); ++ this.count = clumps$getClumpedMap().values().stream().reduce(Integer::sum).orElse(1); ++ this.age = Math.min(this.age, (orb).age); ++ clumps$setClumpedMap(java.util.stream.Stream.of(clumps$getClumpedMap(), otherMap) ++ .flatMap(map -> map.entrySet().stream()) ++ .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue, Integer::sum))); ++ orb.discard(EntityRemoveEvent.Cause.MERGE); // DivineMC - add Bukkit remove cause ++ return; ++ } ++ // DivineMC end - Clump experience orbs + this.count = this.count + orb.count; + this.age = Math.min(this.age, orb.age); + orb.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause +@@ -308,6 +346,13 @@ public class ExperienceOrb extends Entity { + compound.putInt("Value", this.value); // Paper - save as Integer + compound.putInt("Count", this.count); + this.savePaperNBT(compound); // Paper ++ // DivineMC start - Clump experience orbs ++ if (clumps$clumpedMap != null) { ++ CompoundTag map = new CompoundTag(); ++ clumps$getClumpedMap().forEach((value, count) -> map.putInt(String.valueOf(value), count)); ++ compound.put("clumpedMap", map); ++ } ++ // DivineMC end - Clump experience orbs + } + + @Override +@@ -317,10 +362,51 @@ public class ExperienceOrb extends Entity { + this.value = compound.getInt("Value"); // Paper - load as Integer + this.count = Math.max(compound.getInt("Count"), 1); + this.loadPaperNBT(compound); // Paper ++ // DivineMC start - Clump experience orbs ++ java.util.Map map = new java.util.HashMap<>(); ++ if (compound.contains("clumpedMap")) { ++ CompoundTag clumpedMap = compound.getCompound("clumpedMap"); ++ for (String s : clumpedMap.getAllKeys()) { ++ map.put(Integer.parseInt(s), clumpedMap.getInt(s)); ++ } ++ } else { ++ map.put(value, count); ++ } ++ ++ clumps$setClumpedMap(map); ++ // DivineMC end - Clump experience orbs + } + + @Override + public void playerTouch(Player entity) { ++ // DivineMC start - Clump experience orbs ++ if(entity instanceof ServerPlayer && org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ entity.takeXpDelay = 0; ++ entity.take(this, 1); ++ ++ if(this.value != 0 || clumps$resolve()) { ++ java.util.concurrent.atomic.AtomicInteger toGive = new java.util.concurrent.atomic.AtomicInteger(); ++ clumps$getClumpedMap().forEach((value, amount) -> { ++ int actualValue = value; ++ ++ for(int i = 0; i < amount; i++) { ++ int leftOver = actualValue; ++ if(leftOver == actualValue) { ++ leftOver = this.repairPlayerItems((ServerPlayer) entity, actualValue); ++ } ++ if(leftOver > 0) { ++ toGive.addAndGet(leftOver); ++ } ++ } ++ }); ++ if(toGive.get() > 0) { ++ entity.giveExperiencePoints(toGive.get()); ++ } ++ } ++ this.discard(); ++ return; ++ } ++ // DivineMC end - Clump experience orbs + 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, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay +@@ -338,10 +424,57 @@ public class ExperienceOrb extends Entity { + } + } + +- private int repairPlayerItems(ServerPlayer player, int value) { +- 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 +- ); ++ // DivineMC start - Clump experience orbs ++ public Optional clumps$captureCurrentEntry(Optional entry) { ++ clumps$currentEntry = entry; ++ return entry; ++ } ++ ++ public java.util.Map clumps$getClumpedMap() { ++ if (clumps$clumpedMap == null) { ++ clumps$clumpedMap = new java.util.HashMap<>(); ++ clumps$clumpedMap.put(this.value, 1); ++ } ++ ++ return clumps$clumpedMap; ++ } ++ ++ public void clumps$setClumpedMap(java.util.Map map) { ++ clumps$clumpedMap = map; ++ clumps$resolve(); ++ } ++ ++ public boolean clumps$resolve() { ++ value = clumps$getClumpedMap().entrySet() ++ .stream() ++ .map(entry -> entry.getKey() * entry.getValue()) ++ .reduce(Integer::sum) ++ .orElse(1); ++ ++ return value > 0; ++ } ++ ++ private int repairPlayerItems(ServerPlayer player, int amount) { ++ Optional randomItemWith = clumps$captureCurrentEntry(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 (org.bxteam.divinemc.DivineConfig.clumpOrbs) { ++ return clumps$currentEntry ++ .map(foundItem -> { ++ ItemStack itemstack = foundItem.itemStack(); ++ int xpToRepair = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, (int) (amount * 1)); ++ int toRepair = Math.min(xpToRepair, itemstack.getDamageValue()); ++ itemstack.setDamageValue(itemstack.getDamageValue() - toRepair); ++ if(toRepair > 0) { ++ int used = amount - toRepair * amount / xpToRepair; ++ if(used > 0) { ++ return this.repairPlayerItems(player, used); ++ } ++ } ++ return 0; ++ }) ++ .orElse(amount); ++ } ++ // DivineMC end - Clump experience orbs + if (randomItemWith.isPresent()) { + ItemStack itemStack = randomItemWith.get().itemStack(); + int i = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemStack, value); diff --git a/divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch b/divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch new file mode 100644 index 0000000..07902e7 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0017-Optimize-explosions.patch @@ -0,0 +1,192 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 16:57:01 +0300 +Subject: [PATCH] Optimize explosions + + +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 278761647a6095581f8d8ff4f94ccc28b6e9c8a7..6d1a73c319e19dbc17122abb508aff462c4a56f4 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -377,6 +377,11 @@ public class ServerExplosion implements Explosion { + } + + private List calculateExplodedPositions() { ++ // DivineMC start - Optimize explosions ++ if (org.bxteam.divinemc.DivineConfig.enableFasterTntOptimization && !level.isClientSide && !(getIndirectSourceEntity() instanceof net.minecraft.world.entity.monster.breeze.Breeze)) { ++ return doExplosionA(this); ++ } ++ // DivineMC end - Optimize explosions + // Paper start - collision optimisations + final ObjectArrayList ret = new ObjectArrayList<>(); + +@@ -475,6 +480,157 @@ public class ServerExplosion implements Explosion { + // Paper end - collision optimisations + } + ++ // DivineMC start - Optimize explosions ++ private static final it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap> densityCache = new it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap<>(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap stateCache = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap fluidCache = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); ++ private static final BlockPos.MutableBlockPos posMutable = new BlockPos.MutableBlockPos(0, 0, 0); ++ private static final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet affectedBlockPositionsSet = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); ++ private static boolean firstRay; ++ private static boolean rayCalcDone; ++ ++ public static @org.jetbrains.annotations.NotNull List doExplosionA(ServerExplosion e) { ++ List toBlow; ++ ++ if (!org.bxteam.divinemc.DivineConfig.explosionNoBlockDamage && e.damageSource != null) { ++ rayCalcDone = false; ++ firstRay = true; ++ getAffectedPositionsOnPlaneY(e, 0, 0, 15, 0, 15); // bottom ++ getAffectedPositionsOnPlaneY(e, 15, 0, 15, 0, 15); // top ++ getAffectedPositionsOnPlaneX(e, 0, 1, 14, 0, 15); // west ++ getAffectedPositionsOnPlaneX(e, 15, 1, 14, 0, 15); // east ++ getAffectedPositionsOnPlaneZ(e, 0, 1, 14, 1, 14); // north ++ getAffectedPositionsOnPlaneZ(e, 15, 1, 14, 1, 14); // south ++ stateCache.clear(); ++ fluidCache.clear(); ++ ++ toBlow = new ArrayList<>(affectedBlockPositionsSet); ++ affectedBlockPositionsSet.clear(); ++ } else { ++ toBlow = java.util.Collections.emptyList(); ++ } ++ densityCache.clear(); ++ ++ return toBlow; ++ } ++ ++ private static void getAffectedPositionsOnPlaneX(Explosion e, int x, int yStart, int yEnd, int zStart, int zEnd) { ++ if (!rayCalcDone) { ++ final double xRel = (double) x / 15.0D * 2.0D - 1.0D; ++ ++ for (int z = zStart; z <= zEnd; ++z) { ++ double zRel = (double) z / 15.0D * 2.0D - 1.0D; ++ ++ for (int y = yStart; y <= yEnd; ++y) { ++ double yRel = (double) y / 15.0D * 2.0D - 1.0D; ++ ++ if (checkAffectedPosition((ServerExplosion) e, xRel, yRel, zRel)) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ private static void getAffectedPositionsOnPlaneY(Explosion e, int y, int xStart, int xEnd, int zStart, int zEnd) { ++ if (!rayCalcDone) { ++ final double yRel = (double) y / 15.0D * 2.0D - 1.0D; ++ ++ for (int z = zStart; z <= zEnd; ++z) { ++ double zRel = (double) z / 15.0D * 2.0D - 1.0D; ++ ++ for (int x = xStart; x <= xEnd; ++x) { ++ double xRel = (double) x / 15.0D * 2.0D - 1.0D; ++ ++ if (checkAffectedPosition((ServerExplosion) e, xRel, yRel, zRel)) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ private static void getAffectedPositionsOnPlaneZ(Explosion e, int z, int xStart, int xEnd, int yStart, int yEnd) { ++ if (!rayCalcDone) { ++ final double zRel = (double) z / 15.0D * 2.0D - 1.0D; ++ ++ for (int x = xStart; x <= xEnd; ++x) { ++ double xRel = (double) x / 15.0D * 2.0D - 1.0D; ++ ++ for (int y = yStart; y <= yEnd; ++y) { ++ double yRel = (double) y / 15.0D * 2.0D - 1.0D; ++ ++ if (checkAffectedPosition((ServerExplosion) e, xRel, yRel, zRel)) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ ++ private static boolean checkAffectedPosition(ServerExplosion e, double xRel, double yRel, double zRel) { ++ double len = Math.sqrt(xRel * xRel + yRel * yRel + zRel * zRel); ++ double xInc = (xRel / len) * 0.3; ++ double yInc = (yRel / len) * 0.3; ++ double zInc = (zRel / len) * 0.3; ++ float rand = e.level().random.nextFloat(); ++ float sizeRand = (org.bxteam.divinemc.DivineConfig.tntRandomRange >= 0 ? (float) org.bxteam.divinemc.DivineConfig.tntRandomRange : rand); ++ float size = e.radius() * (0.7F + sizeRand * 0.6F); ++ Vec3 vec3 = e.center(); ++ double posX = vec3.x; ++ double posY = vec3.y; ++ double posZ = vec3.z; ++ ++ for (float f1 = 0.3F; size > 0.0F; size -= 0.22500001F) { ++ posMutable.set(posX, posY, posZ); ++ ++ // Don't query already cached positions again from the world ++ BlockState state = stateCache.get(posMutable); ++ FluidState fluid = fluidCache.get(posMutable); ++ BlockPos posImmutable = null; ++ ++ if (state == null) { ++ posImmutable = posMutable.immutable(); ++ state = e.level().getBlockState(posImmutable); ++ stateCache.put(posImmutable, state); ++ fluid = e.level().getFluidState(posImmutable); ++ fluidCache.put(posImmutable, fluid); ++ } ++ ++ if (!state.isAir()) { ++ float resistance = Math.max(state.getBlock().getExplosionResistance(), fluid.getExplosionResistance()); ++ ++ if (e.source != null) { ++ resistance = e.source.getBlockExplosionResistance(e, e.level(), posMutable, state, fluid, resistance); ++ } ++ ++ size -= (resistance + 0.3F) * 0.3F; ++ } ++ ++ if (size > 0.0F) { ++ if ((e.source == null || e.source.shouldBlockExplode(e, e.level(), posMutable, state, size))) ++ affectedBlockPositionsSet.add(posImmutable != null ? posImmutable : posMutable.immutable()); ++ } else if (firstRay) { ++ rayCalcDone = true; ++ return true; ++ } ++ ++ firstRay = false; ++ ++ posX += xInc; ++ posY += yInc; ++ posZ += zInc; ++ } ++ ++ return false; ++ } ++ ++ private Optional noBlockCalcsWithNoBLockDamage(final ExplosionDamageCalculator instance, final Explosion explosion, final BlockGetter blockGetter, final BlockPos blockPos, final BlockState blockState, final FluidState fluidState) { ++ if (org.bxteam.divinemc.DivineConfig.explosionNoBlockDamage) return Optional.of(Blocks.BEDROCK.getExplosionResistance()); ++ return instance.getBlockExplosionResistance(explosion, blockGetter, blockPos, blockState, fluidState); ++ } ++ // DivineMC end - Optimize explosions ++ + private void hurtEntities() { + float f = this.radius * 2.0F; + int floor = Mth.floor(this.center.x - f - 1.0); +@@ -567,6 +723,11 @@ public class ServerExplosion implements Explosion { + } + + private void interactWithBlocks(List blocks) { ++ // DivineMC start - Optimize explosions ++ if (org.bxteam.divinemc.DivineConfig.explosionNoBlockDamage) { ++ blocks.clear(); ++ } ++ // DivineMC end - Optimize explosions + List list = new ArrayList<>(); + Util.shuffle(blocks, this.level.random); + diff --git a/divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch b/divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch new file mode 100644 index 0000000..dd618e6 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0018-Stop-teleporting-players-when-they-move-too-quickly.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 18:03:13 +0300 +Subject: [PATCH] Stop teleporting players when they move too quickly + + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index aabce23007006fe2dca1e4ac7c0657d2a1cae30e..f52e0ba1ef613895c2f21f39da852593d2f7883f 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1444,18 +1444,22 @@ public class ServerGamePacketListenerImpl + if (this.shouldCheckPlayerMovement(isFallFlying)) { + float f2 = isFallFlying ? 300.0F : 100.0F; + if (d7 - d6 > Math.max(f2, Mth.square(org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed))) { +- // CraftBukkit end +- // Paper start - Add fail move event +- io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, +- toX, toY, toZ, toYaw, toPitch, true); +- if (!event.isAllowed()) { +- if (event.getLogWarning()) { +- LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5); ++ // DivineMC start - Stop teleporting players when they move too quickly ++ if (!org.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement && !(org.bxteam.divinemc.DivineConfig.ignoreMovedTooQuicklyWhenLagging && player.serverLevel().getServer().lagging)) { ++ // CraftBukkit end ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ if (event.getLogWarning()) { ++ 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()); ++ return; + } +- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); +- return; ++ // Paper end - Add fail move event + } +- // Paper end - Add fail move event ++ // DivineMC end - Stop teleporting players when they move too quickly + } + } + } +@@ -1516,6 +1520,7 @@ public class ServerGamePacketListenerImpl + d7 = d3 * d3 + d4 * d4 + d5 * d5; + boolean movedWrongly = false; // Paper - Add fail move event; rename + if (!this.player.isChangingDimension() ++ && !org.bxteam.divinemc.DivineConfig.alwaysAllowWeirdMovement // DivineMC - Stop teleporting players when they move too quickly + && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold // Spigot + && !this.player.isSleeping() + && !this.player.gameMode.isCreative() diff --git a/divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch b/divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch new file mode 100644 index 0000000..6478d23 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0019-Lag-compensation.patch @@ -0,0 +1,331 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 18:38:26 +0300 +Subject: [PATCH] Lag compensation + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index ee45df82c3328d5cf91cb3e56786aec2d5263641..138de3fed3cc7a4dd0633dfdaf9c883f5f6fbd54 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -307,6 +307,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1577,6 +1578,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= this.portal.getPortalTransitionTime(level, entity); ++ return canChangeDimensions && lagCompensation(this.portalTime++, level) >= this.portal.getPortalTransitionTime(level, entity); // DivineMC - Lag compensation + } + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(int original, ServerLevel world) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.portalAcceleration) return original; ++ if (world.isClientSide()) return original; ++ ++ portalTime = portalTime + world.tpsCalculator.applicableMissedTicks(); ++ return portalTime; ++ } ++ // DivineMC end - Lag compensation ++ + @Nullable + public TeleportTransition getPortalDestination(ServerLevel level, Entity entity) { + return this.portal.getPortalDestination(level, entity, this.entryPosition); +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index 771b169fa360411bb313ae04c7dd55836875c611..e64ed6a23efbe89b8d3dd1e5a2a69ba4b7743369 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -153,8 +153,25 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + // Paper end - EAR 2 + ++ // DivineMC start - Lag compensation ++ private void lagCompensation() { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.pickupAcceleration) return; ++ if ((this).level().isClientSide()) return; ++ ++ if (pickupDelay == 0) return; ++ ++ if (pickupDelay - ((ServerLevel) this.level()).tpsCalculator.applicableMissedTicks() <= 0) { ++ pickupDelay = 0; ++ return; ++ } ++ ++ pickupDelay = pickupDelay - ((ServerLevel) this.level()).tpsCalculator.applicableMissedTicks(); ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public void tick() { ++ lagCompensation(); // DivineMC - Lag compensation + if (this.getItem().isEmpty()) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else { +diff --git a/net/minecraft/world/item/Item.java b/net/minecraft/world/item/Item.java +index 6821d39a24ef610ea8b04f6dbeca2cdc0b8e7787..a4d76e4aafb98b1bbc0e5a80d65cf0f9a3a53fd5 100644 +--- a/net/minecraft/world/item/Item.java ++++ b/net/minecraft/world/item/Item.java +@@ -258,9 +258,21 @@ public class Item implements FeatureElement, ItemLike { + return consumable != null ? consumable.animation() : ItemUseAnimation.NONE; + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(int original, net.minecraft.server.level.ServerLevel level) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.eatingAcceleration || original == 0) return original; ++ return org.bxteam.divinemc.util.tps.TPSUtil.tt20(original, true, level); ++ } ++ // DivineMC end - Lag compensation ++ + public int getUseDuration(ItemStack stack, LivingEntity entity) { + Consumable consumable = stack.get(DataComponents.CONSUMABLE); +- return consumable != null ? consumable.consumeTicks() : 0; ++ int original = consumable != null ? consumable.consumeTicks() : 0; ++ if (entity.level() instanceof net.minecraft.server.level.ServerLevel serverLevel) { ++ return lagCompensation(original, serverLevel); ++ } ++ ++ return original; + } + + public boolean releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeLeft) { +diff --git a/net/minecraft/world/level/GameRules.java b/net/minecraft/world/level/GameRules.java +index 02d64a5ea756b2c91a71b7a0fc0f21219983616a..d515ba4e775e1199e1cbf4f79978d318eaa6b336 100644 +--- a/net/minecraft/world/level/GameRules.java ++++ b/net/minecraft/world/level/GameRules.java +@@ -320,8 +320,31 @@ public class GameRules { + } + + public int getInt(GameRules.Key key) { +- return this.getRule(key).get(); ++ return lagCompensation(this.getRule(key).get(), key); // DivineMC - Lag compensation ++ } ++ ++ // DivineMC start - Lag compensation ++ private final java.util.concurrent.atomic.AtomicReference level = new java.util.concurrent.atomic.AtomicReference<>(); ++ ++ private int lagCompensation(int original, GameRules.Key rule) { ++ ServerLevel level = getOrCacheLevel(); ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.randomTickSpeedAcceleration) return original; ++ if (!(rule == GameRules.RULE_RANDOMTICKING)) return original; ++ return (int) (original * org.bxteam.divinemc.util.tps.TPSCalculator.MAX_TPS / (float) level.tpsCalculator.getMostAccurateTPS()); ++ } ++ ++ private ServerLevel getOrCacheLevel() { ++ if (level.get() == null) { ++ for (final ServerLevel level : MinecraftServer.getServer().getAllLevels()) { ++ if (level.getGameRules() == this) { ++ this.level.set(level); ++ break; ++ } ++ } ++ } ++ return level.get(); + } ++ // DivineMC end - Lag compensation + + public static class BooleanValue extends GameRules.Value { + private boolean value; +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index b631e35e965b1914cdeeddab8bd6bdbfd2465079..bb7dab597850fba8f0dff4461fc518e0a33b00c7 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -346,13 +346,21 @@ public abstract class BlockBehaviour implements FeatureElement { + protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + } + ++ // DivineMC start - Lag compensation ++ private float lagCompensation(float original, Player player) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.blockBreakingAcceleration) return original; ++ if (player.level().isClientSide) return original; ++ return original * org.bxteam.divinemc.util.tps.TPSCalculator.MAX_TPS / (float) ((ServerLevel) player.level()).tpsCalculator.getMostAccurateTPS(); ++ } ++ // DivineMC end - Lag compensation ++ + protected float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) { + float destroySpeed = state.getDestroySpeed(level, pos); + if (destroySpeed == -1.0F) { +- return 0.0F; ++ return lagCompensation(0.0F, player); // DivineMC - Lag compensation + } else { + int i = player.hasCorrectToolForDrops(state) ? 30 : 100; +- return player.getDestroySpeed(state) / destroySpeed / i; ++ return lagCompensation(player.getDestroySpeed(state) / destroySpeed / i, player); // DivineMC - Lag compensation + } + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 6167f72d1e374a7093f9880ab50e27eda603a680..88dc5f6e6668d802dc1a21bd894f8ddb1e568033 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -913,6 +913,19 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + this.ticker = ticker; + } + ++ // DivineMC start - Lag compensation ++ private void lagCompensation(Runnable original) { ++ original.run(); ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled) return; ++ if (!org.bxteam.divinemc.DivineConfig.blockEntityAcceleration) return; ++ if (LevelChunk.this.level.isClientSide()) return; ++ ++ for (int i = 0; i < ((ServerLevel) this.blockEntity.getLevel()).tpsCalculator.applicableMissedTicks(); i++) { ++ original.run(); ++ } ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public void tick() { + if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) { +@@ -923,7 +936,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + profilerFiller.push(this::getType); + BlockState blockState = LevelChunk.this.getBlockState(blockPos); + if (this.blockEntity.getType().isValid(blockState)) { +- this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity); ++ // DivineMC start - Lag compensation ++ lagCompensation(() -> { ++ this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity); ++ }); ++ // DivineMC end - Lag compensation + this.loggedInvalidBlockState = false; + // Paper start - Remove the Block Entity if it's invalid + } else { +diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java +index 35b5a33c79c883f28c99c992695b188524593b55..6845a1c3b6038700751312d8beb0f9a2844003a5 100644 +--- a/net/minecraft/world/level/material/LavaFluid.java ++++ b/net/minecraft/world/level/material/LavaFluid.java +@@ -175,9 +175,22 @@ public abstract class LavaFluid extends FlowingFluid { + return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER); + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(int original, ServerLevel level) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.fluidAcceleration) return original; ++ return org.bxteam.divinemc.util.tps.TPSUtil.tt20(original, true, level); ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public int getTickDelay(LevelReader level) { +- return level.dimensionType().ultraWarm() ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable ++ // DivineMC start - Lag compensation ++ int original = level.dimensionType().ultraWarm() ? level.getWorldBorder().world.purpurConfig.lavaSpeedNether : level.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - Make lava flow speed configurable ++ if (level instanceof ServerLevel serverLevel) { ++ return lagCompensation(original, serverLevel); ++ } ++ return original; ++ // DivineMC end - Lag compensation + } + + @Override +diff --git a/net/minecraft/world/level/material/WaterFluid.java b/net/minecraft/world/level/material/WaterFluid.java +index 2e4fed7c27910b6c886f710f33b0841c2a175837..89f22ebcbaf21df3afb6a00f60d8e00777875aac 100644 +--- a/net/minecraft/world/level/material/WaterFluid.java ++++ b/net/minecraft/world/level/material/WaterFluid.java +@@ -113,8 +113,16 @@ public abstract class WaterFluid extends FlowingFluid { + return 1; + } + ++ // DivineMC start - Lag compensation ++ private int lagCompensation(ServerLevel level) { ++ if (!org.bxteam.divinemc.DivineConfig.lagCompensationEnabled || !org.bxteam.divinemc.DivineConfig.fluidAcceleration) return 5; ++ return org.bxteam.divinemc.util.tps.TPSUtil.tt20(5, true, level); ++ } ++ // DivineMC end - Lag compensation ++ + @Override + public int getTickDelay(LevelReader level) { ++ if (level instanceof ServerLevel serverLevel) return lagCompensation(serverLevel); // DivineMC - Lag compensation + return 5; + } + diff --git a/divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch b/divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch new file mode 100644 index 0000000..0d714dc --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0020-MSPT-Tracking-for-each-world.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 18:55:59 +0300 +Subject: [PATCH] MSPT Tracking for each world + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 138de3fed3cc7a4dd0633dfdaf9c883f5f6fbd54..506f0469d1a9ee58de0e7a34a61a8092e451dcfd 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1778,7 +1778,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 1 Feb 2025 19:14:09 +0300 +Subject: [PATCH] Skip EntityScheduler's executeTick checks if there isn't any + tasks to be run + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 506f0469d1a9ee58de0e7a34a61a8092e451dcfd..b7815831ff1a2fa9aa52e96f1a50a5aa6823ff8a 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -308,6 +308,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1715,17 +1716,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) { +- if (entity.isRemoved()) { +- continue; +- } +- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); +- if (bukkit != null) { +- bukkit.taskScheduler.executeTick(); +- } ++ // DivineMC start - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ for (final net.minecraft.world.entity.Entity entity : entitiesWithScheduledTasks) { ++ if (entity.isRemoved()) { ++ continue; + } +- }); ++ ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } ++ // DivineMC end - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + // Paper end - Folia scheduler API + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + profilerFiller.push("commandFunctions"); diff --git a/divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch b/divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch new file mode 100644 index 0000000..26c3bd3 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0022-Carpet-Fixes-RecipeManager-Optimize.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:17:31 +0300 +Subject: [PATCH] Carpet-Fixes: RecipeManager Optimize + +Original project: https://github.com/fxmorin/carpet-fixes +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/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java +index aefaac550b58be479cc282f52dea91d4b1e530f6..2877a3229e03285e9ba5ec2bb68e17c9da202816 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 recipeType, I input, Level level) { + // CraftBukkit start +- List> list = this.recipes.getRecipesFor(recipeType, input, level).toList(); ++ List> list = this.recipes.getRecipesForList(recipeType, input, level); // DivineMC - 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/net/minecraft/world/item/crafting/RecipeMap.java b/net/minecraft/world/item/crafting/RecipeMap.java +index 098753ddd215b6ef5915fac71d8c4f0b19cf4142..1778e58dca9430756d59d07bf017ebe4cc1f4ed4 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)); + } ++ ++ // DivineMC start - Carpet-Fixes - Remove streams to be faster ++ public > java.util.List> getRecipesForList(RecipeType type, I input, Level world) { ++ java.util.List> list; ++ ++ if (input.isEmpty()) { ++ return java.util.List.of(); ++ } else { ++ list = new java.util.ArrayList<>(); ++ } ++ ++ for (RecipeHolder recipeholder : this.byType(type)) { ++ if (recipeholder.value().matches(input, world)) { ++ list.add(recipeholder); ++ } ++ } ++ ++ return list; ++ } ++ // DivineMC end - Carpet-Fixes - Remove streams to be faster + } diff --git a/divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch b/divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch new file mode 100644 index 0000000..22cdda0 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0023-Snowball-and-Egg-knockback.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:24:47 +0300 +Subject: [PATCH] Snowball and Egg knockback + + +diff --git a/net/minecraft/world/entity/projectile/Snowball.java b/net/minecraft/world/entity/projectile/Snowball.java +index 1d399532c67c213c95c06837b0c7855384f1a25c..cad1f8cb68ef9615587e651a3120f68a3c32add0 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); ++ // DivineMC start - Make snowball can knockback player ++ if (this.level().divineConfig.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()); ++ } ++ // DivineMC end - 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..8f0aa83cc81f36d70a39600a82d0212db70e02ec 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(); // DivineMC - make egg can knockback player + result.getEntity().hurt(this.damageSources().thrown(this, this.getOwner()), 0.0F); ++ // DivineMC start - Make egg can knockback player ++ if (this.level().divineConfig.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()); ++ } ++ // DivineMC end - Make egg can knockback player + } + + @Override diff --git a/divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch b/divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch new file mode 100644 index 0000000..cb5aeef --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0024-Block-Log4Shell-exploit.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:28:34 +0300 +Subject: [PATCH] Block Log4Shell exploit + + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f52e0ba1ef613895c2f21f39da852593d2f7883f..9e4ea539afcd07294bdc5018f479e496ee011451 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2409,6 +2409,7 @@ public class ServerGamePacketListenerImpl + } + + private void tryHandleChat(String message, Runnable handler, boolean sync) { // CraftBukkit ++ if (ServerGamePacketListenerImpl.isLog4ShellExploit(message)) return; // DivineMC - Block Log4Shell exploit + if (isChatMessageIllegal(message)) { + this.disconnectAsync(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add proper async disconnect + } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales +@@ -2437,6 +2438,15 @@ public class ServerGamePacketListenerImpl + } + } + ++ // DivineMC start - Block Log4Shell exploit ++ public static boolean isLog4ShellExploit(String message) { ++ java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(".*\\$\\{[^}]*}.*"); ++ java.util.regex.Matcher matcher = pattern.matcher(message); ++ ++ return matcher.find(); ++ } ++ // DivineMC end - Block Log4Shell exploit ++ + public static boolean isChatMessageIllegal(String message) { + for (int i = 0; i < message.length(); i++) { + if (!StringUtil.isAllowedChatCharacter(message.charAt(i))) { diff --git a/divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch b/divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch new file mode 100644 index 0000000..6310c67 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0025-Re-Fix-MC-117075.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:43:42 +0300 +Subject: [PATCH] Re-Fix MC-117075 + + +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 8f37c27bba829733fb8db5f35470092a76c83e98..595ebb40f47dd55127c630813813d21d8a1274cd 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -115,7 +115,7 @@ 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 org.bxteam.divinemc.util.BlockEntityTickersList blockEntityTickers = new org.bxteam.divinemc.util.BlockEntityTickersList(); // Paper - public // DivineMC - optimize block entity removals - Fix MC-117075 + protected final NeighborUpdater neighborUpdater; + private final List pendingBlockEntityTickers = Lists.newArrayList(); + private boolean tickingBlockEntities; +@@ -1527,7 +1527,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); + // Spigot end + if (tickingBlockEntity.isRemoved()) { +- toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll ++ this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // DivineMC - optimize block entity removals - Fix MC-117075 + } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { + tickingBlockEntity.tick(); + // Paper start - rewrite chunk system +@@ -1539,6 +1539,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 + ++ this.blockEntityTickers.removeMarkedEntries(); // DivineMC - optimize block entity removals - Fix MC-117075 + this.tickingBlockEntities = false; + profilerFiller.pop(); + this.spigotConfig.currentPrimedTnt = 0; // Spigot diff --git a/divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch b/divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch new file mode 100644 index 0000000..4aaea74 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0026-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:50:02 +0300 +Subject: [PATCH] Skip distanceToSqr call in ServerEntity#sendChanges if the + delta movement hasn't changed + + +diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java +index 3ee43ca5c49af83a067c7ffe74d3f2bc6e4a6c9e..a03dced5231ca47abf5919e3eca358c16a25337b 100644 +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -200,23 +200,27 @@ public class ServerEntity { + + if (this.entity.hasImpulse || this.trackDelta || this.entity instanceof LivingEntity && ((LivingEntity)this.entity).isFallFlying()) { + Vec3 deltaMovement = this.entity.getDeltaMovement(); +- double d = deltaMovement.distanceToSqr(this.lastSentMovement); +- if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) { +- this.lastSentMovement = deltaMovement; +- if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) { +- this.broadcast +- .accept( +- new ClientboundBundlePacket( +- List.of( +- new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement), +- new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower) ++ // DivineMC start - Skip "distanceToSqr" call in "ServerEntity#sendChanges" if the delta movement hasn't changed ++ if (deltaMovement != this.lastSentMovement) { ++ double d = deltaMovement.distanceToSqr(this.lastSentMovement); ++ if (d > 1.0E-7 || d > 0.0 && deltaMovement.lengthSqr() == 0.0) { ++ this.lastSentMovement = deltaMovement; ++ if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) { ++ this.broadcast ++ .accept( ++ new ClientboundBundlePacket( ++ List.of( ++ new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement), ++ new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower) ++ ) + ) +- ) +- ); +- } else { +- this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement)); ++ ); ++ } else { ++ this.broadcast.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement)); ++ } + } + } ++ // DivineMC end - Skip "distanceToSqr" call in "ServerEntity#sendChanges" if the delta movement hasn't changed + } + + if (packet != null) { diff --git a/divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch b/divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch new file mode 100644 index 0000000..5f4c2bd --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0027-Optimize-canSee-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:52:39 +0300 +Subject: [PATCH] Optimize canSee checks + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index e3427e7aecfc4e1fafb38316824aa1ee50c9901a..9bca28f33aa3b3cb8964c06b2f4d2f0a591e81f5 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -1317,7 +1317,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 // DivineMC - optimize canSee checks + flag = false; + } + // CraftBukkit end diff --git a/divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch b/divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch new file mode 100644 index 0000000..f889048 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0028-Implement-NoChatReports.patch @@ -0,0 +1,309 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 4 Feb 2025 01:49:17 +0300 +Subject: [PATCH] Implement NoChatReports + + +diff --git a/net/minecraft/network/FriendlyByteBuf.java b/net/minecraft/network/FriendlyByteBuf.java +index e5e5d9bc095ccd9fbf1c8aaa09e5c4ebb1d1c920..af94dd26b553b5e64f305135328aa30b6fae6b0b 100644 +--- a/net/minecraft/network/FriendlyByteBuf.java ++++ b/net/minecraft/network/FriendlyByteBuf.java +@@ -101,7 +101,28 @@ public class FriendlyByteBuf extends ByteBuf { + return this; + } + ++ @SuppressWarnings({"unchecked", "rawtypes"}) // DivineMC - Implement NoChatReports + public T readJsonWithCodec(Codec codec) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ if (codec == net.minecraft.network.protocol.status.ServerStatus.CODEC) { ++ JsonElement jsonElement = GsonHelper.fromJson(GSON, this.readUtf(), JsonElement.class); ++ DataResult dataResult = codec.parse(JsonOps.INSTANCE, jsonElement); ++ Object result; ++ try { ++ result = dataResult.getOrThrow(string -> new DecoderException("Failed to decode json: " + string)); ++ } catch (Throwable e) { ++ throw new RuntimeException("Unable to decode json!", e); ++ } ++ ++ if (jsonElement.getAsJsonObject().has("preventsChatReports")) { ++ ((net.minecraft.network.protocol.status.ServerStatus) result).setPreventsChatReports(jsonElement.getAsJsonObject().get("preventsChatReports").getAsBoolean()); ++ } ++ ++ return (T) (result); ++ } ++ } ++ // DivineMC end - Implement NoChatReports + JsonElement jsonElement = GsonHelper.fromJson(GSON, this.readUtf(), JsonElement.class); + DataResult dataResult = codec.parse(JsonOps.INSTANCE, jsonElement); + return dataResult.getOrThrow(exception -> new DecoderException("Failed to decode json: " + exception)); +@@ -113,6 +134,19 @@ public class FriendlyByteBuf extends ByteBuf { + } + public void writeJsonWithCodec(Codec codec, T value, int maxLength) { + // Paper end - Adventure; add max length parameter ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsAddQueryData && codec == net.minecraft.network.protocol.status.ServerStatus.CODEC) { ++ DataResult dataResult = codec.encodeStart(JsonOps.INSTANCE, value); ++ JsonElement element = dataResult.getOrThrow(string -> new EncoderException("Failed to encode: " + string + " " + value)); ++ ++ element.getAsJsonObject().addProperty("preventsChatReports", true); ++ ++ this.writeUtf(GSON.toJson(element)); ++ return; ++ } ++ } ++ // DivineMC end - Implement NoChatReports + DataResult dataResult = codec.encodeStart(JsonOps.INSTANCE, value); + 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/ServerboundChatCommandSignedPacket.java b/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java +index 07943553b562b95076bdce232d6f0796f469400f..61ecf4c6ae37b13ed42dff8d4165d32f3a5cc0c9 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatCommandSignedPacket.java +@@ -36,4 +36,15 @@ public record ServerboundChatCommandSignedPacket( + public void handle(ServerGamePacketListener handler) { + handler.handleSignedChatCommand(this); + } ++ ++ // DivineMC start - Implement NoChatReports ++ @Override ++ public ArgumentSignatures argumentSignatures() { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ return ArgumentSignatures.EMPTY; ++ } ++ ++ return argumentSignatures; ++ } ++ // DivineMC end - Implement NoChatReports + } +diff --git a/net/minecraft/network/protocol/game/ServerboundChatPacket.java b/net/minecraft/network/protocol/game/ServerboundChatPacket.java +index b5afc05924ae899e020c303c8b86398e1d4ab8a0..3af0436ac2dff04cfaa1b3dda11a5417f2c0890c 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatPacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatPacket.java +@@ -36,4 +36,16 @@ public record ServerboundChatPacket(String message, Instant timeStamp, long salt + public void handle(ServerGamePacketListener handler) { + handler.handleChat(this); + } ++ ++ // DivineMC start - Implement NoChatReports ++ @Override ++ @Nullable ++ public MessageSignature signature() { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ return null; ++ } ++ ++ return signature; ++ } ++ // DivineMC end - Implement NoChatReports + } +diff --git a/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java b/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java +index 1df628ac0b414511aaed6e09d78f884c4170f730..d94858facc06d57139e953796ee09dad17648dbb 100644 +--- a/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java ++++ b/net/minecraft/network/protocol/game/ServerboundChatSessionUpdatePacket.java +@@ -26,6 +26,19 @@ public record ServerboundChatSessionUpdatePacket(RemoteChatSession.Data chatSess + + @Override + public void handle(ServerGamePacketListener handler) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ var impl = (net.minecraft.server.network.ServerGamePacketListenerImpl) handler; ++ ++ if (!impl.getPlayer().getServer().isSingleplayerOwner(impl.getPlayer().getGameProfile())) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsDemandOnClient) { ++ impl.disconnect(net.minecraft.network.chat.Component.literal(org.bxteam.divinemc.DivineConfig.noChatReportsDisconnectDemandOnClientMessage)); ++ } ++ } ++ ++ return; ++ } ++ // DivineMC end - Implement NoChatReports + handler.handleChatSessionUpdate(this); + } + } +diff --git a/net/minecraft/network/protocol/status/ServerStatus.java b/net/minecraft/network/protocol/status/ServerStatus.java +index 30bd254542d631676494f349ff3f44f52d54ab2f..6c728ae3b58bc1b8449d34c6c74091612b79f39e 100644 +--- a/net/minecraft/network/protocol/status/ServerStatus.java ++++ b/net/minecraft/network/protocol/status/ServerStatus.java +@@ -15,13 +15,7 @@ import net.minecraft.network.chat.CommonComponents; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.ComponentSerialization; + +-public record ServerStatus( +- Component description, +- Optional players, +- Optional version, +- Optional favicon, +- boolean enforcesSecureChat +-) { ++public final class ServerStatus { + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance.group( + ComponentSerialization.CODEC.lenientOptionalFieldOf("description", CommonComponents.EMPTY).forGetter(ServerStatus::description), +@@ -33,6 +27,63 @@ public record ServerStatus( + .apply(instance, ServerStatus::new) + ); + ++ // DivineMC start - Implement NoChatReports - convert to class ++ private final Component description; ++ private final Optional players; ++ private final Optional version; ++ private final Optional favicon; ++ private final boolean enforcesSecureChat; ++ private boolean preventsChatReports; ++ ++ public ServerStatus( ++ Component description, ++ Optional players, ++ Optional version, ++ Optional favicon, ++ boolean enforcesSecureChat ++ ) { ++ this.description = description; ++ this.players = players; ++ this.version = version; ++ this.favicon = favicon; ++ this.enforcesSecureChat = enforcesSecureChat; ++ } ++ ++ public Component description() { ++ return description; ++ } ++ ++ public Optional players() { ++ return players; ++ } ++ ++ public Optional version() { ++ return version; ++ } ++ ++ public Optional favicon() { ++ return favicon; ++ } ++ ++ public boolean enforcesSecureChat() { ++ return enforcesSecureChat; ++ } ++ ++ public boolean preventsChatReports() { ++ var self = (ServerStatus) (Object) this; ++ ++ if (self.version().isPresent() && self.version().get().protocol() < 759 ++ && self.version().get().protocol() > 0) ++ return true; ++ ++ return this.preventsChatReports; ++ } ++ ++ public void setPreventsChatReports(boolean prevents) { ++ this.preventsChatReports = prevents; ++ } ++ // DivineMC end - Implement NoChatReports ++ + public record Favicon(byte[] iconBytes) { + private static final String PREFIX = "data:image/png;base64,"; + public static final Codec CODEC = Codec.STRING.comapFlatMap(string -> { +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 697f690305db56ae5a05483aae37994d4e8f9f83..dc6417943ec339326684323c3ec3d132d55be354 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -668,6 +668,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public boolean enforceSecureProfile() { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) return false; // DivineMC - Implement NoChatReports + 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 398c1733824b689520170de0be94006731afa5cd..072e9b8810a6ccc292f1eb5ffe02355ab4719c5e 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -312,10 +312,64 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + public void send(Packet packet) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ Object self = this; ++ boolean cancel = false; ++ ++ if (self instanceof ServerGamePacketListenerImpl listener) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsDebugLog && packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { ++ MinecraftServer.LOGGER.info("Sending message: {}", chat.unsignedContent() != null ? chat.unsignedContent() ++ : chat.body().content()); ++ } ++ ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsConvertToGameMessage) { ++ 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); ++ ++ cancel = true; ++ listener.send(packet); ++ } ++ } ++ } ++ ++ if (cancel) { ++ return; ++ } ++ } ++ // DivineMC end - Implement NoChatReports + this.send(packet, null); + } + + public void send(Packet packet, @Nullable PacketSendListener listener) { ++ // DivineMC start - Implement NoChatReports ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) { ++ Object self = this; ++ boolean cancel = false; ++ ++ if (self instanceof ServerGamePacketListenerImpl listenerImpl) { ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsDebugLog && packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat) { ++ MinecraftServer.LOGGER.info("Sending message: {}", chat.unsignedContent() != null ? chat.unsignedContent() ++ : chat.body().content()); ++ } ++ ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsConvertToGameMessage) { ++ if (packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket chat && listener != null) { ++ cancel = true; ++ listenerImpl.send(chat); ++ } ++ } ++ ++ } ++ ++ if (cancel) { ++ return; ++ } ++ } ++ // DivineMC end - Implement NoChatReports + // 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 6b23cf5122fe65b2ad253ed8536658441297e953..e8c0a87cec53788573748b5e900370bb968e3fab 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -274,7 +274,7 @@ public abstract class PlayerList { + !_boolean, + _boolean2, + player.createCommonSpawnInfo(serverLevel), +- this.server.enforceSecureProfile() ++ org.bxteam.divinemc.DivineConfig.noChatReportsEnabled || this.server.enforceSecureProfile() // DivineMC - Implement NoChatReports + ) + ); + player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit +@@ -1328,6 +1328,7 @@ public abstract class PlayerList { + } + + public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public ++ if (org.bxteam.divinemc.DivineConfig.noChatReportsEnabled) return true; // DivineMC - Implement NoChatReports + return message.hasSignature() && !message.hasExpiredServer(Instant.now()); + } + diff --git a/divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch b/divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch new file mode 100644 index 0000000..5a6a5d6 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0029-Optimize-Structure-Generation.patch @@ -0,0 +1,275 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 4 Feb 2025 19:52:24 +0300 +Subject: [PATCH] Optimize Structure Generation + + +diff --git a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +index 63143ceec98f7a84ec4064d05e8f88c11200172f..02f67af9c312f3d9213af1e410986165dcf618a4 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/JigsawPlacement.java +@@ -4,6 +4,8 @@ import com.google.common.collect.Lists; + import com.mojang.logging.LogUtils; + import java.util.List; + import java.util.Optional; ++import java.util.ArrayList; ++import java.util.LinkedHashSet; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Holder; +@@ -292,6 +294,108 @@ public class JigsawPlacement { + this.random = random; + } + ++ // DivineMC start - Optimize Structure Generation ++ private boolean structureLayoutOptimizer$optimizeJigsawConnecting(StructureTemplate.JigsawBlockInfo jigsaw1, StructureTemplate.JigsawBlockInfo jigsaw2) { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ return JigsawBlock.canAttach(jigsaw1, jigsaw2); ++ } ++ return org.bxteam.divinemc.util.structure.GeneralUtils.canJigsawsAttach(jigsaw1, jigsaw2); ++ } ++ ++ private void structureLayoutOptimizer$replaceVoxelShape3(MutableObject instance, BoundingBox pieceBounds) { ++ org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape = new org.bxteam.divinemc.util.structure.TrojanVoxelShape(new org.bxteam.divinemc.util.structure.BoxOctree(AABB.of(pieceBounds))); ++ instance.setValue(trojanVoxelShape); ++ } ++ ++ private void structureLayoutOptimizer$replaceVoxelShape4(MutableObject instance, BoundingBox pieceBounds) { ++ if (instance.getValue() instanceof org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape) { ++ trojanVoxelShape.boxOctree.addBox(AABB.of(pieceBounds)); ++ } ++ } ++ ++ private List structureLayoutOptimizer$removeDuplicateTemplatePoolElementLists(StructureTemplatePool instance, RandomSource random) { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer || !org.bxteam.divinemc.DivineConfig.deduplicateShuffledTemplatePoolElementList) { ++ return instance.getShuffledTemplates(random); ++ } ++ ++ // Linked hashset keeps order of elements. ++ LinkedHashSet uniquePieces = new LinkedHashSet<>((instance).rawTemplates.size()); ++ ++ // Don't use addAll. Want to keep it simple in case of inefficiency in collection's addAll. ++ // Set will ignore duplicates after first appearance of an element. ++ for (StructurePoolElement piece : instance.getShuffledTemplates(random)) { ++ //noinspection UseBulkOperation ++ uniquePieces.add(piece); ++ } ++ ++ // Move the elements from set to the list in the same order. ++ int uniquePiecesFound = uniquePieces.size(); ++ List deduplicatedListOfPieces = new ArrayList<>(uniquePiecesFound); ++ for (int i = 0; i < uniquePiecesFound; i++) { ++ deduplicatedListOfPieces.add(uniquePieces.removeFirst()); ++ } ++ ++ return deduplicatedListOfPieces; ++ } ++ ++ private ArrayList structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists1() { ++ // Swap with trojan list, so we can record what pieces we visited ++ return org.bxteam.divinemc.DivineConfig.deduplicateShuffledTemplatePoolElementList ? Lists.newArrayList() : new org.bxteam.divinemc.util.structure.TrojanArrayList<>(); ++ } ++ ++ private List structureLayoutOptimizer$skipBlockedJigsaws( ++ List original, ++ boolean useExpansionHack, ++ MutableObject voxelShapeMutableObject, ++ StructurePoolElement structurePoolElement, ++ StructureTemplate.StructureBlockInfo parentJigsawBlockInfo, ++ BlockPos parentTargetPosition) ++ { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ return original; ++ } ++ if (voxelShapeMutableObject.getValue() instanceof org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape) { ++ // If rigid and target position is already an invalid spot, do not run rest of logic. ++ StructureTemplatePool.Projection candidatePlacementBehavior = structurePoolElement.getProjection(); ++ boolean isCandidateRigid = candidatePlacementBehavior == StructureTemplatePool.Projection.RIGID; ++ if (isCandidateRigid && (!trojanVoxelShape.boxOctree.boundaryContains(parentTargetPosition) || trojanVoxelShape.boxOctree.withinAnyBox(parentTargetPosition))) { ++ return new ArrayList<>(); ++ } ++ } ++ return original; ++ } ++ ++ private List structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists2(List original, ++ List list, ++ StructurePoolElement structurepoolelement1) ++ { ++ if (!org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ return original; ++ } ++ if (!org.bxteam.divinemc.DivineConfig.deduplicateShuffledTemplatePoolElementList && list instanceof org.bxteam.divinemc.util.structure.TrojanArrayList trojanArrayList) { ++ // Do not run this piece's logic since we already checked its 4 rotations in the past. ++ if (trojanArrayList.elementsAlreadyParsed.contains(structurepoolelement1)) { ++ ++ // Prime the random with the random calls we would've skipped. ++ // Maintains vanilla compat. ++ for (Rotation rotation1 : original) { ++ structurepoolelement1.getShuffledJigsawBlocks(this.structureTemplateManager, BlockPos.ZERO, rotation1, this.random); ++ } ++ ++ // Short circuit the Rotation loop ++ return new ArrayList<>(); ++ } ++ // Record piece as it will go through the 4 rotation checks for spawning. ++ else { ++ trojanArrayList.elementsAlreadyParsed.add(structurepoolelement1); ++ } ++ } ++ ++ // Allow the vanilla code to run normally. ++ return original; ++ } ++ // DivineMC end - Optimize Structure Generation ++ + void tryPlacingChildren( + PoolElementStructurePiece piece, + MutableObject free, +@@ -349,9 +453,9 @@ public class JigsawPlacement { + mutableObject1 = free; + } + +- List list = Lists.newArrayList(); ++ List list = structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists1(); // DivineMC - Optimize Structure Generation + if (depth != this.maxDepth) { +- list.addAll(holder.value().getShuffledTemplates(this.random)); ++ list.addAll(structureLayoutOptimizer$removeDuplicateTemplatePoolElementLists(holder.value(), this.random)); // DivineMC - Optimize Structure Generation + } + + list.addAll(fallback.value().getShuffledTemplates(this.random)); +@@ -362,10 +466,14 @@ public class JigsawPlacement { + break; + } + +- for (Rotation rotation1 : Rotation.getShuffled(this.random)) { +- List shuffledJigsawBlocks = structurePoolElement.getShuffledJigsawBlocks( ++ // DivineMC start - Optimize Structure Generation ++ for (Rotation rotation1 : structureLayoutOptimizer$skipDuplicateTemplatePoolElementLists2(Rotation.getShuffled(this.random), list, structurePoolElement)) { ++ List shuffledJigsawBlocks = structureLayoutOptimizer$skipBlockedJigsaws( ++ structurePoolElement.getShuffledJigsawBlocks( + this.structureTemplateManager, BlockPos.ZERO, rotation1, this.random ++ ), useExpansionHack, mutableObject1, structurePoolElement, structureBlockInfo, blockPos1 + ); ++ // DivineMC end - Optimize Structure Generation + BoundingBox boundingBox1 = structurePoolElement.getBoundingBox(this.structureTemplateManager, BlockPos.ZERO, rotation1); + int i2; + if (useExpansionHack && boundingBox1.getYSpan() <= 16) { +@@ -398,7 +506,7 @@ public class JigsawPlacement { + } + + for (StructureTemplate.JigsawBlockInfo jigsawBlockInfo1 : shuffledJigsawBlocks) { +- if (JigsawBlock.canAttach(jigsawBlockInfo, jigsawBlockInfo1)) { ++ if (structureLayoutOptimizer$optimizeJigsawConnecting(jigsawBlockInfo, jigsawBlockInfo1)) { // DivineMC - Optimize Structure Generation + BlockPos blockPos2 = jigsawBlockInfo1.info().pos(); + BlockPos blockPos3 = blockPos1.subtract(blockPos2); + BoundingBox boundingBox2 = structurePoolElement.getBoundingBox(this.structureTemplateManager, blockPos3, rotation1); +@@ -427,9 +535,26 @@ public class JigsawPlacement { + boundingBox3.encapsulate(new BlockPos(boundingBox3.minX(), boundingBox3.minY() + max, boundingBox3.minZ())); + } + +- if (!Shapes.joinIsNotEmpty( +- mutableObject1.getValue(), Shapes.create(AABB.of(boundingBox3).deflate(0.25)), BooleanOp.ONLY_SECOND +- )) { ++ // DivineMC start - Optimize Structure Generation ++ boolean internal$joinIsNotEmpty; ++ VoxelShape parentBounds = mutableObject1.getValue(); ++ java.util.function.Supplier original = () -> Shapes.joinIsNotEmpty( ++ parentBounds, Shapes.create(AABB.of(boundingBox3).deflate(0.25)), BooleanOp.ONLY_SECOND ++ ); ++ if (org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ if (parentBounds instanceof org.bxteam.divinemc.util.structure.TrojanVoxelShape trojanVoxelShape) { ++ AABB pieceAABB = AABB.of(boundingBox3).deflate(0.25D); ++ ++ // Have to inverse because of an ! outside our wrap ++ internal$joinIsNotEmpty = !trojanVoxelShape.boxOctree.withinBoundsButNotIntersectingChildren(pieceAABB); ++ } else { ++ internal$joinIsNotEmpty = original.get(); ++ } ++ } else { ++ internal$joinIsNotEmpty = original.get(); ++ } ++ if (!internal$joinIsNotEmpty) { ++ // DivineMC end - Optimize Structure Generation + mutableObject1.setValue( + Shapes.joinUnoptimized( + mutableObject1.getValue(), Shapes.create(AABB.of(boundingBox3)), BooleanOp.ONLY_FIRST +diff --git a/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java b/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java +index 4a6da3648c513a6cce16cf71246937d2d0ad014d..6af4c9026e814dee1ed4c7593ad00b5f8af9b979 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement.java +@@ -119,8 +119,16 @@ public class SinglePoolElement extends StructurePoolElement { + StructureTemplateManager structureTemplateManager, BlockPos pos, Rotation rotation, RandomSource random + ) { + List jigsaws = this.getTemplate(structureTemplateManager).getJigsaws(pos, rotation); +- Util.shuffle(jigsaws, random); +- sortBySelectionPriority(jigsaws); ++ // DivineMC start - Optimize Structure Generation ++ if (org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer) { ++ structureLayoutOptimizer$fasterJigsawListShuffling1(jigsaws, random); ++ structureLayoutOptimizer$fasterJigsawListShuffling2(jigsaws); ++ } else { ++ Util.shuffle(jigsaws, random); ++ sortBySelectionPriority(jigsaws); ++ } ++ // DivineMC end - Optimize Structure Generation ++ + return jigsaws; + } + +@@ -191,4 +199,12 @@ public class SinglePoolElement extends StructurePoolElement { + public String toString() { + return "Single[" + this.template + "]"; + } ++ ++ // DivineMC start - Optimize Structure Generation ++ private void structureLayoutOptimizer$fasterJigsawListShuffling1(List list, RandomSource randomSource) { ++ org.bxteam.divinemc.util.structure.GeneralUtils.shuffleAndPrioritize(list, randomSource); ++ } ++ ++ private void structureLayoutOptimizer$fasterJigsawListShuffling2(List structureBlockInfos) { } ++ // Quiil end - Optimize Structure Generation + } +diff --git a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index ab1dcbe416e2c3c94cfddf04b7ed053425a71806..985d11f0b72858d66ad011d83106730b07e25242 100644 +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -249,6 +249,12 @@ public class StructureTemplate { + return transform(pos, decorator.getMirror(), decorator.getRotation(), decorator.getRotationPivot()); + } + ++ // DivineMC start - Optimize Structure Generation ++ private List structureLayoutOptimizer$shrinkStructureTemplateBlocksList(StructureTemplate.Palette palette, BlockPos offset, StructurePlaceSettings settings) { ++ return org.bxteam.divinemc.util.structure.StructureTemplateOptimizer.getStructureBlockInfosInBounds(palette, offset, settings); ++ } ++ // DivineMC end - Optimize Structure Generation ++ + public boolean placeInWorld(ServerLevelAccessor serverLevel, BlockPos offset, BlockPos pos, StructurePlaceSettings settings, RandomSource random, int flags) { + if (this.palettes.isEmpty()) { + return false; +@@ -266,7 +272,11 @@ public class StructureTemplate { + } + } + // CraftBukkit end +- List list = settings.getRandomPalette(this.palettes, offset).blocks(); ++ // DivineMC start - Optimize Structure Generation ++ List list = org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer ++ ? structureLayoutOptimizer$shrinkStructureTemplateBlocksList(settings.getRandomPalette(this.palettes, offset), offset, settings) ++ : settings.getRandomPalette(this.palettes, offset).blocks(); ++ // DivineMC end - Optimize Structure Generation + if ((!list.isEmpty() || !settings.isIgnoreEntities() && !this.entityInfoList.isEmpty()) + && this.size.getX() >= 1 + && this.size.getY() >= 1 +@@ -890,7 +900,11 @@ public class StructureTemplate { + private List cachedJigsaws; + + Palette(List blocks) { +- this.blocks = blocks; ++ // DivineMC start - Optimize Structure Generation ++ this.blocks = org.bxteam.divinemc.DivineConfig.enableStructureLayoutOptimizer ++ ? new org.bxteam.divinemc.util.structure.PalettedStructureBlockInfoList(blocks) ++ : blocks; ++ // DivineMC end - Optimize Structure Generation + } + + public List jigsaws() { diff --git a/divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch b/divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch new file mode 100644 index 0000000..7ce0984 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0030-Verify-Minecraft-EULA-earlier.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 5 Feb 2025 17:48:56 +0300 +Subject: [PATCH] Verify Minecraft EULA earlier + + +diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java +index 680369af59fd2aa36bf1cf4e28b598854383abe3..d415a175ea1e7b5a5bf1149187247dd7b2619c29 100644 +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -143,7 +143,6 @@ public class Main { + dedicatedServerSettings.forceSave(); + RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression); + Path path2 = Paths.get("eula.txt"); +- Eula eula = new Eula(path2); + // Paper start - load config files early for access below if needed + org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("bukkit-settings")); + org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("spigot-settings")); +@@ -166,19 +165,6 @@ public class Main { + return; + } + +- // Spigot start +- boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree"); +- if (eulaAgreed) { +- LOGGER.error("You have used the Spigot command line EULA agreement flag."); +- LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA)."); +- LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately."); +- } +- if (!eula.hasAgreedToEULA() && !eulaAgreed) { +- // Spigot end +- LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); +- return; +- } +- + // Paper start - Detect headless JRE + String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); + if (awtException != null) { diff --git a/divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch b/divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch new file mode 100644 index 0000000..82848f2 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0031-Density-Function-Compiler.patch @@ -0,0 +1,1205 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 12 Feb 2025 01:05:50 +0300 +Subject: [PATCH] Density Function Compiler + +Implements density function compiler to accelerate world generation from C2ME + +Density function: https://minecraft.wiki/w/Density_function + +This functionality compiles density functions from world generation +datapacks (including vanilla generation) to JVM bytecode to increase +performance by allowing JVM JIT to better optimize the code. +All functions provided by vanilla are implemented. + +Not all server will benefit performance from this feature, as it +can sometimes slow down chunk performance than speed it up + +diff --git a/net/minecraft/util/CubicSpline.java b/net/minecraft/util/CubicSpline.java +index f36f8f2d49d4eba5c80eb243883749d6f831eb8a..b43b7e242ea0a4f87704853c03201144ce355565 100644 +--- a/net/minecraft/util/CubicSpline.java ++++ b/net/minecraft/util/CubicSpline.java +@@ -254,31 +254,47 @@ public interface CubicSpline> extends ToFloatFun + + @Override + public float apply(C object) { +- float f = this.coordinate.apply(object); +- int i = findIntervalStart(this.locations, f); +- int i1 = this.locations.length - 1; +- if (i < 0) { +- return linearExtend(f, this.locations, this.values.get(0).apply(object), this.derivatives, 0); +- } else if (i == i1) { +- return linearExtend(f, this.locations, this.values.get(i1).apply(object), this.derivatives, i1); ++ // DivineMC start - Density Function Compiler ++ float point = this.coordinate.apply(object); ++ int rangeForLocation = findIntervalStart(this.locations, point); ++ int last = this.locations.length - 1; ++ if (rangeForLocation < 0) { ++ return linearExtend(point, this.locations, this.values.get(0).apply(object), this.derivatives, 0); ++ } else if (rangeForLocation == last) { ++ return linearExtend(point, this.locations, this.values.get(last).apply(object), this.derivatives, last); + } else { +- float f1 = this.locations[i]; +- float f2 = this.locations[i + 1]; +- float f3 = (f - f1) / (f2 - f1); +- ToFloatFunction toFloatFunction = (ToFloatFunction)this.values.get(i); +- ToFloatFunction toFloatFunction1 = (ToFloatFunction)this.values.get(i + 1); +- float f4 = this.derivatives[i]; +- float f5 = this.derivatives[i + 1]; +- float f6 = toFloatFunction.apply(object); +- float f7 = toFloatFunction1.apply(object); +- float f8 = f4 * (f2 - f1) - (f7 - f6); +- float f9 = -f5 * (f2 - f1) + (f7 - f6); +- return Mth.lerp(f3, f6, f7) + f3 * (1.0F - f3) * Mth.lerp(f3, f8, f9); ++ float loc0 = this.locations[rangeForLocation]; ++ float loc1 = this.locations[rangeForLocation + 1]; ++ float locDist = loc1 - loc0; ++ float k = (point - loc0) / locDist; ++ float n = this.values.get(rangeForLocation).apply(object); ++ float o = this.values.get(rangeForLocation + 1).apply(object); ++ float onDist = o - n; ++ float p = this.derivatives[rangeForLocation] * locDist - onDist; ++ float q = -this.derivatives[rangeForLocation + 1] * locDist + onDist; ++ return Mth.lerp(k, n, o) + k * (1.0F - k) * Mth.lerp(k, p, q); + } ++ // DivineMC end - Density Function Compiler + } + + private static int findIntervalStart(float[] locations, float start) { +- return Mth.binarySearch(0, locations.length, i -> start < locations[i]) - 1; ++ // DivineMC start - Density Function Compiler ++ int min = 0; ++ int i = locations.length; ++ ++ while (i > 0) { ++ int j = i / 2; ++ int k = min + j; ++ if (start < locations[k]) { ++ i = j; ++ } else { ++ min = k + 1; ++ i -= j + 1; ++ } ++ } ++ ++ return min - 1; ++ // DivineMC end - Density Function Compiler + } + + @VisibleForTesting +@@ -313,5 +329,27 @@ public interface CubicSpline> extends ToFloatFun + this.derivatives + ); + } ++ ++ // DivineMC start - Density Function Compiler ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ Multipoint that = (Multipoint) o; ++ return java.util.Objects.equals(coordinate, that.coordinate()) && java.util.Arrays.equals(locations, that.locations()) && java.util.Objects.equals(values, that.values()) && java.util.Arrays.equals(derivatives, that.derivatives()); ++ } ++ ++ @Override ++ public int hashCode() { ++ int result = 1; ++ ++ result = 31 * result + java.util.Objects.hashCode(coordinate); ++ result = 31 * result + java.util.Arrays.hashCode(locations); ++ result = 31 * result + java.util.Objects.hashCode(values); ++ result = 31 * result + java.util.Arrays.hashCode(derivatives); ++ ++ return result; ++ } ++ // DivineMC end - Density Function Compiler + } + } +diff --git a/net/minecraft/world/level/levelgen/DensityFunctions.java b/net/minecraft/world/level/levelgen/DensityFunctions.java +index 7178013421233d7dab36eb07a768907ce40e8745..f56321eefa5fcdfdb30883beaf97c87ac6fa0183 100644 +--- a/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -275,38 +275,66 @@ public final class DensityFunctions { + + @Override + public void fillArray(double[] array, DensityFunction.ContextProvider contextProvider) { +- this.argument1.fillArray(array, contextProvider); +- switch (this.type) { +- case ADD: +- double[] doubles = new double[array.length]; +- this.argument2.fillArray(doubles, contextProvider); +- +- for (int i = 0; i < array.length; i++) { +- array[i] += doubles[i]; +- } +- break; +- case MUL: +- for (int i1 = 0; i1 < array.length; i1++) { +- double d = array[i1]; +- array[i1] = d == 0.0 ? 0.0 : d * this.argument2.compute(contextProvider.forIndex(i1)); +- } +- break; +- case MIN: +- double d1 = this.argument2.minValue(); ++ // DivineMC start - Density Function Compiler ++ Runnable run = () -> { ++ this.argument1.fillArray(array, contextProvider); ++ switch (this.type) { ++ case ADD: ++ double[] doubles = new double[array.length]; ++ this.argument2.fillArray(doubles, contextProvider); ++ ++ for (int i = 0; i < array.length; i++) { ++ array[i] += doubles[i]; ++ } ++ break; ++ case MUL: ++ for (int i1 = 0; i1 < array.length; i1++) { ++ double d = array[i1]; ++ array[i1] = d == 0.0 ? 0.0 : d * this.argument2.compute(contextProvider.forIndex(i1)); ++ } ++ break; ++ case MIN: ++ double d1 = this.argument2.minValue(); ++ ++ for (int i2 = 0; i2 < array.length; i2++) { ++ double d2 = array[i2]; ++ array[i2] = d2 < d1 ? d2 : Math.min(d2, this.argument2.compute(contextProvider.forIndex(i2))); ++ } ++ break; ++ case MAX: ++ d1 = this.argument2.maxValue(); ++ ++ for (int i2 = 0; i2 < array.length; i2++) { ++ double d2 = array[i2]; ++ array[i2] = d2 > d1 ? d2 : Math.max(d2, this.argument2.compute(contextProvider.forIndex(i2))); ++ } ++ } ++ }; ++ if (org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler && this.type == DensityFunctions.TwoArgumentSimpleFunction.Type.ADD) { ++ this.argument1.fillArray(array, contextProvider); ++ double[] ds; + +- for (int i2 = 0; i2 < array.length; i2++) { +- double d2 = array[i2]; +- array[i2] = d2 < d1 ? d2 : Math.min(d2, this.argument2.compute(contextProvider.forIndex(i2))); +- } +- break; +- case MAX: +- d1 = this.argument2.maxValue(); ++ org.bxteam.divinemc.dfc.common.util.ArrayCache arrayCache = contextProvider instanceof org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable arrayCacheCapable ? arrayCacheCapable.c2me$getArrayCache() : null; + +- for (int i2 = 0; i2 < array.length; i2++) { +- double d2 = array[i2]; +- array[i2] = d2 > d1 ? d2 : Math.max(d2, this.argument2.compute(contextProvider.forIndex(i2))); +- } ++ if (arrayCache != null) { ++ ds = arrayCache.getDoubleArray(array.length, false); ++ } else { ++ ds = new double[array.length]; ++ } ++ ++ this.argument2.fillArray(ds, contextProvider); ++ ++ for (int i = 0; i < array.length; i++) { ++ array[i] += ds[i]; ++ } ++ ++ if (arrayCache != null) { ++ arrayCache.recycle(ds); ++ } ++ } else { ++ run.run(); + } ++ // DivineMC end - Density Function Compiler + } + + @Override +@@ -704,7 +732,105 @@ public final class DensityFunctions { + } + } + +- protected record Marker(@Override DensityFunctions.Marker.Type type, @Override DensityFunction wrapped) implements DensityFunctions.MarkerOrMarked { ++ // DivineMC start - Density Function Compiler ++ public static final class Marker implements org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike, org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding, MarkerOrMarked { ++ private final Type type; ++ private final DensityFunction wrapped; ++ private Object c2me$optionalEquality; ++ ++ @Override ++ public boolean equals(final Object that) { ++ Function original = (o) -> { ++ if (o == this) return true; ++ if (o == null || o.getClass() != this.getClass()) return false; ++ var a = (Marker) o; ++ return java.util.Objects.equals(this.type, a.type) && ++ java.util.Objects.equals(this.wrapped, a.wrapped); ++ }; ++ if (true) { ++ return original.apply(that); ++ } ++ Object a = this.c2me$getOverriddenEquality(); ++ Object b = that instanceof org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding equalityOverriding ? equalityOverriding.c2me$getOverriddenEquality() : null; ++ if (a == null) { ++ return original.apply(b != null ? b : that); ++ } else { ++ return a.equals(b != null ? b : that); ++ } ++ } ++ ++ @Override ++ public int hashCode() { ++ java.util.function.Supplier original = () -> java.util.Objects.hash(type, wrapped); ++ Object c2me$optionalEquality1 = this.c2me$optionalEquality; ++ if (c2me$optionalEquality1 != null && false) { ++ return c2me$optionalEquality1.hashCode(); ++ } else { ++ return original.get(); ++ } ++ } ++ ++ public Marker(Type type, DensityFunction wrapped) { ++ this.type = type; ++ this.wrapped = wrapped; ++ } ++ ++ @Override ++ public double c2me$getCached(int x, int y, int z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType) { ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType) { ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, org.bxteam.divinemc.dfc.common.ast.EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.wrapped; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ DensityFunctions.Marker wrapping = new DensityFunctions.Marker(this.type(), delegate); ++ ((org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding) (Object) wrapping).c2me$overrideEquality(this); ++ return wrapping; ++ } ++ ++ @Override ++ public void c2me$overrideEquality(Object object) { ++ Object inner = object; ++ while (true) { ++ Object inner1 = inner instanceof org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding e1 ? e1.c2me$getOverriddenEquality() : null; ++ if (inner1 == null) { ++ this.c2me$optionalEquality = inner; ++ break; ++ } ++ inner = inner1; ++ } ++ } ++ ++ @Override ++ public Object c2me$getOverriddenEquality() { ++ return this.c2me$optionalEquality; ++ } ++ ++ @Override ++ public DensityFunction mapAll(Visitor visitor) { ++ return visitor.apply(this.c2me$withDelegate(this.wrapped().mapAll(visitor))); ++ } ++ // DivineMC end - Density Function Compiler ++ + @Override + public double compute(DensityFunction.FunctionContext context) { + return this.wrapped.compute(context); +@@ -725,7 +851,19 @@ public final class DensityFunctions { + return this.wrapped.maxValue(); + } + +- static enum Type implements StringRepresentable { ++ // DivineMC start - Density Function Compiler ++ @Override ++ public Type type() { ++ return type; ++ } ++ ++ @Override ++ public DensityFunction wrapped() { ++ return wrapped; ++ } ++ ++ public static enum Type implements StringRepresentable { // - public ++ // DivineMC end - Density Function Compiler + Interpolated("interpolated"), + FlatCache("flat_cache"), + Cache2D("cache_2d"), +diff --git a/net/minecraft/world/level/levelgen/NoiseChunk.java b/net/minecraft/world/level/levelgen/NoiseChunk.java +index 977b0d595e5637c80e7d4bb20da8896a0b64b844..991025da1005d6c4122897231095dc909a11d8f7 100644 +--- a/net/minecraft/world/level/levelgen/NoiseChunk.java ++++ b/net/minecraft/world/level/levelgen/NoiseChunk.java +@@ -4,9 +4,11 @@ import com.google.common.collect.Lists; + import it.unimi.dsi.fastutil.longs.Long2IntMap; + import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; + import java.util.ArrayList; ++import java.util.Arrays; + import java.util.HashMap; + import java.util.List; + import java.util.Map; ++import java.util.function.Supplier; + import javax.annotation.Nullable; + import net.minecraft.core.QuartPos; + import net.minecraft.core.SectionPos; +@@ -20,7 +22,18 @@ import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.levelgen.blending.Blender; + import net.minecraft.world.level.levelgen.material.MaterialRuleList; + +-public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunction.FunctionContext { ++// DivineMC start - Density Function Compiler ++import org.bxteam.divinemc.dfc.common.ast.EvalType; ++import org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable; ++import org.bxteam.divinemc.dfc.common.ducks.ICoordinatesFilling; ++import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; ++import org.bxteam.divinemc.dfc.common.gen.DelegatingBlendingAwareVisitor; ++import org.bxteam.divinemc.dfc.common.util.ArrayCache; ++import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; ++import org.bxteam.divinemc.dfc.common.vif.NoisePosVanillaInterface; ++// DivineMC end - Density Function Compiler ++ ++public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunction.FunctionContext, IArrayCacheCapable, ICoordinatesFilling { + private final NoiseSettings noiseSettings; + final int cellCountXZ; + final int cellCountY; +@@ -56,7 +69,47 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + long interpolationCounter; + long arrayInterpolationCounter; + int arrayIndex; +- private final DensityFunction.ContextProvider sliceFillingContextProvider = new DensityFunction.ContextProvider() { ++ // DivineMC start - Density Function Compiler ++ private final ArrayCache c2me$arrayCache = new ArrayCache(); ++ ++ @Override ++ public ArrayCache c2me$getArrayCache() { ++ return this.c2me$arrayCache != null ? this.c2me$arrayCache : new ArrayCache(); ++ } ++ ++ @Override ++ public void c2me$fillCoordinates(int[] x, int[] y, int[] z) { ++ int index = 0; ++ for (int i = this.cellHeight - 1; i >= 0; i--) { ++ int blockY = this.cellStartBlockY + i; ++ for (int j = 0; j < this.cellWidth; j++) { ++ int blockX = this.cellStartBlockX + j; ++ for (int k = 0; k < this.cellWidth; k++) { ++ int blockZ = this.cellStartBlockZ + k; ++ ++ x[index] = blockX; ++ y[index] = blockY; ++ z[index] = blockZ; ++ ++ index++; ++ } ++ } ++ } ++ } ++ ++ private @org.jetbrains.annotations.NotNull DelegatingBlendingAwareVisitor c2me$getDelegatingBlendingAwareVisitor(DensityFunction.Visitor visitor) { ++ return new DelegatingBlendingAwareVisitor(visitor, this.getBlender() != Blender.empty()); ++ } ++ ++ private DensityFunction.Visitor modifyVisitor1(DensityFunction.Visitor visitor) { ++ return c2me$getDelegatingBlendingAwareVisitor(visitor); ++ } ++ ++ private DensityFunction.Visitor modifyVisitor2(DensityFunction.Visitor visitor) { ++ return c2me$getDelegatingBlendingAwareVisitor(visitor); ++ } ++ ++ public class NoiseChunkSliceFillingContextProvider implements DensityFunction.ContextProvider, IArrayCacheCapable, ICoordinatesFilling { + @Override + public DensityFunction.FunctionContext forIndex(int arrayIndex) { + NoiseChunk.this.cellStartBlockY = (arrayIndex + NoiseChunk.this.cellNoiseMinY) * NoiseChunk.this.cellHeight; +@@ -76,7 +129,23 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + values[i] = function.compute(NoiseChunk.this); + } + } +- }; ++ ++ @Override ++ public ArrayCache c2me$getArrayCache() { ++ return (NoiseChunk.this).c2me$getArrayCache(); ++ } ++ ++ @Override ++ public void c2me$fillCoordinates(int[] x, int[] y, int[] z) { ++ for (int i = 0; i < (NoiseChunk.this).cellCountY + 1; i++) { ++ x[i] = (NoiseChunk.this).cellStartBlockX + (NoiseChunk.this).inCellX; ++ y[i] = (i + (NoiseChunk.this).cellNoiseMinY) * (NoiseChunk.this).cellHeight; ++ z[i] = (NoiseChunk.this).cellStartBlockZ + (NoiseChunk.this).inCellZ; ++ } ++ } ++ } ++ private final DensityFunction.ContextProvider sliceFillingContextProvider = new NoiseChunkSliceFillingContextProvider(); ++ // DivineMC end - Density Function Compiler + + public static NoiseChunk forChunk( + ChunkAccess chunk, +@@ -135,7 +204,7 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + + NoiseRouter noiseRouter = random.router(); +- NoiseRouter noiseRouter1 = noiseRouter.mapAll(this::wrap); ++ NoiseRouter noiseRouter1 = noiseRouter.mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor1(this::wrap) : this::wrap); // DivineMC - Density Function Compiler + if (!noiseGeneratorSettings.isAquifersEnabled()) { + this.aquifer = Aquifer.createDisabled(fluidPicker); + } else { +@@ -150,7 +219,7 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + DensityFunction densityFunction = DensityFunctions.cacheAllInCell( + DensityFunctions.add(noiseRouter1.finalDensity(), DensityFunctions.BeardifierMarker.INSTANCE) + ) +- .mapAll(this::wrap); ++ .mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap); // DivineMC - Density Function Compiler + list.add(context -> this.aquifer.computeSubstance(context, densityFunction.compute(context))); + if (noiseGeneratorSettings.oreVeinsEnabled()) { + list.add(OreVeinifier.create(noiseRouter1.veinToggle(), noiseRouter1.veinRidged(), noiseRouter1.veinGap(), random.oreRandom())); +@@ -162,12 +231,14 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + protected Climate.Sampler cachedClimateSampler(NoiseRouter noiseRouter, List points) { + return new Climate.Sampler( +- noiseRouter.temperature().mapAll(this::wrap), +- noiseRouter.vegetation().mapAll(this::wrap), +- noiseRouter.continents().mapAll(this::wrap), +- noiseRouter.erosion().mapAll(this::wrap), +- noiseRouter.depth().mapAll(this::wrap), +- noiseRouter.ridges().mapAll(this::wrap), ++ // DivineMC start - Density Function Compiler ++ noiseRouter.temperature().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.vegetation().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.continents().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.erosion().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.depth().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ noiseRouter.ridges().mapAll(org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler ? modifyVisitor2(this::wrap) : this::wrap), ++ // DivineMC end - Density Function Compiler + points + ); + } +@@ -366,6 +437,13 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + + private DensityFunction wrapNew(DensityFunction densityFunction) { ++ // DivineMC start - Density Function Compiler ++ if (org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler) { ++ if (this.interpolating && densityFunction instanceof DensityFunctions.Marker) { ++ throw new IllegalStateException("Cannot create more wrapping during interpolation loop"); ++ } ++ } ++ // DivineMC end - Density Function Compiler + if (densityFunction instanceof DensityFunctions.Marker marker) { + return (DensityFunction)(switch (marker.type()) { + case Interpolated -> new NoiseChunk.NoiseInterpolator(marker.wrapped()); +@@ -475,10 +553,48 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + BlockState calculate(DensityFunction.FunctionContext context); + } + +- static class Cache2D implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ static class Cache2D implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + private DensityFunction function; + private long lastPos2D = ChunkPos.INVALID_CHUNK_POS; + private double lastValue; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ long l = ChunkPos.asLong(x, z); ++ if (this.lastPos2D == l) { ++ return this.lastValue; ++ } else { ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ this.lastPos2D = ChunkPos.asLong(x, z); ++ this.lastValue = cached; ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.function; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.function = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + Cache2D(DensityFunction function) { + this.function = function; +@@ -515,9 +631,92 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- class CacheAllInCell implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { +- final DensityFunction noiseFiller; ++ class CacheAllInCell implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler ++ DensityFunction noiseFiller; // DivineMC - remove final + final double[] values; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ int cellBlockX = x - startBlockX; ++ int cellBlockY = y - startBlockY; ++ int cellBlockZ = z - startBlockZ; ++ if (cellBlockX >= 0 && ++ cellBlockY >= 0 && ++ cellBlockZ >= 0 && ++ cellBlockX < horizontalCellBlockCount && ++ cellBlockY < verticalCellBlockCount && ++ cellBlockZ < horizontalCellBlockCount) { ++ return this.values[((verticalCellBlockCount - 1 - cellBlockY) * horizontalCellBlockCount + cellBlockX) ++ * horizontalCellBlockCount ++ + cellBlockZ]; ++ } ++ } ++ } ++ ++ return CACHE_MISS_NAN_BITS; ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ for (int i = 0; i < res.length; i++) { ++ int cellBlockX = x[i] - startBlockX; ++ int cellBlockY = y[i] - startBlockY; ++ int cellBlockZ = z[i] - startBlockZ; ++ if (cellBlockX >= 0 && ++ cellBlockY >= 0 && ++ cellBlockZ >= 0 && ++ cellBlockX < horizontalCellBlockCount && ++ cellBlockY < verticalCellBlockCount && ++ cellBlockZ < horizontalCellBlockCount) { ++ res[i] = this.values[((verticalCellBlockCount - 1 - cellBlockY) * horizontalCellBlockCount + cellBlockX) * horizontalCellBlockCount + cellBlockZ]; ++ } else { ++ System.out.println("partial cell cache hit"); ++ return false; // partial hit possible ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.noiseFiller; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.noiseFiller = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + CacheAllInCell(final DensityFunction noiseFilter) { + this.noiseFiller = noiseFilter; +@@ -527,18 +726,51 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + @Override + public double compute(DensityFunction.FunctionContext context) { +- if (context != NoiseChunk.this) { +- return this.noiseFiller.compute(context); +- } else if (!NoiseChunk.this.interpolating) { +- throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); +- } else { +- int i = NoiseChunk.this.inCellX; +- int i1 = NoiseChunk.this.inCellY; +- int i2 = NoiseChunk.this.inCellZ; +- return i >= 0 && i1 >= 0 && i2 >= 0 && i < NoiseChunk.this.cellWidth && i1 < NoiseChunk.this.cellHeight && i2 < NoiseChunk.this.cellWidth +- ? this.values[((NoiseChunk.this.cellHeight - 1 - i1) * NoiseChunk.this.cellWidth + i) * NoiseChunk.this.cellWidth + i2] ++ // DivineMC start - Density Function Compiler ++ Supplier run = () -> { ++ if (context != NoiseChunk.this) { ++ return this.noiseFiller.compute(context); ++ } else if (!NoiseChunk.this.interpolating) { ++ throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); ++ } else { ++ int i = NoiseChunk.this.inCellX; ++ int i1 = NoiseChunk.this.inCellY; ++ int i2 = NoiseChunk.this.inCellZ; ++ return i >= 0 && i1 >= 0 && i2 >= 0 && i < NoiseChunk.this.cellWidth && i1 < NoiseChunk.this.cellHeight && i2 < NoiseChunk.this.cellWidth ++ ? this.values[((NoiseChunk.this.cellHeight - 1 - i1) * NoiseChunk.this.cellWidth + i) * NoiseChunk.this.cellWidth + i2] ++ : this.noiseFiller.compute(context); ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || context instanceof NoiseChunk) { ++ return run.get(); ++ } ++ if (context instanceof NoisePosVanillaInterface vif && vif.getType() == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (!isInInterpolationLoop) { ++ return run.get(); ++ } ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ int cellBlockX = context.blockX() - startBlockX; ++ int cellBlockY = context.blockY() - startBlockY; ++ int cellBlockZ = context.blockZ() - startBlockZ; ++ return cellBlockX >= 0 ++ && cellBlockY >= 0 ++ && cellBlockZ >= 0 ++ && cellBlockX < horizontalCellBlockCount ++ && cellBlockY < verticalCellBlockCount ++ && cellBlockZ < horizontalCellBlockCount ++ ? this.values[((verticalCellBlockCount - 1 - cellBlockY) * horizontalCellBlockCount + cellBlockX) ++ * horizontalCellBlockCount ++ + cellBlockZ] + : this.noiseFiller.compute(context); + } ++ ++ return run.get(); ++ // DivineMC end - Density Function Compiler + } + + @Override +@@ -557,13 +789,84 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- class CacheOnce implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ class CacheOnce implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + private DensityFunction function; + private long lastCounter; + private long lastArrayCounter; + private double lastValue; + @Nullable + private double[] lastArray; ++ // DivineMC start - Density Function Compiler ++ private double c2me$lastValue = Double.NaN; ++ private int c2me$lastX = Integer.MIN_VALUE; ++ private int c2me$lastY = Integer.MIN_VALUE; ++ private int c2me$lastZ = Integer.MIN_VALUE; ++ ++ private int[] c2me$lastXa; ++ private int[] c2me$lastYa; ++ private int[] c2me$lastZa; ++ private double[] c2me$lastValuea; ++ ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ if (c2me$lastValuea != null) { ++ for (int i = 0; i < this.c2me$lastValuea.length; i ++) { ++ if (c2me$lastXa[i] == x && c2me$lastYa[i] == y && c2me$lastZa[i] == z) { ++ return c2me$lastValuea[i]; ++ } ++ } ++ } ++ if (!Double.isNaN(c2me$lastValue) && c2me$lastX == x && c2me$lastY == y && c2me$lastZ == z) { ++ return c2me$lastValue; ++ } ++ ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (c2me$lastValuea != null && Arrays.equals(y, c2me$lastYa) && Arrays.equals(x, c2me$lastXa) && Arrays.equals(z, c2me$lastZa)) { ++ System.arraycopy(c2me$lastValuea, 0, res, 0, c2me$lastValuea.length); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ c2me$lastValue = cached; ++ c2me$lastX = x; ++ c2me$lastY = y; ++ c2me$lastZ = z; ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (c2me$lastValuea != null && this.c2me$lastValuea.length == res.length) { ++ System.arraycopy(res, 0, this.c2me$lastValuea, 0, this.c2me$lastValuea.length); ++ System.arraycopy(x, 0, this.c2me$lastXa, 0, this.c2me$lastValuea.length); ++ System.arraycopy(y, 0, this.c2me$lastYa, 0, this.c2me$lastValuea.length); ++ System.arraycopy(z, 0, this.c2me$lastZa, 0, this.c2me$lastValuea.length); ++ } else { ++ this.c2me$lastValuea = Arrays.copyOf(res, res.length); ++ this.c2me$lastXa = Arrays.copyOf(x, x.length); ++ this.c2me$lastYa = Arrays.copyOf(y, y.length); ++ this.c2me$lastZa = Arrays.copyOf(z, z.length); ++ } ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.function; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.function = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + CacheOnce(final DensityFunction function) { + this.function = function; +@@ -571,34 +874,82 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + @Override + public double compute(DensityFunction.FunctionContext context) { +- if (context != NoiseChunk.this) { +- return this.function.compute(context); +- } else if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { +- return this.lastArray[NoiseChunk.this.arrayIndex]; +- } else if (this.lastCounter == NoiseChunk.this.interpolationCounter) { +- return this.lastValue; +- } else { +- this.lastCounter = NoiseChunk.this.interpolationCounter; +- double d = this.function.compute(context); +- this.lastValue = d; +- return d; ++ // DivineMC start - Density Function Compiler ++ Supplier run = () -> { ++ if (context != NoiseChunk.this) { ++ return this.function.compute(context); ++ } else if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { ++ return this.lastArray[NoiseChunk.this.arrayIndex]; ++ } else if (this.lastCounter == NoiseChunk.this.interpolationCounter) { ++ return this.lastValue; ++ } else { ++ this.lastCounter = NoiseChunk.this.interpolationCounter; ++ double d = this.function.compute(context); ++ this.lastValue = d; ++ return d; ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || context instanceof NoiseChunk) { ++ return run.get(); + } ++ int blockX = context.blockX(); ++ int blockY = context.blockY(); ++ int blockZ = context.blockZ(); ++ if (c2me$lastValuea != null) { ++ for (int i = 0; i < this.c2me$lastValuea.length; i ++) { ++ if (c2me$lastXa[i] == blockX && c2me$lastYa[i] == blockY && c2me$lastZa[i] == blockZ) { ++ return c2me$lastValuea[i]; ++ } ++ } ++ } ++ if (!Double.isNaN(c2me$lastValue) && c2me$lastX == blockX && c2me$lastY == blockY && c2me$lastZ == blockZ) { ++ return c2me$lastValue; ++ } ++ double sample = this.function.compute(context); ++ c2me$lastValue = sample; ++ c2me$lastX = blockX; ++ c2me$lastY = blockY; ++ c2me$lastZ = blockZ; ++ ++ return sample; ++ // DivineMC end - Density Function Compiler + } + + @Override + public void fillArray(double[] array, DensityFunction.ContextProvider contextProvider) { +- if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { +- System.arraycopy(this.lastArray, 0, array, 0, array.length); +- } else { +- this.wrapped().fillArray(array, contextProvider); +- if (this.lastArray != null && this.lastArray.length == array.length) { +- System.arraycopy(array, 0, this.lastArray, 0, array.length); ++ // DivineMC start - Density Function Compiler ++ Runnable run = () -> { ++ if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { ++ System.arraycopy(this.lastArray, 0, array, 0, array.length); + } else { +- this.lastArray = (double[])array.clone(); +- } ++ this.wrapped().fillArray(array, contextProvider); ++ if (this.lastArray != null && this.lastArray.length == array.length) { ++ System.arraycopy(array, 0, this.lastArray, 0, array.length); ++ } else { ++ this.lastArray = (double[]) array.clone(); ++ } + +- this.lastArrayCounter = NoiseChunk.this.arrayInterpolationCounter; ++ this.lastArrayCounter = NoiseChunk.this.arrayInterpolationCounter; ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || contextProvider instanceof NoiseChunk) { ++ run.run(); ++ return; ++ } ++ if (contextProvider instanceof EachApplierVanillaInterface ap) { ++ if (c2me$lastValuea != null && Arrays.equals(ap.getY(), c2me$lastYa) && Arrays.equals(ap.getX(), c2me$lastXa) && Arrays.equals(ap.getZ(), c2me$lastZa)) { ++ System.arraycopy(c2me$lastValuea, 0, array, 0, c2me$lastValuea.length); ++ } else { ++ this.function.fillArray(array, contextProvider); ++ this.c2me$lastValuea = Arrays.copyOf(array, array.length); ++ this.c2me$lastXa = ap.getX(); ++ this.c2me$lastYa = ap.getY(); ++ this.c2me$lastZa = ap.getZ(); ++ } ++ return; + } ++ this.function.fillArray(array, contextProvider); ++ // DivineMC end - Density Function Compiler + } + + @Override +@@ -612,9 +963,63 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- class FlatCache implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ class FlatCache implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + private DensityFunction noiseFiller; + final double[][] values; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ int i = QuartPos.fromBlock(x); ++ int j = QuartPos.fromBlock(z); ++ int k = i - (NoiseChunk.this).firstNoiseX; ++ int l = j - (NoiseChunk.this).firstNoiseZ; ++ int m = this.values.length; ++ if (k >= 0 && l >= 0 && k < m && l < m) { ++ return this.values[k][l]; ++ } else { ++ return Double.longBitsToDouble(CACHE_MISS_NAN_BITS); ++ } ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ for (int i = 0; i < res.length; i ++) { ++ int i1 = QuartPos.fromBlock(x[i]); ++ int j1 = QuartPos.fromBlock(z[i]); ++ int k = i1 - (NoiseChunk.this).firstNoiseX; ++ int l = j1 - (NoiseChunk.this).firstNoiseZ; ++ int m = this.values.length; ++ if (k >= 0 && l >= 0 && k < m && l < m) { ++ res[i] = this.values[k][l]; ++ } else { ++ System.out.println("partial flat cache hit"); ++ return false; // partial hit possible ++ } ++ } ++ return true; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.noiseFiller; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.noiseFiller = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + FlatCache(final DensityFunction noiseFiller, final boolean computeValues) { + this.noiseFiller = noiseFiller; +@@ -673,7 +1078,7 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + } + } + +- public class NoiseInterpolator implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction { ++ public class NoiseInterpolator implements DensityFunctions.MarkerOrMarked, NoiseChunk.NoiseChunkDensityFunction, IFastCacheLike { // DivineMC - Density Function Compiler + double[][] slice0; + double[][] slice1; + private DensityFunction noiseFiller; +@@ -692,6 +1097,104 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + private double valueZ0; + private double valueZ1; + private double value; ++ // DivineMC start - Density Function Compiler ++ @Override ++ public double c2me$getCached(int x, int y, int z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ if ((NoiseChunk.this).fillingCell) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ int horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ int verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ int cellBlockX = x - startBlockX; ++ int cellBlockY = y - startBlockY; ++ int cellBlockZ = z - startBlockZ; ++ return Mth.lerp3( ++ (double) cellBlockX / (double) horizontalCellBlockCount, ++ (double) cellBlockY / (double) verticalCellBlockCount, ++ (double) cellBlockZ / (double) horizontalCellBlockCount, ++ this.noise000, ++ this.noise100, ++ this.noise010, ++ this.noise110, ++ this.noise001, ++ this.noise101, ++ this.noise011, ++ this.noise111 ++ ); ++ } else { ++ return this.value; ++ } ++ } ++ } ++ ++ return CACHE_MISS_NAN_BITS; ++ } ++ ++ @Override ++ public boolean c2me$getCached(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ if (evalType == EvalType.INTERPOLATION) { ++ boolean isInInterpolationLoop = (NoiseChunk.this).interpolating; ++ if (isInInterpolationLoop) { ++ if ((NoiseChunk.this).fillingCell) { ++ int startBlockX = (NoiseChunk.this).cellStartBlockX; ++ int startBlockY = (NoiseChunk.this).cellStartBlockY; ++ int startBlockZ = (NoiseChunk.this).cellStartBlockZ; ++ double horizontalCellBlockCount = (NoiseChunk.this).cellWidth; ++ double verticalCellBlockCount = (NoiseChunk.this).cellHeight; ++ for (int i = 0; i < res.length; i ++) { ++ int cellBlockX = x[i] - startBlockX; ++ int cellBlockY = y[i] - startBlockY; ++ int cellBlockZ = z[i] - startBlockZ; ++ res[i] = Mth.lerp3( ++ (double)cellBlockX / horizontalCellBlockCount, ++ (double)cellBlockY / verticalCellBlockCount, ++ (double)cellBlockZ / horizontalCellBlockCount, ++ this.noise000, ++ this.noise100, ++ this.noise010, ++ this.noise110, ++ this.noise001, ++ this.noise101, ++ this.noise011, ++ this.noise111 ++ ); ++ } ++ return true; ++ } else { ++ Arrays.fill(res, this.value); ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void c2me$cache(int x, int y, int z, EvalType evalType, double cached) { ++ // nop ++ } ++ ++ @Override ++ public void c2me$cache(double[] res, int[] x, int[] y, int[] z, EvalType evalType) { ++ // nop ++ } ++ ++ @Override ++ public DensityFunction c2me$getDelegate() { ++ return this.noiseFiller; ++ } ++ ++ @Override ++ public DensityFunction c2me$withDelegate(DensityFunction delegate) { ++ this.noiseFiller = delegate; ++ return this; ++ } ++ // DivineMC end - Density Function Compiler + + NoiseInterpolator(final DensityFunction noiseFilter) { + this.noiseFiller = noiseFilter; +@@ -741,16 +1244,18 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + + @Override + public double compute(DensityFunction.FunctionContext context) { +- if (context != NoiseChunk.this) { +- return this.noiseFiller.compute(context); +- } else if (!NoiseChunk.this.interpolating) { +- throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); +- } else { +- return NoiseChunk.this.fillingCell +- ? Mth.lerp3( +- (double)NoiseChunk.this.inCellX / NoiseChunk.this.cellWidth, +- (double)NoiseChunk.this.inCellY / NoiseChunk.this.cellHeight, +- (double)NoiseChunk.this.inCellZ / NoiseChunk.this.cellWidth, ++ // DivineMC start - Density Function Compiler ++ Supplier original = () -> { ++ if (context != NoiseChunk.this) { ++ return this.noiseFiller.compute(context); ++ } else if (!NoiseChunk.this.interpolating) { ++ throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); ++ } else { ++ return NoiseChunk.this.fillingCell ++ ? Mth.lerp3( ++ (double) NoiseChunk.this.inCellX / NoiseChunk.this.cellWidth, ++ (double) NoiseChunk.this.inCellY / NoiseChunk.this.cellHeight, ++ (double) NoiseChunk.this.inCellZ / NoiseChunk.this.cellWidth, + this.noise000, + this.noise100, + this.noise010, +@@ -760,8 +1265,45 @@ public class NoiseChunk implements DensityFunction.ContextProvider, DensityFunct + this.noise011, + this.noise111 + ) +- : this.value; ++ : this.value; ++ } ++ }; ++ if (!org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler || context instanceof NoiseChunk) { ++ return original.get(); + } ++ if (context instanceof NoisePosVanillaInterface vif && vif.getType() == EvalType.INTERPOLATION) { ++ NoiseChunk field = NoiseChunk.this; ++ boolean isInInterpolationLoop = field.interpolating; ++ boolean isSamplingForCaches = field.fillingCell; ++ if (!isInInterpolationLoop) { ++ return original.get(); ++ } ++ int startBlockX = field.cellStartBlockX; ++ int startBlockY = field.cellStartBlockY; ++ int startBlockZ = field.cellStartBlockZ; ++ int horizontalCellBlockCount = field.cellWidth(); ++ int verticalCellBlockCount = field.cellHeight(); ++ int cellBlockX = context.blockX() - startBlockX; ++ int cellBlockY = context.blockY() - startBlockY; ++ int cellBlockZ = context.blockZ() - startBlockZ; ++ return isSamplingForCaches ++ ? Mth.lerp3( ++ (double)cellBlockX / (double)horizontalCellBlockCount, ++ (double)cellBlockY / (double)verticalCellBlockCount, ++ (double)cellBlockZ / (double)horizontalCellBlockCount, ++ this.noise000, ++ this.noise100, ++ this.noise010, ++ this.noise110, ++ this.noise001, ++ this.noise101, ++ this.noise011, ++ this.noise111 ++ ) : this.value; ++ } ++ ++ return original.get(); ++ // DivineMC end - Density Function Compiler + } + + @Override +diff --git a/net/minecraft/world/level/levelgen/RandomState.java b/net/minecraft/world/level/levelgen/RandomState.java +index f1e089ecfffa40cd794c49db30fcedf138d3fee9..4e98103b35b03cd67ab9b4bdb91c39a9c94312df 100644 +--- a/net/minecraft/world/level/levelgen/RandomState.java ++++ b/net/minecraft/world/level/levelgen/RandomState.java +@@ -122,6 +122,41 @@ public final class RandomState { + this.router.ridges().mapAll(visitor), + settings.spawnTarget() + ); ++ ++ // DivineMC start - Density Function Compiler ++ if (org.bxteam.divinemc.DivineConfig.enableDensityFunctionCompiler) { ++ com.google.common.base.Stopwatch stopwatch = com.google.common.base.Stopwatch.createStarted(); ++ it.unimi.dsi.fastutil.objects.Reference2ReferenceMap tempCache = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(); ++ this.router = new NoiseRouter( ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.barrierNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.fluidLevelFloodednessNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.fluidLevelSpreadNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.lavaNoise(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.temperature(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.vegetation(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.continents(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.erosion(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.depth(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.ridges(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.initialDensityWithoutJaggedness(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.finalDensity(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.veinToggle(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.veinRidged(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.router.veinGap(), tempCache) ++ ); ++ this.sampler = new Climate.Sampler( ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.temperature(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.humidity(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.continentalness(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.erosion(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.depth(), tempCache), ++ org.bxteam.divinemc.dfc.common.gen.BytecodeGen.compile(this.sampler.weirdness(), tempCache), ++ this.sampler.spawnTarget() ++ ); ++ stopwatch.stop(); ++ System.out.printf("Density function compilation finished in %s%n", stopwatch); ++ } ++ // DivineMC end - Density Function Compiler + } + + public NormalNoise getOrCreateNoise(ResourceKey resourceKey) { diff --git a/divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch b/divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch new file mode 100644 index 0000000..c7d5458 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch @@ -0,0 +1,15 @@ +--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +@@ -381,8 +_,10 @@ + final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; + + return Integer.compare( +- Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), +- Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) ++ // DivineMC start - Chunk Loading Priority Optimization ++ (c1x - centerX) * (c1x - centerX) + (c1z - centerZ) * (c1z - centerZ), ++ (c2x - centerX) * (c2x - centerX) + (c2z - centerZ) * (c2z - centerZ) ++ // DivineMC end - Chunk Loading Priority Optimization + ); + }; + private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); diff --git a/divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch b/divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch new file mode 100644 index 0000000..502b179 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/io/papermc/paper/command/subcommands/FixLightCommand.java.patch @@ -0,0 +1,32 @@ +--- a/io/papermc/paper/command/subcommands/FixLightCommand.java ++++ b/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -95,17 +_,22 @@ + ((StarLightLightingProvider)lightengine).starlight$serverRelightChunks(chunks, + (final ChunkPos chunkPos) -> { + ++relitChunks[0]; +- sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( +- text("Relit chunk ", BLUE), text(chunkPos.toString()), +- text(", progress: ", BLUE), text(ONE_DECIMAL_PLACES.get().format(100.0 * (double) (relitChunks[0]) / (double) pending[0]) + "%") +- )); ++ // DivineMC start - Make FixLight use action bar ++ sender.getBukkitEntity().sendActionBar(text().color(DARK_AQUA).append( ++ text("Relighting Chunks: ", DARK_AQUA), text(chunkPos.toString()), ++ text(" " + relitChunks[0], net.kyori.adventure.text.format.NamedTextColor.YELLOW), ++ text("/", DARK_AQUA), ++ text(pending[0] + " ", net.kyori.adventure.text.format.NamedTextColor.YELLOW), ++ text("(" + (int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%)", net.kyori.adventure.text.format.NamedTextColor.YELLOW) ++ )); // DivineMC end - Make FixLight use action bar + }, + (final int totalRelit) -> { + final long end = System.nanoTime(); + sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( +- text("Relit ", BLUE), text(totalRelit), +- text(" chunks. Took ", BLUE), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms") +- )); ++ // DivineMC start - Make FixLight use action bar ++ text("Relit ", DARK_AQUA), text(totalRelit, net.kyori.adventure.text.format.NamedTextColor.YELLOW), ++ text(" chunks. Took ", DARK_AQUA), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms", net.kyori.adventure.text.format.NamedTextColor.YELLOW) ++ )); // DivineMC end - Make FixLight use action bar + if (done != null) { + done.run(); + } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch new file mode 100644 index 0000000..2ee5bd7 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -19,7 +_,7 @@ + + @Override + public final void addPlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener listener) { +- playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener); // Paper - fix PlayerAdvancements leak ++ playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newConcurrentHashSet()).add(listener); // Paper - fix PlayerAdvancements leak // DivineMC - use concurrent set + } + + @Override diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch new file mode 100644 index 0000000..05a9f23 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -994,6 +_,13 @@ + if (this.hasStopped) return; + this.hasStopped = true; + } ++ // DivineMC start - Respawn players that were dead on server restart ++ for (ServerPlayer player : this.playerList.players) { ++ if (player.isDeadOrDying() || (player.isRemoved() && player.getRemovalReason() == net.minecraft.world.entity.Entity.RemovalReason.KILLED)) { ++ this.playerList.respawn(player, false, net.minecraft.world.entity.Entity.RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH); ++ } ++ } ++ // DivineMC end - Respawn players that were dead on server restart + if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging + shutdownThread = Thread.currentThread(); // Paper - Improved watchdog support + org.spigotmc.WatchdogThread.doStop(); // Paper - Improved watchdog support diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch new file mode 100644 index 0000000..2facf9c --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -60,7 +_,7 @@ + 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 org.bxteam.divinemc.util.map.ConcurrentReferenceHashMap<>(); // Paper - fix advancement data player leakage // DivineMC - Use ConcurrentReferenceHashMap + + public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) { + this.playerList = playerList; diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch new file mode 100644 index 0000000..f759c2f --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -2259,6 +_,7 @@ + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { + this.removeEntitiesOnShoulder(); ++ this.stopSleeping(); // DivineMC - Fix MC-119417 + this.stopRiding(); + EnchantmentHelper.stopLocationBasedEffects(this); + } else { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch new file mode 100644 index 0000000..deb2c27 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch @@ -0,0 +1,28 @@ +--- a/net/minecraft/stats/ServerStatsCounter.java ++++ b/net/minecraft/stats/ServerStatsCounter.java +@@ -81,12 +_,6 @@ + this.dirty.add(stat); + } + +- private Set> getDirty() { +- Set> set = Sets.newHashSet(this.dirty); +- this.dirty.clear(); +- return set; +- } +- + public void parseLocal(DataFixer fixerUpper, String json) { + try { + try (JsonReader jsonReader = new JsonReader(new StringReader(json))) { +@@ -190,9 +_,11 @@ + public void sendStats(ServerPlayer player) { + Object2IntMap> map = new Object2IntOpenHashMap<>(); + +- for (Stat stat : this.getDirty()) { ++ for (Stat stat : this.dirty) { // DivineMC - Skip dirty stats copy when requesting player stats + map.put(stat, this.getValue(stat)); + } ++ ++ this.dirty.clear(); // DivineMC - Skip dirty stats copy when requesting player stats + + player.connection.send(new ClientboundAwardStatsPacket(map)); + } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch new file mode 100644 index 0000000..2933219 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/util/Mth.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/util/Mth.java ++++ b/net/minecraft/util/Mth.java +@@ -46,11 +_,11 @@ + private static final double[] COS_TAB = new double[257]; + + public static float sin(float value) { +- return SIN[(int)(value * 10430.378F) & 65535]; ++ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.sin(value); // DivineMC - lithium: CompactSineLUT + } + + public static float cos(float value) { +- return SIN[(int)(value * 10430.378F + 16384.0F) & 65535]; ++ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.cos(value); // DivineMC - lithium: CompactSineLUT + } + + public static float sqrt(float value) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch new file mode 100644 index 0000000..e8a5a70 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/net/minecraft/util/thread/BlockableEventLoop.java +@@ -22,7 +_,7 @@ + import org.slf4j.Logger; + + public abstract class BlockableEventLoop implements ProfilerMeasured, TaskScheduler, Executor { +- public static final long BLOCK_TIME_NANOS = 100000L; ++ public static final long BLOCK_TIME_NANOS = 2000000L; // DivineMC - Fix MC-183518 + private final String name; + private static final Logger LOGGER = LogUtils.getLogger(); + private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); +@@ -146,8 +_,7 @@ + } + + protected void waitForTasks() { +- Thread.yield(); +- LockSupport.parkNanos("waiting for tasks", 100000L); ++ LockSupport.parkNanos("waiting for tasks", 2000000L); // DivineMC - Fix MC-183518 + } + + protected void doRunTask(R task) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch new file mode 100644 index 0000000..61dc5bd --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1385,7 +_,7 @@ + player.setRealHealth(health); + } + +- player.updateScaledHealth(false); ++ this.entityData.set(LivingEntity.DATA_HEALTH_ID, player.getScaledHealth()); // DivineMC - Fix sprint glitch + return; + } + // CraftBukkit end +@@ -2666,6 +_,7 @@ + } + + protected void updateSwingTime() { ++ if (!this.swinging && this.swingTime == 0) return; // DivineMC - lithium: entity.fast_hand_swing + int currentSwingDuration = this.getCurrentSwingDuration(); + if (this.swinging) { + this.swingTime++; +@@ -3635,6 +_,7 @@ + protected void updateFallFlying() { + this.checkSlowFallDistance(); + if (!this.level().isClientSide) { ++ if (!this.isFallFlying() && this.fallFlyTicks == 0) return; // DivineMC - lithium: entity.fast_elytra_check + if (!this.canGlide()) { + if (this.getSharedFlag(7) != false && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit + this.setSharedFlag(7, false); diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch new file mode 100644 index 0000000..494bacd --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +@@ -114,6 +_,7 @@ + } + + protected void alertOther(Mob mob, LivingEntity target) { ++ if (mob == target) return; // DivineMC - Fix MC-110386 + mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason + } + } diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch new file mode 100644 index 0000000..f300a3f --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +@@ -22,6 +_,12 @@ + + @Override + protected void doTick(ServerLevel level, Villager entity) { ++ // DivineMC start - skip useless secondary poi sensor ++ if (org.bxteam.divinemc.DivineConfig.skipUselessSecondaryPoiSensor && entity.getVillagerData().getProfession().secondaryPoi().isEmpty()) { ++ entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); ++ return; ++ } ++ // DivineMC end - skip useless secondary poi sensor + // 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) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch new file mode 100644 index 0000000..abf709e --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -320,6 +_,12 @@ + if (!this.isSilent()) { + serverLevel.levelEvent(null, 1027, this.blockPosition(), 0); + } ++ ++ // DivineMC start - Fix MC-200418 ++ if (villager.isPassenger() && villager.getVehicle() instanceof net.minecraft.world.entity.animal.Chicken && villager.isBaby()) { ++ villager.removeVehicle(); ++ } ++ // DivineMC end - Fix MC-200418 + // CraftBukkit start + }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.CURED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CURED // CraftBukkit + ); diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch new file mode 100644 index 0000000..b431131 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -1879,6 +_,11 @@ + } + + public void causeFoodExhaustion(float exhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason reason) { ++ // DivineMC start - Fix MC-31819 ++ if (this.level().getDifficulty() == Difficulty.PEACEFUL) { ++ return; ++ } ++ // DivineMC end - Fix MC-31819 + // CraftBukkit end + if (!this.abilities.invulnerable) { + if (!this.level().isClientSide) { diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch new file mode 100644 index 0000000..56a0ade --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/GameRules.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/GameRules.java ++++ b/net/minecraft/world/level/GameRules.java +@@ -249,7 +_,7 @@ + } + + private GameRules(Map, GameRules.Value> rules, FeatureFlagSet enabledFeatures) { +- this.rules = rules; ++ this.rules = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(rules); // DivineMC - lithium: collections.gamerules + this.enabledFeatures = enabledFeatures; + + // Paper start - Perf: Use array for gamerule storage diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch new file mode 100644 index 0000000..4cd6154 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/Blocks.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/block/Blocks.java ++++ b/net/minecraft/world/level/block/Blocks.java +@@ -6632,6 +_,7 @@ + .mapColor(MapColor.COLOR_ORANGE) + .instrument(NoteBlockInstrument.BASEDRUM) + .requiresCorrectToolForDrops() ++ .sound(SoundType.COPPER) // DivineMC - Fix MC-223153 + .strength(5.0F, 6.0F) + ); + public static final Block RAW_GOLD_BLOCK = register( diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch new file mode 100644 index 0000000..adeb412 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/level/block/LeavesBlock.java ++++ b/net/minecraft/world/level/block/LeavesBlock.java +@@ -82,7 +_,23 @@ + + @Override + protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { +- level.setBlock(pos, updateDistance(state, level, pos), 3); ++ // DivineMC start - Make leaves not suffocate the server ++ int newValue = 7; ++ int oldValue = state.getValue(DISTANCE); ++ BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); ++ ++ for (Direction direction : Direction.values()) { ++ mutable.setWithOffset(pos, direction); ++ newValue = Math.min(newValue, getDistanceAt(level.getBlockState(mutable)) + 1); ++ if (newValue == 1) { ++ break; ++ } ++ } ++ ++ if (newValue != oldValue) { ++ level.setBlock(pos, state.setValue(DISTANCE, newValue), 3); ++ } ++ // DivineMC end - Make leaves not suffocate the server + } + + @Override diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch new file mode 100644 index 0000000..42d13f8 --- /dev/null +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -0,0 +1,26 @@ +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -267,11 +_,18 @@ + public BlockState getBlockStateFinal(final int x, final int y, final int z) { + // Copied and modified from below + final int sectionIndex = this.getSectionIndex(y); +- if (sectionIndex < 0 || sectionIndex >= this.sections.length +- || this.sections[sectionIndex].nonEmptyBlockCount == 0) { +- return Blocks.AIR.defaultBlockState(); +- } +- return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15); ++ // DivineMC start - optimize block state lookup ++ if (sectionIndex < 0 || sectionIndex >= this.sections.length) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ ++ final LevelChunkSection section = this.sections[sectionIndex]; ++ if (section.nonEmptyBlockCount == 0) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ ++ return section.states.get((y & 15) << 8 | (z & 15) << 4 | (x & 15)); ++ // DivineMC end - optimize block state lookup + } + @Override + public BlockState getBlockState(BlockPos pos) { diff --git a/divinemc-server/paper-patches/features/0001-Rebrand.patch b/divinemc-server/paper-patches/features/0001-Rebrand.patch index 3ac9f3b..7fdb2fa 100644 --- a/divinemc-server/paper-patches/features/0001-Rebrand.patch +++ b/divinemc-server/paper-patches/features/0001-Rebrand.patch @@ -1,36 +1,14 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 11 Jan 2025 22:11:40 +0300 +Date: Mon, 27 Jan 2025 01:35:41 +0300 Subject: [PATCH] Rebrand -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index be1bb14dca9367b9685841985b6198376986c496..58f16cdf593dbf874652a46799ecfa5c418c0486 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("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files -+ Metrics metrics = new Metrics("DivineMC", serverUUID, logFailedRequests, Bukkit.getLogger()); // DivineMC - - metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { - String minecraftVersion = Bukkit.getVersion(); -@@ -602,7 +602,7 @@ public class Metrics { - - metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); - metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur - Purpur config files -- metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur - Purpur config files -+ metrics.addCustomChart(new Metrics.SimplePie("divinemc_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // DivineMC - - metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { - Map> map = new HashMap<>(); diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index fe66e43c27e0798770e102d1385bacbaa90bda07..8c72ac3376ee1ea6f2c98de4d4fdbea91df2cfd7 100644 +index fe66e43c27e0798770e102d1385bacbaa90bda07..5aef1f7aa8c614e50b34747456e7d5a3f0045aff 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -36,13 +36,13 @@ public class PaperVersionFetcher implements VersionFetcher { +@@ -36,7 +36,7 @@ public class PaperVersionFetcher implements VersionFetcher { private static final int DISTANCE_ERROR = -1; private static final int DISTANCE_UNKNOWN = -2; // Purpur start - Rebrand @@ -39,19 +17,12 @@ index fe66e43c27e0798770e102d1385bacbaa90bda07..8c72ac3376ee1ea6f2c98de4d4fdbea9 private static int distance = DISTANCE_UNKNOWN; public int distance() { return distance; } // Purpur end - Rebrand - @Override - public long getCacheTime() { -- return 720000; -+ return 600000; // DivineMC - Decrease cache time to 10 minutes - } - - @Override @@ -52,7 +52,7 @@ public class PaperVersionFetcher implements VersionFetcher { if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); } else { - updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", build); // Purpur - Rebrand -+ updateMessage = getUpdateStatusMessage("BX-Team/DivineMC", build); // DivineMC - Branding ++ updateMessage = getUpdateStatusMessage("BX-Team/DivineMC", build); // DivineMC - Rebrand } final @Nullable Component history = this.getHistory(); @@ -70,13 +41,13 @@ index fe66e43c27e0798770e102d1385bacbaa90bda07..8c72ac3376ee1ea6f2c98de4d4fdbea9 - if (gitBranch.isPresent() && gitCommit.isPresent()) { - distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get()); - } -+ // DivineMC start - Branding ++ // DivineMC start - Rebrand + final Optional gitBranch = build.gitBranch(); + final Optional gitCommit = build.gitCommit(); + if (gitBranch.isPresent() && gitCommit.isPresent()) { + distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get()); } -+ // DivineMC end - Branding ++ // DivineMC end - Rebrand return switch (distance) { case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur - Rebrand @@ -121,31 +92,50 @@ index bc7e4e5560708fea89c584b1d8b471f4966f311a..6567ff18cb1c21230565c2d92caf3a7f .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 b36e30fd4057a938e4d90cb42a2dca661f16478e..fd860b1f3acbffa923dcfd39f7cb6f23cacbd408 100644 +index b36e30fd4057a938e4d90cb42a2dca661f16478e..4e29f5f55b9a894099bef6f7c7f11e2a96b02fc8 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( +@@ -23,15 +23,18 @@ public record ServerBuildInfoImpl( + Optional gitBranch, + Optional gitCommit + ) implements ServerBuildInfo { ++ public static boolean IS_EXPERIMENTAL = false; // DivineMC - Rebrand + private static final String ATTRIBUTE_BRAND_ID = "Brand-Id"; + private static final String ATTRIBUTE_BRAND_NAME = "Brand-Name"; + private static final String ATTRIBUTE_BUILD_TIME = "Build-Time"; + private static final String ATTRIBUTE_BUILD_NUMBER = "Build-Number"; + private static final String ATTRIBUTE_GIT_BRANCH = "Git-Branch"; + private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; ++ private static final String ATTRIBUTE_EXPERIMENTAL = "Experimental"; // DivineMC - Rebrand private static final String BRAND_PAPER_NAME = "Paper"; private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur - Rebrand -+ private static final String BRAND_DIVINEMC_NAME = "DivineMC"; // DivineMC - Branding ++ private static final String BRAND_DIVINEMC_NAME = "DivineMC"; // DivineMC - Rebrand private static final String BUILD_DEV = "DEV"; -@@ -43,9 +44,9 @@ public record ServerBuildInfoImpl( +@@ -43,9 +46,9 @@ public record ServerBuildInfoImpl( this( getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) .map(Key::key) - .orElse(BRAND_PURPUR_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ .orElse(BRAND_DIVINEMC_ID), // DivineMC - Branding ++ .orElse(BRAND_DIVINEMC_ID), // DivineMC - Rebrand getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) - .orElse(BRAND_PURPUR_NAME), // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ .orElse(BRAND_DIVINEMC_NAME), // DivineMC - Branding ++ .orElse(BRAND_DIVINEMC_NAME), // DivineMC - Rebrand SharedConstants.getCurrentVersion().getId(), SharedConstants.getCurrentVersion().getName(), getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) +@@ -58,6 +61,7 @@ public record ServerBuildInfoImpl( + getManifestAttribute(manifest, ATTRIBUTE_GIT_BRANCH), + getManifestAttribute(manifest, ATTRIBUTE_GIT_COMMIT) + ); ++ IS_EXPERIMENTAL = Boolean.parseBoolean(getManifestAttribute(manifest, ATTRIBUTE_EXPERIMENTAL).orElse("false")); // DivineMC - Rebrand + } + + @Override diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..7f3d6dc1c37ffce387ddd04bdd7328e0154b33ed 100644 +index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..a839dbbb72f48b8f8736d9f4693c528686570732 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 { @@ -153,7 +143,7 @@ index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..7f3d6dc1c37ffce387ddd04bdd7328e0 } 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 Purpur"); // Paper // Purpur - Rebrand -+ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to DivineMC"); // Paper // DivineMC - Rebrand ++ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to DivineMC"); // DivineMC - Rebrand // 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) } @@ -171,7 +161,7 @@ index 99eb04643fce44c37fd96c99756837ccafe7b559..4aef151bd162c4c99a3eaec1854b5463 if (stream != null) { diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb26485c8d87ab 100644 +index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..3731ca80ed58cd385cd66ffbe67f2eeaae642d0f 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -77,14 +77,14 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre @@ -179,7 +169,7 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb2648 // Paper end logger.log(Level.SEVERE, "------------------------------"); - logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug."); // Paper // Purpur - Rebrand -+ logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a DivineMC bug."); // Paper // DivineMC - Rebrand ++ logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a DivineMC bug."); // DivineMC - Rebrand 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"); @@ -203,7 +193,7 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb2648 // 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 Purpur!):" ); // Paper // Purpur - Rebrand -+ logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to DivineMC!):" ); // Paper // DivineMC - Rebrand ++ logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to DivineMC!):" ); // DivineMC - Rebrand 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, "------------------------------"); @@ -216,315 +206,3 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..26a3a0ab93ffd4d0a172e47fafeb2648 } logger.log(Level.SEVERE, "------------------------------"); -diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png -index 518591dd83289e041a16e2c2e7d7e7640d4b2e1b..f54753531b3bf2e8b5377f342465e727c7da98f2 100644 -GIT binary patch -literal 6677 -zcmZ`ecT^MIvk65y(vr|S0wD$n1T=z3m0m+wF=$9cr3j%}P!W;dMd>0)Cy6mZP(Xpu -z`KXEn5Jf2hLO#R>ih{iOJMX-A-XCxG+?_jjX7=vRopyIq-Cd813CjzEKp-&(dmB#> -z2#omK1bMk5u9dOJxq$DSrHds9#LO1i@#p8_sw8_)7Z51s00J|A35u(#^8|4ULRXr{u5ar-vE3hmqE3r=>bw!l> -zCLnNFgew+2R&lAAi)cmJ0#RrDqXICbhyX4Cp$t%{gN6nNQN~z96O4fg24$*eV1O|& -z`24~m`2Pr82s;ya_R9Y+a5FP`iYwq7063g=aRI@(eL)aESPJx4yI}4K0?UK`s+8LU -zIf51br|${Y`EMQ`5Qs-kkkt%z@DqvQS0OciO<6(B~X-Ttj8^Ly;I -zo6TV6_jdf1w>dz}4f>(bF>w9Nl+(tmyuNjboV5a{jFW#sdhZ3z{C6FC!V#*o?@AZ* -z6@#2M7W4S(D=_e$&8q=XmdM!qgOR%?ntYTjPFBys6aK5aZOW9A-@mVhM4$Tn#+hCB -zNpgxYSKw4B-@=Ml8HJRuwG70o3_~8YcHFp3NRQPixJk)8jWlBG`$)0fR?<9ouc(Iq -z@M>AWUQmUPC;T+To4^Nb6>2S#hFq7L&p%!1Cs%tkSst0NNYZm;6BFuVg$q)J!)F7( -z%(e_;?{knc?~C+ODT}S?y_c3y-kj+)Wgt5{`{U-T!|fUc+rlYhRs);U0u{fq@D7>$ -z_>Lp=enScNrA5b3pV^mFIh4g1^)G(S4aMb`J2kRvHwN--6UVxbA8uFjF<}pE*7ZMK -zD7IlonkOy3AIJvqJ+^v3B{iHk+09aQ#>kl{ -zmaISJQBQP8%5~(PD4NTU;H% -zN4-1eLAj|t8F9I*tOJKM3*6eu_ik{zyQXVr`+F=q1YAwrdHH=#%BbwO{yyyY;)#O9 -zGoK`Ujx6-fNTj483E1!FGtYVz6zXJkTq}>qH=7wK@z<-uMz+lx_|+jFzKR&rwU*h7 -z-SX~-k+=i6A(lrB*0r`9T8GUNIhfO&-z;_$ZArSIsvPp?xN$s|$dHe^`OM^F>B%4e -z+B+^26JapEmg)E{;0#))&Eg{uHb-Q`84A;T-g<0_D1txOzm@fAkH7Bi)a>I9KNL*w -z8u{ixFO8tyq?{r4KmQm -zSNiE?)c%L{xJxhP4&5oKhr-|T5c1%mU!3b24|v+PPTx7XiKE<3_og)W8hwd4bQ{h> -zw@BZ$Vn;F^AsxsUQYH4)WS37yHJ(jv^McSfJ!KYWI{j#p^1Z|(l4aXFHm%b)_D;p6 -zeK-F2{zQ2YFT{AfE%kmcleZA9 -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 -zKQC*8IZKJ85gXf`D@XmrZ}z0B4$-% -z#5u_aJlT~Rz#nDlIJ6PugYP20>(80*@KTjt2Lvz5mZ0M7$)CcUk@p_s$^h?o_rF$>!zEoV+I-e)}5CY2o9PEjay* -zV>Cj8ZChL}gx7qpb>?}aNx}GS69W2l#$e=$@mq@2rNQ3ZaqlTtH2F0b9YVEg_&cmp -z9vx$cf7u~q1PkZir56Xw60;6fM=X)hnQ`bvgB}QZiFos!aZPc!EHRvJhPYeZiG3_? -zjZbnKVSm#-9*)S}?Zz7Ix5mdiDb3_8pG#x{I61G8qoRfu1(?S9zqVOT5UPa0RFVoy -zxGZG6EN57Ym|95?5w#v3susU+2$|LdW!O*>lhl?!cqW?wb$~FN*e&rbyxv*?dCe5X -zA3X1$($YNfAX74Uu7Tv&Y0zVaUwg5SyFa7>J}6Pc<8{{Dz36a2cWZ@ziU|3oYtGnA -znJD06B5HU9wr-RKbRVXY{N@dMhVhN*V%+a3VjRb0wX;hVazYu=%el;UduXEp%w^7< -zb|+!8yUq+Ya!HO6tIB5eqD~poR2H$D+@pe77pwERgEIA -z0!|y>AQ6FFu;Cr?4;OGC36S8`-RHRs!ojv|9~nbh^^c7~^@OJH?SB5}xeQZzNnGTp -zU${G$vNFg^JlLjxT2*m!{P!2zieBFsmDoj7By5jYgbdVWx_kcpnE-OIb+w^e5#s*~ -DA-Nv< - -literal 9260 -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 -z<wCdy-3tTl}j4kzaCmrni9Diizn#a{@0 -zu;<>ZBofe$`#ML)Z*Aj8mbPS3-(VgSdF3LA8LJgBPj{3IaB~uLBuylKO*>ev*i|tuS(Wff3`Ogj^{GsE -zb-&NcY_)d|4#vv+AW}skSWrN{p$KOp?oug)BPoM*O}*h=wrr-PGYCt{qwP-&I!~hn -z&+*{EI5^09slk8i%kFcTjCcO6Oc}M9iiS}cuLLw0fyMPfjZ_M`;HWDHP^ke=f*5^! -z(0!2p!N9nZ8J+AP5sfunmbw64l@2)3@53_`Q@g&4FYKeDLbz2F-Pl@Er#INAtM()n -z<;MFT;osC}L2=Y4P1?cm)V{7nauSgh -zx4)k@#lLT}(uM?{Z&NvzT8pnJk@-MDvv4wOKsY+l9S8OdOw}cex#$t*B6ppAEloQy -z8u`9L`Ky$*$*G}A=)h6BjVn=_YuPie5epXe0vLKZP=Pxps%a&uGrdg4y#R{YobnDn -zWlMl5J;9|5ev`f`BO3Uh(yuK%@i^`+fAimq+_>*)z(;wrFdXn2nVJw1y_>PcV%p-) -zt#ZCU`}~DIE5y1BiI$qSd5XIebq(uRB-FnL#^x=bDHO-ls3({4R}-PgePOPH8ggGN -z^EAdT|N11qATWCZQ}ZY#=hum&;_xeyp*|O{VN?%jM$?&+DK=8LzLP4?U!YQ_JT0`M -z`m!W@TCB5mlr9&jJ{^cX4Ye=uf(7g!BDG1LQfZM#P5u-^$L1K~rOMk7oWI24W>ZJVoZ8!*NX>jC%2pgw@7$v*amGL8JnA`*TjN)y=JPix$ -zHom^xjfr~ZFvhO?xbJHg&jfEZgboIqN@pk*%G-#&cX5DM>_>dMo{P<(#6bOBlgEyG -zzi7pMrVsz<*TT+n%Xo*+rUVN -ztJqe<9EEg`YU)i$PQ+gn%oA5sG>b*6QUjZVf+Ju4QKWr32^B^>#t1pQ*dNT#SxuC8 -zGNJ9mH6R-92O+m#!DECXMrB|3+|cZ$?^qoag;w8-vr)Z5hx@2MVoazOwqdLo2-lu( -ziwF(a3SNH{+U_=d6KFp}-N^eInrfTZ{p%Yc;K{NXO^Sl0qy(%)%sUXL -zA6Ic}&$4}O+ulD-?|#%otX@aO$zD_RW;|WXeL`u5;iz`2Ja937zabkT*wwyB&2HIU -z%>tpNcvDWCsG=IjRr(V|Z7N`9^tphbhiC}fIrVcS>=>)uMStm;g&z-HK{fFz2ud&X -zKo;y87PhB3IZGZ&D%bJ2dZ<-Z7I$fCrPWl6O0ir})>f~+rM8k@!Q(EpWCD9f!k>me -zRhs@Q3_eZ!{s3QHUMaN0uShhn$y!N+)a -z6F#K#O21}#Wz2NGar1LUq3S|3=0~&VTjxVeqcysO1QIGwgglMcjXc3^9R9i556MW! -zA%G1+Y)mPX16RcVB#B?R~Xl -zIeR6G9EBEoU+Sk|=Q=4gQdT~j9ecG~G45m|E+IkhfuBxT`M%^wZkNkXpL0AjS!$%u -zg-={lr6QW@$p<#-f}T{y|0rAEpY~$9`Ffd=BP+`1#;?ge=@mLGkdmI~u?Dai2i+}> -zr-d}7M&xTHsK7@<3$tPd^Yg3fLzxDa!oOJ_N!hGt&$}5!9&TOIuzhIP>)^&CM1CIF -zY>A-293)fzq2lbB(1wKk=>#3=G1eTT&8(iBSJab=V@AGnDSwOhxKg{m8sa~TR||^x -zH?*-f-l|Zq;GkYEt~~O`56i(Z6gi`*^TxMZuc-f=NMlvry~%u2a>kz*@R^tJ@{7%-A@wfa)Z -z0~dL|Z*tTjEX7ED(M%2gZ|Zi_L4TWr%=BR=y4%;?-COo)8t(xaW)#f?Uhc9^d-1?K -z=UDW-cu8J#%~t1$>T7o{K5NXC7Z^ -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 -zWZQ?eQkgCCqwANoAm&K4|fw*k7ipV -zB1v2pR!seE-oV-2@pAb2ne;%k;&0+LB7z16@)VH1MO9)MHU9kSp)dCNVB91j?4mmO -zv3NP2%#IeX=+W7Ni#8IjoTpc(!3T*dt7!EX8VlAzQq6uvFcs%W{$71OuAvW8Wpseg+*PVYLv?WMN=C|H@$;E_S5fVp$L;GyupU7jZ$kfvC58X?uLqEZijH!v -HqBZh=dic2S - diff --git a/divinemc-server/paper-patches/features/0002-DivineMC-Configuration.patch b/divinemc-server/paper-patches/features/0002-Configuration.patch similarity index 64% rename from divinemc-server/paper-patches/features/0002-DivineMC-Configuration.patch rename to divinemc-server/paper-patches/features/0002-Configuration.patch index d720b57..f3d36e0 100644 --- a/divinemc-server/paper-patches/features/0002-DivineMC-Configuration.patch +++ b/divinemc-server/paper-patches/features/0002-Configuration.patch @@ -1,45 +1,45 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 16:19:01 +0300 -Subject: [PATCH] DivineMC Configuration +Date: Mon, 27 Jan 2025 20:53:24 +0300 +Subject: [PATCH] Configuration diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 472c649e08fa1fb5f050d178d1c49fbb1e5c6adf..0c03829de02f31a15b5f2686d03bf3977e3710cb 100644 +index 6a08a42acdb2ee24b5e403b15fb825f3cb49c968..c3c6e23b4da16025d0f6472290183732f5eb9880 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1104,6 +1104,7 @@ public final class CraftServer implements Server { +@@ -1103,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); org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - Purpur config files -+ org.bxteam.divinemc.configuration.DivineConfig.init((File) console.options.valueOf("divinemc-settings")); // DivineMC - DivineMC config files ++ org.bxteam.divinemc.DivineConfig.init((File) console.options.valueOf("divinemc-settings")); // DivineMC - Configuration 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)) -@@ -1120,6 +1121,7 @@ public final class CraftServer implements Server { +@@ -1119,6 +1120,7 @@ public final class CraftServer implements Server { } world.spigotConfig.init(); // Spigot world.purpurConfig.init(); // Purpur - Purpur config files -+ world.divinemcConfig.init(); // DivineMC - DivineMC config files ++ world.divineConfig.init(); // DivineMC - Configuration } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -3126,6 +3128,13 @@ public final class CraftServer implements Server { - return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); +@@ -3134,6 +3136,13 @@ public final class CraftServer implements Server { } + // Purpur end - Purpur config files -+ // DivineMC start - DivineMC configuration ++ // DivineMC start - Configuration + @Override + public YamlConfiguration getDivineConfig() { -+ return org.bxteam.divinemc.configuration.DivineConfig.config; ++ return org.bxteam.divinemc.DivineConfig.config; + } -+ // DivineMC end - DivineMC configuration ++ // DivineMC end - Configuration + - // Purpur start - Purpur config files @Override - public YamlConfiguration getPurpurConfig() { + public void restart() { + org.spigotmc.RestartCommand.restart(); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 2e1b7f613de8876095ef39bb0341a3f9520c8d5d..8c619b7d72cb153a3204cb9e215f7f5de83e8347 100644 +index 2e1b7f613de8876095ef39bb0341a3f9520c8d5d..5eb36ddf8eea7a84299a91f28a031e2b750975ce 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -183,6 +183,15 @@ public class Main { @@ -47,13 +47,13 @@ index 2e1b7f613de8876095ef39bb0341a3f9520c8d5d..8c619b7d72cb153a3204cb9e215f7f5d .describedAs("Yml file"); // Purpur end - Purpur config files + -+ // DivineMC start - DivineMC config files -+ acceptsAll(asList("divinemc", "divinemc-settings"), "File for divinemc settings") ++ // DivineMC start - Configuration ++ acceptsAll(asList("divinemc", "divinemc-settings"), "File for DivineMC settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("divinemc.yml")) + .describedAs("Yml file"); -+ // DivineMC end - DivineMC config files ++ // DivineMC end - Configuration + // Paper start acceptsAll(asList("server-name"), "Name of the server") diff --git a/divinemc-server/paper-patches/features/0003-Delete-timings.patch b/divinemc-server/paper-patches/features/0003-Delete-timings.patch new file mode 100644 index 0000000..eb2dbc2 --- /dev/null +++ b/divinemc-server/paper-patches/features/0003-Delete-timings.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Mon, 27 Jan 2025 18:42:29 +0300 +Subject: [PATCH] Delete timings + + +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 14949b9b0a14e4b6f38bf04a6246f181db2a7b3f..534510a94694d013fc11f6985088c6cf14dbb695 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 @@ + package io.papermc.paper.plugin.manager; + +-import co.aikar.timings.TimedEventExecutor; + import com.destroystokyo.paper.event.server.ServerExceptionEvent; + import com.destroystokyo.paper.exception.ServerEventException; + import com.google.common.collect.Sets; +@@ -102,7 +101,6 @@ class PaperEventManager { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + +- executor = new TimedEventExecutor(executor, plugin, null, event); + this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } + +@@ -189,7 +187,7 @@ class PaperEventManager { + } + } + +- EventExecutor executor = new TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); ++ EventExecutor executor = EventExecutor.create(method, eventClass); // DivineMC - Delete timings + eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } + return ret; +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java +index 097500a59336db1bbfffcd1aa4cff7a8586e46ec..69341cb3b11409e41b9ff756b11d9bd1b9e6da10 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java +@@ -232,7 +232,7 @@ public class PaperPluginManagerImpl implements PluginManager, DependencyContext + + @Override + public boolean useTimings() { +- return co.aikar.timings.Timings.isTimingsEnabled(); ++ return false; // DivineMC - Delete timings + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c3c6e23b4da16025d0f6472290183732f5eb9880..652acceb96843e8242a0989518dec5c65fbcf953 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -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 +- //target.timings.stopTiming(); // Spigot // Paper + throw ex; + } catch (Throwable ex) { +- //target.timings.stopTiming(); // Spigot // Paper + String msg = "Unhandled exception executing '" + commandLine + "' in " + target; + this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper + throw new CommandException(msg, ex); diff --git a/divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch b/divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch deleted file mode 100644 index 2129618..0000000 --- a/divinemc-server/paper-patches/features/0003-Optimize-default-values-for-configs.patch +++ /dev/null @@ -1,377 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 11 Jan 2025 23:18:11 +0300 -Subject: [PATCH] Optimize default values for configs - - -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 42777adb028fe282c1619aeb5431c442ad5df0d0..3afcf93d1e9519577ca9b6974f23f2258ecaad3e 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -343,9 +343,9 @@ public class GlobalConfiguration extends ConfigurationPart { - public boolean fixEntityPositionDesync = true; - public boolean loadPermissionsYmlBeforePlugins = true; - @Constraints.Min(4) -- public int regionFileCacheSize = 256; -+ public int regionFileCacheSize = 512; // DivineMC - Optimize default values for configs - @Comment("See https://luckformula.emc.gs") -- public boolean useAlternativeLuckFormula = false; -+ public boolean useAlternativeLuckFormula = true; // DivineMC - Optimize default values for configs - public boolean useDimensionTypeForCustomSpawners = false; - public boolean strictAdvancementDimensionCheck = false; - public IntOr.Default compressionLevel = IntOr.Default.USE_DEFAULT; -diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -index a426ba82af695426952bb5e04fa721e6ccff2f89..6ae624f873c77625c7ff9a1b94ff015cc6d321f0 100644 ---- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -@@ -145,9 +145,9 @@ public class WorldConfiguration extends ConfigurationPart { - - public ArmorStands armorStands; - -- public class ArmorStands extends ConfigurationPart { -- public boolean doCollisionEntityLookups = true; -- public boolean tick = true; -+ public class ArmorStands extends ConfigurationPart { // DivineMC - optimize default values for configs -+ public boolean doCollisionEntityLookups = false; -+ public boolean tick = false; - } - - public Markers markers; -@@ -270,8 +270,8 @@ public class WorldConfiguration extends ConfigurationPart { - public Behavior behavior; - - public class Behavior extends ConfigurationPart { -- public boolean disableChestCatDetection = false; -- public boolean spawnerNerfedMobsShouldJump = false; -+ public boolean disableChestCatDetection = true; // DivineMC - Optimize default values for configs -+ public boolean spawnerNerfedMobsShouldJump = true; // DivineMC - Optimize default values for configs - public int experienceMergeMaxValue = -1; - public boolean shouldRemoveDragon = false; - public boolean zombiesTargetTurtleEggs = true; -@@ -295,7 +295,7 @@ public class WorldConfiguration extends ConfigurationPart { - public int playerInsomniaStartTicks = 72000; - public int phantomsSpawnAttemptMinSeconds = 60; - public int phantomsSpawnAttemptMaxSeconds = 119; -- public boolean parrotsAreUnaffectedByPlayerMovement = false; -+ public boolean parrotsAreUnaffectedByPlayerMovement = true; // DivineMC - Optimize default values for configs - @BelowZeroToEmpty - public DoubleOr.Default zombieVillagerInfectionChance = DoubleOr.Default.USE_DEFAULT; - public MobsCanAlwaysPickUpLoot mobsCanAlwaysPickUpLoot; -@@ -306,7 +306,7 @@ public class WorldConfiguration extends ConfigurationPart { - } - - public boolean disablePlayerCrits = false; -- public boolean nerfPigmenFromNetherPortals = false; -+ public boolean nerfPigmenFromNetherPortals = true; // DivineMC - Optimize default values for configs - @Comment("Prevents merging items that are not on the same y level, preventing potential visual artifacts.") - public boolean onlyMergeItemsHorizontally = false; - public PillagerPatrols pillagerPatrols; -@@ -404,7 +404,7 @@ public class WorldConfiguration extends ConfigurationPart { - public class Environment extends ConfigurationPart { - public boolean disableThunder = false; - public boolean disableIceAndSnow = false; -- public boolean optimizeExplosions = false; -+ public boolean optimizeExplosions = true; // DivineMC - Optimize default values for configs - public boolean disableExplosionKnockback = false; - public boolean generateFlatBedrock = false; - public FrostedIce frostedIce; -@@ -453,7 +453,7 @@ public class WorldConfiguration extends ConfigurationPart { - - public class Maps extends ConfigurationPart { - public int itemFrameCursorLimit = 128; -- public int itemFrameCursorUpdateInterval = 10; -+ public int itemFrameCursorUpdateInterval = 20; // DivineMC - Optimize default values for configs - } - - public Fixes fixes; -@@ -479,7 +479,7 @@ public class WorldConfiguration extends ConfigurationPart { - public class Hopper extends ConfigurationPart { - public boolean cooldownWhenFull = true; - public boolean disableMoveEvent = false; -- public boolean ignoreOccludingBlocks = false; -+ public boolean ignoreOccludingBlocks = true; // DivineMC - Optimize default values for configs - } - - public Collisions collisions; -@@ -487,9 +487,9 @@ public class WorldConfiguration extends ConfigurationPart { - public class Collisions extends ConfigurationPart { - public boolean onlyPlayersCollide = false; - public boolean allowVehicleCollisions = true; -- public boolean fixClimbingBypassingCrammingRule = false; -+ public boolean fixClimbingBypassingCrammingRule = true; // DivineMC - Optimize default values for configs - @RequiresSpigotInitialization(MaxEntityCollisionsInitializer.class) -- public int maxEntityCollisions = 8; -+ public int maxEntityCollisions = 2; // DivineMC - Optimize default values for configs - public boolean allowPlayerCrammingDamage = false; - } - -@@ -497,18 +497,31 @@ public class WorldConfiguration extends ConfigurationPart { - - public class Chunks extends ConfigurationPart { - public AutosavePeriod autoSaveInterval = AutosavePeriod.def(); -- public int maxAutoSaveChunksPerTick = 24; -+ public int maxAutoSaveChunksPerTick = 12; // DivineMC - Optimize default values for configs - public int fixedChunkInhabitedTime = -1; -- public boolean preventMovingIntoUnloadedChunks = false; -- public Duration delayChunkUnloadsBy = Duration.of("10s"); -+ public boolean preventMovingIntoUnloadedChunks = true; -+ public Duration delayChunkUnloadsBy = Duration.of("5s"); - public Reference2IntMap> entityPerChunkSaveLimit = Util.make(new Reference2IntOpenHashMap<>(BuiltInRegistries.ENTITY_TYPE.size()), map -> { -- map.defaultReturnValue(-1); -- map.put(EntityType.EXPERIENCE_ORB, -1); -- map.put(EntityType.SNOWBALL, -1); -- map.put(EntityType.ENDER_PEARL, -1); -- map.put(EntityType.ARROW, -1); -- map.put(EntityType.FIREBALL, -1); -- map.put(EntityType.SMALL_FIREBALL, -1); -+ // DivineMC start - Optimize default values for configs -+ map.put(EntityType.EXPERIENCE_ORB, 16); -+ map.put(EntityType.SNOWBALL, 8); -+ map.put(EntityType.ENDER_PEARL, 8); -+ map.put(EntityType.ARROW, 16); -+ map.put(EntityType.FIREBALL, 8); -+ map.put(EntityType.SMALL_FIREBALL, 8); -+ map.put(EntityType.DRAGON_FIREBALL, 3); -+ map.put(EntityType.EGG, 8); -+ map.put(EntityType.EYE_OF_ENDER, 8); -+ map.put(EntityType.FIREWORK_ROCKET, 8); -+ map.put(EntityType.POTION, 8); -+ map.put(EntityType.LLAMA_SPIT, 3); -+ map.put(EntityType.SHULKER_BULLET, 8); -+ map.put(EntityType.SPECTRAL_ARROW, 16); -+ map.put(EntityType.EXPERIENCE_BOTTLE, 3); -+ map.put(EntityType.TRIDENT, 16); -+ map.put(EntityType.WITHER_SKULL, 4); -+ map.put(EntityType.AREA_EFFECT_CLOUD, 8); -+ // DivineMC end - }); - public boolean flushRegionsOnSave = false; - } -@@ -523,13 +536,13 @@ public class WorldConfiguration extends ConfigurationPart { - public TickRates tickRates; - - public class TickRates extends ConfigurationPart { -- public int grassSpread = 1; -- public int containerUpdate = 1; -- public int mobSpawner = 1; -+ public int grassSpread = 4; // DivineMC - Optimize default values for configs -+ public int containerUpdate = 3; // DivineMC - Optimize default values for configs -+ public int mobSpawner = 2; // DivineMC - Optimize default values for configs - public int wetFarmland = 1; - public int dryFarmland = 1; -- public Table, String, Integer> sensor = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "secondarypoisensor", 40)); -- public Table, String, Integer> behavior = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "validatenearbypoi", -1)); -+ public Table, String, Integer> sensor = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "secondarypoisensor", 80)); // DivineMC - Optimize default values for configs -+ public Table, String, Integer> behavior = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "validatenearbypoi", 60)); // DivineMC - Optimize default values for configs - } - - @Setting(FeatureSeedsGeneration.FEATURE_SEEDS_KEY) -@@ -538,7 +551,7 @@ public class WorldConfiguration extends ConfigurationPart { - public class FeatureSeeds extends ConfigurationPart { - @SuppressWarnings("unused") // Is used in FeatureSeedsGeneration - @Setting(FeatureSeedsGeneration.GENERATE_KEY) -- public boolean generateRandomSeedsForAll = false; -+ public boolean generateRandomSeedsForAll = true; // DivineMC - Optimize default values for configs - @Setting(FeatureSeedsGeneration.FEATURES_KEY) - public Reference2LongMap>> features = new Reference2LongOpenHashMap<>(); - -@@ -559,9 +572,9 @@ public class WorldConfiguration extends ConfigurationPart { - - public class Misc extends ConfigurationPart { - public int lightQueueSize = 20; -- public boolean updatePathfindingOnBlockUpdate = true; -+ public boolean updatePathfindingOnBlockUpdate = false; // DivineMC - Optimize default values for configs - public boolean showSignClickCommandFailureMsgsToPlayer = false; -- public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA; -+ public RedstoneImplementation redstoneImplementation = RedstoneImplementation.ALTERNATE_CURRENT; // DivineMC - Optimize default values for configs - public AlternateCurrentUpdateOrder alternateCurrentUpdateOrder = AlternateCurrentUpdateOrder.HORIZONTAL_FIRST_OUTWARD; - public boolean disableEndCredits = false; - public DoubleOr.Default maxLeashDistance = DoubleOr.Default.USE_DEFAULT; -diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index e0d4222a99f22d7130d95cf29b034a98f2f3b76e..ecedb1ba79fa40180ff7eb16d12a4602ab357de3 100644 ---- a/src/main/java/org/spigotmc/SpigotConfig.java -+++ b/src/main/java/org/spigotmc/SpigotConfig.java -@@ -269,7 +269,7 @@ public class SpigotConfig { - - public static boolean saveUserCacheOnStopOnly; - private static void saveUserCacheOnStopOnly() { -- SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean("settings.save-user-cache-on-stop-only", false); -+ SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean("settings.save-user-cache-on-stop-only", true); // DivineMC - Optimize default values for configs - } - - public static double movedWronglyThreshold; -@@ -323,9 +323,9 @@ public class SpigotConfig { - - public static boolean logVillagerDeaths; - public static boolean logNamedDeaths; -- private static void logDeaths() { -- SpigotConfig.logVillagerDeaths = SpigotConfig.getBoolean("settings.log-villager-deaths", true); -- SpigotConfig.logNamedDeaths = SpigotConfig.getBoolean("settings.log-named-deaths", true); -+ private static void logDeaths() { // DivineMC - Optimize default values for configs -+ SpigotConfig.logVillagerDeaths = SpigotConfig.getBoolean("settings.log-villager-deaths", false); -+ SpigotConfig.logNamedDeaths = SpigotConfig.getBoolean("settings.log-named-deaths", false); - } - - public static boolean disablePlayerDataSaving; -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 89e2adbc1e1a0709d03e151e3ffcdbff10a44098..5b305092a808c2b9b339b9072bf7f7bfc00f0b8a 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -135,13 +135,13 @@ public class SpigotWorldConfig { - - public double itemMerge; - private void itemMerge() { -- this.itemMerge = this.getDouble("merge-radius.item", 0.5); -+ this.itemMerge = this.getDouble("merge-radius.item", 3.5); // DivineMC - Optimize default values for configs - this.log("Item Merge Radius: " + this.itemMerge); - } - - public double expMerge; - private void expMerge() { -- this.expMerge = this.getDouble("merge-radius.exp", -1); -+ this.expMerge = this.getDouble("merge-radius.exp", 4.0); // DivineMC - Optimize default values for configs - this.log("Experience Merge Radius: " + this.expMerge); - } - -@@ -174,7 +174,7 @@ public class SpigotWorldConfig { - - public byte mobSpawnRange; - private void mobSpawnRange() { -- this.mobSpawnRange = (byte) getInt("mob-spawn-range", 8); // Paper - Vanilla -+ this.mobSpawnRange = (byte) getInt("mob-spawn-range", 2); // DivineMC - Optimize default values for configs - this.log("Mob Spawn Range: " + this.mobSpawnRange); - } - -@@ -184,14 +184,16 @@ public class SpigotWorldConfig { - this.log("Item Despawn Rate: " + this.itemDespawnRate); - } - -- public int animalActivationRange = 32; -- public int monsterActivationRange = 32; -- public int raiderActivationRange = 64; -- public int miscActivationRange = 16; -+ // DivineMC start - Optimize default values for configs -+ public int animalActivationRange = 16; -+ public int monsterActivationRange = 24; -+ public int raiderActivationRange = 48; -+ public int miscActivationRange = 8; -+ // DivineMC end - Optimize default values for configs - // Paper start - public int flyingMonsterActivationRange = 32; -- public int waterActivationRange = 16; -- public int villagerActivationRange = 32; -+ public int waterActivationRange = 8; // DivineMC - Optimize default values for configs -+ public int villagerActivationRange = 16; // DivineMC - Optimize default values for configs - public int wakeUpInactiveAnimals = 4; - public int wakeUpInactiveAnimalsEvery = 60 * 20; - public int wakeUpInactiveAnimalsFor = 5 * 20; -@@ -208,7 +210,7 @@ public class SpigotWorldConfig { - public int villagersWorkImmunityFor = 20; - public boolean villagersActiveForPanic = true; - // Paper end -- public boolean tickInactiveVillagers = true; -+ public boolean tickInactiveVillagers = false; // DivineMC - Optimize default values for configs - public boolean ignoreSpectatorActivation = false; - - private void activationRange() { -@@ -273,7 +275,7 @@ public class SpigotWorldConfig { - if (SpigotConfig.version < 11) { - this.set("ticks-per.hopper-check", 1); - } -- this.hopperCheck = this.getInt("ticks-per.hopper-check", 1); -+ this.hopperCheck = this.getInt("ticks-per.hopper-check", 8); // DivineMC - Optimize default values for configs - this.hopperAmount = this.getInt("hopper-amount", 1); - this.hopperCanLoadChunks = this.getBoolean("hopper-can-load-chunks", false); - this.log("Hopper Transfer: " + this.hopperTransfer + " Hopper Check: " + this.hopperCheck + " Hopper Amount: " + this.hopperAmount + " Hopper Can Load Chunks: " + this.hopperCanLoadChunks); -@@ -282,7 +284,7 @@ public class SpigotWorldConfig { - public int arrowDespawnRate; - public int tridentDespawnRate; - private void arrowDespawnRate() { -- this.arrowDespawnRate = this.getInt("arrow-despawn-rate", 1200); -+ this.arrowDespawnRate = this.getInt("arrow-despawn-rate", 300); // DivineMC - Optimize default values for configs - this.tridentDespawnRate = this.getInt("trident-despawn-rate", this.arrowDespawnRate); - this.log("Arrow Despawn Rate: " + this.arrowDespawnRate + " Trident Respawn Rate:" + this.tridentDespawnRate); - } -@@ -295,13 +297,13 @@ public class SpigotWorldConfig { - - public boolean nerfSpawnerMobs; - private void nerfSpawnerMobs() { -- this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", false); -+ this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", true); // DivineMC - Optimize default values for configs - this.log("Nerfing mobs spawned from spawners: " + this.nerfSpawnerMobs); - } - - public boolean enableZombiePigmenPortalSpawns; - private void enableZombiePigmenPortalSpawns() { -- this.enableZombiePigmenPortalSpawns = this.getBoolean("enable-zombie-pigmen-portal-spawns", true); -+ this.enableZombiePigmenPortalSpawns = this.getBoolean("enable-zombie-pigmen-portal-spawns", false); // DivineMC - Optimize default values for configs - this.log("Allow Zombie Pigmen to spawn from portal blocks: " + this.enableZombiePigmenPortalSpawns); - } - -@@ -413,7 +415,7 @@ public class SpigotWorldConfig { - - public int hangingTickFrequency; - private void hangingTickFrequency() { -- this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 100); -+ this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 200); // DivineMC - Optimize default values for configs - } - - public int tileMaxTickTime; -diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml -index eef7c125b2689f29cae5464659eacdf33f5695b2..c6b04acb5371a0ac454c5e377bccad5b0972aed8 100644 ---- a/src/main/resources/configurations/bukkit.yml -+++ b/src/main/resources/configurations/bukkit.yml -@@ -18,28 +18,28 @@ settings: - update-folder: update - plugin-profiling: false - connection-throttle: 4000 -- query-plugins: true -+ query-plugins: false - deprecated-verbose: default - shutdown-message: Server closed - minimum-api: none - use-map-color-cache: true - spawn-limits: -- monsters: 70 -- animals: 10 -- water-animals: 5 -- water-ambient: 20 -- water-underground-creature: 5 -- axolotls: 5 -- ambient: 15 -+ monsters: 20 -+ animals: 5 -+ water-animals: 2 -+ water-ambient: 2 -+ water-underground-creature: 3 -+ axolotls: 3 -+ ambient: 1 - chunk-gc: -- period-in-ticks: 600 -+ period-in-ticks: 400 - ticks-per: - animal-spawns: 400 -- monster-spawns: 1 -- water-spawns: 1 -- water-ambient-spawns: 1 -- water-underground-creature-spawns: 1 -- axolotl-spawns: 1 -- ambient-spawns: 1 -+ monster-spawns: 10 -+ water-spawns: 400 -+ water-ambient-spawns: 400 -+ water-underground-creature-spawns: 400 -+ axolotl-spawns: 400 -+ ambient-spawns: 400 - autosave: 6000 - aliases: now-in-commands.yml -diff --git a/src/main/resources/configurations/commands.yml b/src/main/resources/configurations/commands.yml -index 18f54571200e2eca09a39b88f170fe7b99d8618f..1b57d51d92c5c69286800d10baeaa936fa208cae 100644 ---- a/src/main/resources/configurations/commands.yml -+++ b/src/main/resources/configurations/commands.yml -@@ -12,5 +12,3 @@ - command-block-overrides: [] - ignore-vanilla-permissions: false - aliases: -- icanhasbukkit: -- - "version $1-" diff --git a/divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch b/divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch new file mode 100644 index 0000000..1f343f3 --- /dev/null +++ b/divinemc-server/paper-patches/features/0004-Implement-Secure-Seed.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Tue, 28 Jan 2025 00:54:57 +0300 +Subject: [PATCH] Implement Secure Seed + +Original license: GPLv3 +Original project: https://github.com/plasmoapp/matter + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index de8b9048c8395c05b8688bc9d984b8ad680f15b3..98bd60111797225f3be5e2a19e25d654379ca30d 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 ++ // DivineMC start - Implement Secure Seed ++ boolean isSlimeChunk = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? 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; ++ // DivineMC end - Implement Secure Seed + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 652acceb96843e8242a0989518dec5c65fbcf953..be2859b2bb31bdf342c1e8fb14ccac8b6d215439 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1402,7 +1402,11 @@ public final class CraftServer implements Server { + registryAccess = levelDataAndDimensions.dimensions().dimensionsRegistryAccess(); + } else { + LevelSettings levelSettings; +- WorldOptions worldOptions = new WorldOptions(creator.seed(), creator.generateStructures(), false); ++ // DivineMC start - Implement Secure Seed ++ WorldOptions worldOptions = org.bxteam.divinemc.DivineConfig.enableSecureSeed ++ ? new WorldOptions(creator.seed(), su.plo.matter.Globals.createRandomWorldSeed(), creator.generateStructures(), false) ++ : new WorldOptions(creator.seed(), creator.generateStructures(), false); ++ // DivineMC end - Implement 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/divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch b/divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch deleted file mode 100644 index f1c0c6a..0000000 --- a/divinemc-server/paper-patches/features/0004-Remove-Spigot-tick-limiter.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sun, 12 Jan 2025 01:47:27 +0300 -Subject: [PATCH] Remove Spigot tick limiter - - -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 5b305092a808c2b9b339b9072bf7f7bfc00f0b8a..c9ff04efcdf063326119b9f65630979d38dc7abf 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -418,6 +418,7 @@ public class SpigotWorldConfig { - this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 200); // DivineMC - Optimize default values for configs - } - -+ /* DivineMC - remove tick limiter - public int tileMaxTickTime; - public int entityMaxTickTime; - private void maxTickTimes() { -@@ -425,6 +426,7 @@ public class SpigotWorldConfig { - this.entityMaxTickTime = this.getInt("max-tick-time.entity", 50); - this.log("Tile Max Tick Time: " + this.tileMaxTickTime + "ms Entity max Tick Time: " + this.entityMaxTickTime + "ms"); - } -+ */ - - public int thunderChance; - private void thunderChance() { -diff --git a/src/main/java/org/spigotmc/TickLimiter.java b/src/main/java/org/spigotmc/TickLimiter.java -deleted file mode 100644 -index 961489499e220d71339771dcabf151edeaf6d231..0000000000000000000000000000000000000000 ---- a/src/main/java/org/spigotmc/TickLimiter.java -+++ /dev/null -@@ -1,20 +0,0 @@ --package org.spigotmc; -- --public class TickLimiter { -- -- private final int maxTime; -- private long startTime; -- -- public TickLimiter(int maxTime) { -- this.maxTime = maxTime; -- } -- -- public void initTick() { -- this.startTime = System.currentTimeMillis(); -- } -- -- public boolean shouldContinue() { -- long remaining = System.currentTimeMillis() - this.startTime; -- return remaining < this.maxTime; -- } --} diff --git a/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch b/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch new file mode 100644 index 0000000..a2bdc34 --- /dev/null +++ b/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch @@ -0,0 +1,613 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 29 Jan 2025 01:41:25 +0300 +Subject: [PATCH] Parallel world ticking + + +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 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09357f2ef583af04f6b8dc5ba67ef7e1d83e3462 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +@@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; + public class TickThread extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); ++ public static final boolean HARD_THROW = !Boolean.getBoolean("divinemc.disableHardThrow"); // DivineMC - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION! + + private static String getThreadContext() { + return "thread=" + Thread.currentThread().getName(); +@@ -26,14 +27,14 @@ public class TickThread extends Thread { + public static void ensureTickThread(final String reason) { + if (!isTickThread()) { + LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); +- throw new IllegalStateException(reason); ++ if (HARD_THROW) throw new IllegalStateException(reason); // DivineMC - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION! + } + } + + public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { + if (!isTickThreadFor(world, pos)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos; ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -42,7 +43,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) { + if (!isTickThreadFor(world, pos, blockRadius)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius; ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -60,7 +61,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { + if (!isTickThreadFor(world, chunkX, chunkZ)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ); ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -69,7 +70,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Entity entity, final String reason) { + if (!isTickThreadFor(entity)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity); ++ reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -78,7 +79,7 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { + if (!isTickThreadFor(world, aabb)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb; ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -87,12 +88,69 @@ public class TickThread extends Thread { + public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) { + if (!isTickThreadFor(world, blockX, blockZ)) { + final String ex = "Thread failed main thread check: " + +- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ); ++ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ) + " - " + getTickThreadInformation(world.getServer()); // DivineMC - parallel world ticking + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } + } + ++ // DivineMC start - parallel world ticking ++ public static void ensureTickThread(final net.minecraft.server.level.ServerLevel world, final String reason) { ++ if (!isTickThreadFor(world)) { ++ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static void ensureOnlyTickThread(final String reason) { ++ boolean isTickThread = isTickThread(); ++ boolean isServerLevelTickThread = isServerLevelTickThread(); ++ if (!isTickThread || isServerLevelTickThread) { ++ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason, new Throwable()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static void ensureTickThreadOrAsyncThread(final net.minecraft.server.level.ServerLevel world, final String reason) { ++ boolean isValidTickThread = isTickThreadFor(world); ++ boolean isAsyncThread = !isTickThread(); ++ boolean isValid = isAsyncThread || isValidTickThread; ++ if (!isValid) { ++ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { ++ StringBuilder sb = new StringBuilder(); ++ Thread currentThread = Thread.currentThread(); ++ sb.append("Is tick thread? "); ++ sb.append(currentThread instanceof TickThread); ++ sb.append("; Is server level tick thread? "); ++ sb.append(currentThread instanceof ServerLevelTickThread); ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ sb.append("; Currently ticking level: "); ++ if (serverLevelTickThread.currentlyTickingServerLevel != null) { ++ sb.append(serverLevelTickThread.currentlyTickingServerLevel.getWorld().getName()); ++ } else { ++ sb.append("null"); ++ } ++ } ++ sb.append("; Is iterating over levels? "); ++ sb.append(minecraftServer.isIteratingOverLevels); ++ sb.append("; Are we going to hard throw? "); ++ sb.append(HARD_THROW); ++ return sb.toString(); ++ } ++ ++ public static boolean isServerLevelTickThread() { ++ return Thread.currentThread() instanceof ServerLevelTickThread; ++ } ++ // DivineMC end - parallel world ticking ++ + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); +@@ -126,8 +184,13 @@ public class TickThread extends Thread { + return false; + } + ++ // DivineMC start - parallel world ticking + public static boolean isTickThreadFor(final Level world, final BlockPos pos) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { +@@ -135,38 +198,99 @@ public class TickThread extends Thread { + } + + public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final Vec3 pos) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final AABB aabb) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { +- return isTickThread(); ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; ++ } ++ ++ public static boolean isTickThreadFor(final Level world) { ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == world; ++ } else return currentThread instanceof TickThread; + } + + public static boolean isTickThreadFor(final Entity entity) { +- return isTickThread(); ++ if (entity == null) { ++ return true; ++ } ++ ++ Thread currentThread = Thread.currentThread(); ++ ++ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { ++ return serverLevelTickThread.currentlyTickingServerLevel == entity.level(); ++ } else return currentThread instanceof TickThread; ++ } ++ ++ public static class ServerLevelTickThread extends TickThread { ++ public ServerLevelTickThread(String name) { ++ super(name); ++ } ++ ++ public ServerLevelTickThread(Runnable run, String name) { ++ super(run, name); ++ } ++ ++ public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel; + } ++ // DivineMC end - parallel world ticking + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ca60f91ef012c94174a0803eb77699ba9ecff5e1..35afd7268a6f8c5c9d0da751459867c1d8e404bf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -446,7 +446,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean unloadChunkRequest(int x, int z) { +- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + if (this.isChunkLoaded(x, z)) { + this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); + } +@@ -472,6 +472,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean refreshChunk(int x, int z) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (playerChunk == null) return false; + +@@ -522,7 +523,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean loadChunk(int x, int z, boolean generate) { +- org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + warnUnsafeChunk("loading a faraway chunk", x, z); // Paper + ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + +@@ -750,6 +751,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + this.world.captureTreeGeneration = true; + this.world.captureBlockStates = true; + boolean grownTree = this.generateTree(loc, type); +@@ -865,6 +867,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { + // Paper end - expand explosion API ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + net.minecraft.world.level.Level.ExplosionInteraction explosionType; + if (!breakBlocks) { + explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks +@@ -956,6 +959,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper + // Transient load for this tick + return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); +@@ -986,6 +990,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public void setBiome(int x, int y, int z, Holder bb) { + BlockPos pos = new BlockPos(x, 0, z); ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + if (this.world.hasChunkAt(pos)) { + net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); + +@@ -2289,6 +2294,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) + getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); + } + // Paper end +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b330450fbc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -75,6 +75,11 @@ public class CraftBlock implements Block { + } + + public net.minecraft.world.level.block.state.BlockState getNMS() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.world.getBlockState(this.position); + } + +@@ -157,6 +162,11 @@ public class CraftBlock implements Block { + } + + private void setData(final byte data, int flag) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); + } + +@@ -198,6 +208,11 @@ public class CraftBlock implements Block { + } + + public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup + if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes + // SPIGOT-4612: faster - just clear tile +@@ -343,18 +358,33 @@ public class CraftBlock implements Block { + + @Override + public Biome getBiome() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); + } + + // Paper start + @Override + public Biome getComputedBiome() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); + } + // Paper end + + @Override + public void setBiome(Biome bio) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); + } + +@@ -402,6 +432,11 @@ public class CraftBlock implements Block { + + @Override + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); + + Block relative = this.getRelative(face); +@@ -414,6 +449,11 @@ public class CraftBlock implements Block { + + @Override + public int getBlockPower(BlockFace face) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + int power = 0; + net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); + int x = this.getX(); +@@ -484,6 +524,11 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + return this.breakNaturally(null); + } + +@@ -543,6 +588,11 @@ public class CraftBlock implements Block { + + @Override + public boolean applyBoneMeal(BlockFace face) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + Direction direction = CraftBlock.blockFaceToNotch(face); + BlockFertilizeEvent event = null; + ServerLevel world = this.getCraftWorld().getHandle(); +@@ -554,8 +604,10 @@ public class CraftBlock implements Block { + world.captureTreeGeneration = false; + + if (world.capturedBlockStates.size() > 0) { +- TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; ++ // DivineMC start - parallel world ticking ++ TreeType treeType = SaplingBlock.treeTypeRT.get(); ++ SaplingBlock.treeTypeRT.set(null); ++ // DivineMC end - parallel world ticking + List blocks = new ArrayList<>(world.capturedBlockStates.values()); + world.capturedBlockStates.clear(); + StructureGrowEvent structureEvent = null; +@@ -644,6 +696,11 @@ public class CraftBlock implements Block { + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + Preconditions.checkArgument(start != null, "Location start cannot be null"); + Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); + start.checkFinite(); +@@ -685,6 +742,11 @@ public class CraftBlock implements Block { + + @Override + public boolean canPlace(BlockData data) { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + Preconditions.checkArgument(data != null, "BlockData cannot be null"); + net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); + net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); +@@ -719,6 +781,11 @@ public class CraftBlock implements Block { + + @Override + public void tick() { ++ // DivineMC start - parallel world ticking ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking + final ServerLevel level = this.world.getMinecraftWorld(); + this.getNMS().tick(level, this.position, level.random); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 768d3f93da2522d467183654260a8bd8653588b1..762bb2827dc1c0c0649a4cb3d8b0c8c0c9ea95d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -25,7 +25,7 @@ public abstract class CraftBlockEntityState extends Craft + private final T tileEntity; + private final T snapshot; + public boolean snapshotDisabled; // Paper +- public static boolean DISABLE_SNAPSHOT = false; // Paper ++ public static ThreadLocal DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // DivineMC - parallel world ticking + + public CraftBlockEntityState(World world, T tileEntity) { + super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); +@@ -34,8 +34,10 @@ public abstract class CraftBlockEntityState extends Craft + + try { // Paper - Show blockstate location if we failed to read it + // Paper start +- this.snapshotDisabled = DISABLE_SNAPSHOT; +- if (DISABLE_SNAPSHOT) { ++ // DivineMC start - parallel world ticking ++ this.snapshotDisabled = DISABLE_SNAPSHOT.get(); ++ if (DISABLE_SNAPSHOT.get()) { ++ // DivineMC end - parallel world ticking + this.snapshot = this.tileEntity; + } else { + this.snapshot = this.createSnapshot(tileEntity); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..951e47811e861dffd59cc39e2dcd6fd68900fc72 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -215,6 +215,12 @@ public class CraftBlockState implements BlockState { + LevelAccessor access = this.getWorldHandle(); + CraftBlock block = this.getBlock(); + ++ // DivineMC start - parallel world ticking ++ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // DivineMC end - parallel world ticking ++ + if (block.getType() != this.getType()) { + if (!force) { + return false; +@@ -350,6 +356,7 @@ public class CraftBlockState implements BlockState { + + @Override + public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // DivineMC - parallel world ticking + this.requirePlaced(); + net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..cfc4237211b994a222983a2d1f879cb0f515b581 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -249,8 +249,8 @@ public final class CraftBlockStates { + net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS(); + BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition); + // Paper start - block state snapshots +- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; +- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; ++ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get(); // DivineMC - parallel world ticking ++ CraftBlockEntityState.DISABLE_SNAPSHOT.set(!useSnapshot); // DivineMC - parallel world ticking + try { + // Paper end + CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity); +@@ -258,7 +258,7 @@ public final class CraftBlockStates { + return blockState; + // Paper start + } finally { +- CraftBlockEntityState.DISABLE_SNAPSHOT = prev; ++ CraftBlockEntityState.DISABLE_SNAPSHOT.set(prev); // DivineMC - parallel world ticking + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 822ee4a2515ad1d4400bafeaf7177622e88b4aaf..69c44017e7ca2861200c83a16fa9dacaa822d505 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -960,7 +960,7 @@ public class CraftEventFactory { + return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2); + } + +- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // DivineMC - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts) + + public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { + // Suppress during worldgen +@@ -972,7 +972,7 @@ public class CraftEventFactory { + CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); + state.setData(block); + +- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); ++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // DivineMC - parallel world ticking + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +@@ -2242,7 +2242,7 @@ public class CraftEventFactory { + CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); +- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { ++ if (!net.minecraft.world.level.block.DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking + if (!event.callEvent()) { + return itemStack; + } diff --git a/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch b/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch new file mode 100644 index 0000000..346e7cf --- /dev/null +++ b/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:10:42 +0300 +Subject: [PATCH] Skip EntityScheduler's executeTick checks if there isn't any + tasks to be run + +Original project: https://github.com/SparklyPower/SparklyPaper + +diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +index c03608fec96b51e1867f43d8f42e5aefb1520e46..eda35b81c36ca8ebe4f9487cb41e2b0c4cbfc686 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 { + * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. + */ + public final CraftEntity entity; ++ public final net.minecraft.server.MinecraftServer server; // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + + private static final record ScheduledTask(Consumer run, Consumer retired) {} + +@@ -46,7 +47,8 @@ public final class EntityScheduler { + + private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); + +- public EntityScheduler(final CraftEntity entity) { ++ public EntityScheduler(final net.minecraft.server.MinecraftServer server, final CraftEntity entity) { // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ this.server = Validate.notNull(server); + this.entity = Validate.notNull(entity); + } + +@@ -61,15 +63,15 @@ public final class EntityScheduler { + * @throws IllegalStateException If the scheduler is already retired. + */ + public void retire() { ++ final Entity thisEntity = this.entity.getHandleRaw(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + synchronized (this.stateLock) { + if (this.tickCount == RETIRED_TICK_COUNT) { + throw new IllegalStateException("Already retired"); + } + this.tickCount = RETIRED_TICK_COUNT; ++ this.server.entitiesWithScheduledTasks.remove(thisEntity); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + } + +- final Entity thisEntity = this.entity.getHandleRaw(); +- + // correctly handle and order retiring while running executeTick + for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { + final ScheduledTask task = this.currentlyExecuting.pollFirst(); +@@ -124,6 +126,7 @@ public final class EntityScheduler { + if (this.tickCount == RETIRED_TICK_COUNT) { + return false; + } ++ this.server.entitiesWithScheduledTasks.add(this.entity.getHandleRaw()); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { + return new ArrayList<>(); + }).add(task); +@@ -143,6 +146,12 @@ public final class EntityScheduler { + TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); + final List toRun; + synchronized (this.stateLock) { ++ // DivineMC start - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ if (this.currentlyExecuting.isEmpty() && this.oneTimeDelayed.isEmpty()) { ++ this.server.entitiesWithScheduledTasks.remove(thisEntity); ++ return; ++ } ++ // DivineMC end - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + if (this.tickCount == RETIRED_TICK_COUNT) { + throw new IllegalStateException("Ticking retired scheduler"); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 8635cd772c5c2ae0ba326812ff2a1a179285a86f..614e407814fe47dab58fbcbc49d8e9dd54b4245e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -75,7 +75,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); + protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + // Paper start - Folia shedulers +- public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); ++ public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler; // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); + + @Override +@@ -88,6 +88,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.server = server; + this.entity = entity; + this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); ++ this.taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this.entity.getServer(), this); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + } + + // Purpur start - Fire Immunity API diff --git a/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch b/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch new file mode 100644 index 0000000..161fad0 --- /dev/null +++ b/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 19:52:39 +0300 +Subject: [PATCH] Optimize canSee checks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 36fc0ec6e21af31e10f63b6bb3952008530b8f81..e744c4a13092f20a591d092fe537f790d23d1db1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -214,7 +214,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private boolean hasPlayedBefore = false; + private final ConversationTracker conversationTracker = new ConversationTracker(); + private final Set channels = new HashSet(); +- private final Map>> invertedVisibilityEntities = new HashMap<>(); ++ private final Map>> invertedVisibilityEntities = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // DivineMC - optimize canSee checks + private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private int hash = 0; +@@ -2270,9 +2270,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean canSee(org.bukkit.entity.Entity entity) { +- return this.equals(entity) || entity.isVisibleByDefault() ^ this.invertedVisibilityEntities.containsKey(entity.getUniqueId()); // SPIGOT-7312: Can always see self ++ return this.equals(entity) || entity.isVisibleByDefault() ^ (!invertedVisibilityEntities.isEmpty() && this.invertedVisibilityEntities.containsKey(entity.getUniqueId())); // SPIGOT-7312: Can always see self // DivineMC - optimize canSee checks + } + ++ // DivineMC start - optimize canSee checks ++ public boolean canSeeChunkMapUpdatePlayer(org.bukkit.entity.Entity entity) { ++ return entity.isVisibleByDefault() ^ (!invertedVisibilityEntities.isEmpty() && this.invertedVisibilityEntities.containsKey(entity.getUniqueId())); // SPIGOT-7312: Can always see self // SparklyPaper - optimize canSee checks ++ } ++ // DivineMC end - optimize canSee checks ++ + public boolean canSeePlayer(UUID uuid) { + org.bukkit.entity.Entity entity = this.getServer().getPlayer(uuid); + diff --git a/divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch b/divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch new file mode 100644 index 0000000..f0f4e07 --- /dev/null +++ b/divinemc-server/paper-patches/features/0008-Verify-Minecraft-EULA-earlier.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 5 Feb 2025 17:48:56 +0300 +Subject: [PATCH] Verify Minecraft EULA earlier + + +diff --git a/src/main/java/io/papermc/paper/PaperBootstrap.java b/src/main/java/io/papermc/paper/PaperBootstrap.java +index d543b1b107ab8d3eeb1fc3c1cadf489928d2786e..b25afd2a33a61cfbe3dabe65a26aca0669329e32 100644 +--- a/src/main/java/io/papermc/paper/PaperBootstrap.java ++++ b/src/main/java/io/papermc/paper/PaperBootstrap.java +@@ -1,8 +1,11 @@ + package io.papermc.paper; + ++import java.nio.file.Path; ++import java.nio.file.Paths; + import java.util.List; + import joptsimple.OptionSet; + import net.minecraft.SharedConstants; ++import net.minecraft.server.Eula; + import net.minecraft.server.Main; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; +@@ -16,6 +19,22 @@ public final class PaperBootstrap { + public static void boot(final OptionSet options) { + SharedConstants.tryDetectVersion(); + ++ // DivineMC start - Verify Minecraft EULA earlier ++ Path path2 = Paths.get("eula.txt"); ++ Eula eula = new Eula(path2); ++ boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree"); ++ if (eulaAgreed) { ++ LOGGER.error("You have used the Spigot command line EULA agreement flag."); ++ LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA)."); ++ LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately."); ++ } ++ if (!eula.hasAgreedToEULA() && !eulaAgreed) { ++ LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); ++ return; ++ } ++ System.out.println("Loading libraries, please wait..."); // Restore CraftBukkit log ++ // DivineMC end - Verify Minecraft EULA earlier ++ + getStartupVersionMessages().forEach(LOGGER::info); + + Main.main(options); diff --git a/divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch b/divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch new file mode 100644 index 0000000..998469b --- /dev/null +++ b/divinemc-server/paper-patches/features/0009-Add-chunk-worker-algorithm.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 22 Feb 2025 02:33:28 +0300 +Subject: [PATCH] Add chunk worker algorithm + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +index 632920e04686d8a0fd0a60e87348be1fe7862a3c..38b8cdac418ab2308c0392be49289356cbe81fb7 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +@@ -3,6 +3,8 @@ package ca.spottedleaf.moonrise.common.util; + import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; + import ca.spottedleaf.moonrise.common.PlatformHooks; + import com.mojang.logging.LogUtils; ++import org.bxteam.divinemc.DivineConfig; ++import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; + import org.slf4j.Logger; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; +@@ -38,26 +40,16 @@ public final class MoonriseCommon { + public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); + + public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { +- int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; +- if (defaultWorkerThreads <= 4) { +- defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; +- } else { +- defaultWorkerThreads = defaultWorkerThreads / 2; +- } +- defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); +- +- int workerThreads = configWorkerThreads; +- +- if (workerThreads <= 0) { +- workerThreads = defaultWorkerThreads; +- } +- +- final int ioThreads = Math.max(1, configIoThreads); ++ // DivineMC start - Add chunk worker algorithm ++ ChunkSystemAlgorithms algorithm = DivineConfig.chunkWorkerAlgorithm; ++ int workerThreads = algorithm.evalWorkers(configWorkerThreads, configIoThreads); ++ int ioThreads = algorithm.evalIO(configWorkerThreads, configIoThreads); + + WORKER_POOL.adjustThreadCount(workerThreads); + IO_POOL.adjustThreadCount(ioThreads); + +- LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ LOGGER.info("ChunkSystem using '{}' algorithm, {} worker threads, {} I/O threads", algorithm.asDebugString(), workerThreads, ioThreads); ++ // DivineMC end - Add chunk worker algorithm + } + + public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( diff --git a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch new file mode 100644 index 0000000..017d982 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java +@@ -4,7 +_,7 @@ + + public final class MoonriseConstants { + +- public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); ++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", org.bxteam.divinemc.DivineConfig.maxViewDistance); // DivineMC - Configurable view distance + + private MoonriseConstants() {} + diff --git a/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch new file mode 100644 index 0000000..9b55c68 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/command/MSPTCommand.java.patch @@ -0,0 +1,50 @@ +--- a/src/main/java/io/papermc/paper/command/MSPTCommand.java ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -78,6 +_,47 @@ + ) + ) + ); ++ ++ // DivineMC start - MSPT Tracking for each world ++ sender.sendMessage(text()); ++ sender.sendMessage(text().content("World tick times ").color(GOLD) ++ .append(text().color(YELLOW) ++ .append( ++ text("("), ++ text("avg", GRAY), ++ text("/"), ++ text("min", GRAY), ++ text("/"), ++ text("max", GRAY), ++ text(")") ++ ) ++ ).append( ++ text(" from last 5s"), ++ text(",", GRAY), ++ text(" 10s"), ++ text(",", GRAY), ++ text(" 1m"), ++ text(":", YELLOW) ++ ) ++ ); ++ for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) { ++ List worldTimes = new ArrayList<>(); ++ worldTimes.addAll(eval(serverLevel.tickTimes5s.getTimes())); ++ worldTimes.addAll(eval(serverLevel.tickTimes10s.getTimes())); ++ worldTimes.addAll(eval(serverLevel.tickTimes60s.getTimes())); ++ ++ sender.sendMessage(text().content("◴ " + serverLevel.getWorld().getName() + ": ").color(GOLD) ++ .append(text().color(GRAY) ++ .append( ++ worldTimes.get(0), SLASH, worldTimes.get(1), SLASH, worldTimes.get(2), text(", ", YELLOW), ++ worldTimes.get(3), SLASH, worldTimes.get(4), SLASH, worldTimes.get(5), text(", ", YELLOW), ++ worldTimes.get(6), SLASH, worldTimes.get(7), SLASH, worldTimes.get(8) ++ ) ++ ) ++ ); ++ } ++ // DivineMC end - MSPT Tracking for each world ++ + return true; + } + diff --git a/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch new file mode 100644 index 0000000..a2caa3a --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch @@ -0,0 +1,27 @@ +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -36,14 +_,21 @@ + + // SimplePluginManager + public void callEvent(@NotNull Event event) { ++ // DivineMC start - Skip event if no listeners ++ RegisteredListener[] listeners = event.getHandlers().getRegisteredListeners(); ++ if (listeners.length == 0) return; ++ // DivineMC end - Skip event if no listeners + 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()) { ++ // DivineMC start - Multithreaded tracker ++ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent); ++ return; ++ } ++ // DivineMC end - Multithreaded tracker + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } +- +- HandlerList handlers = event.getHandlers(); +- RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { diff --git a/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch new file mode 100644 index 0000000..6a7636e --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -989,7 +_,7 @@ + + @Override + public List getWorlds() { +- return new ArrayList(this.worlds.values()); ++ return new it.unimi.dsi.fastutil.objects.ObjectArrayList(this.worlds.values()); // DivineMC - optimize getWorlds + } + + @Override diff --git a/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch new file mode 100644 index 0000000..ddc0dd6 --- /dev/null +++ b/divinemc-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1976,7 +_,7 @@ + BlockFormEvent event = (entity == null) ? new BlockFormEvent(blockState.getBlock(), blockState) : new EntityBlockFormEvent(entity.getBukkitEntity(), blockState.getBlock(), blockState); + world.getCraftServer().getPluginManager().callEvent(event); + +- if (!event.isCancelled()) { ++ if (!event.isCancelled() && (BlockFormEvent.getHandlerList().getRegisteredListeners().length != 0)) { // DivineMC - skip block update if no listeners + blockState.update(true); + } + diff --git a/divinemc-server/src/c/CMakeLists.txt b/divinemc-server/src/c/CMakeLists.txt new file mode 100644 index 0000000..30597be --- /dev/null +++ b/divinemc-server/src/c/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.25) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + +project(c2me-opts-natives-math C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_COMPILER clang) + +SET(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g") + +set(CMAKE_C_STANDARD_LIBRARIES "") +set(CMAKE_CXX_STANDARD_LIBRARIES "") + +add_library(c2me-opts-natives-math SHARED + exports.c + system_isa_x86_64.c + exports_x86_64_nogather.c + system_isa_aarch64.c + flibc.c +) + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") + set_source_files_properties(exports_x86_64_nogather.c PROPERTIES COMPILE_FLAGS "-mno-gather -mno-scatter") +endif() + +execute_process(COMMAND llvm-config --prefix OUTPUT_VARIABLE LLVM_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE) + +target_include_directories(c2me-opts-natives-math PRIVATE includes/) +target_compile_options(c2me-opts-natives-math PRIVATE $<$:-Wall -Wextra -Wpedantic -ffreestanding -ffile-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-compilation-dir=. -fdebug-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-prefix-map=${LLVM_PREFIX}=.../llvm-prefix -fno-math-errno -mprefer-vector-width=512 -ffp-contract=off -Rpass-analysis=loop-vectorize -mno-stack-arg-probe -fsave-optimization-record "SHELL:-mllvm -extra-vectorizer-passes" "SHELL:-mllvm -slp-vectorize-hor-store" "SHELL:-mllvm -slp-min-tree-size=1" "SHELL:-mllvm -slp-min-reg-size=64" "SHELL:-mllvm -slp-threshold=-1" "SHELL:-mllvm -enable-epilogue-vectorization">) +target_link_options(c2me-opts-natives-math PRIVATE -v -nostdlib -fuse-ld=lld -ffile-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-compilation-dir=. -fdebug-prefix-map=${CMAKE_SOURCE_DIR}=. -fdebug-prefix-map=${LLVM_PREFIX}=.../llvm-prefix) + diff --git a/divinemc-server/src/c/exports.c b/divinemc-server/src/c/exports.c new file mode 100644 index 0000000..6d5dfd8 --- /dev/null +++ b/divinemc-server/src/c/exports.c @@ -0,0 +1,34 @@ +#include +#include + +TARGET_IMPL(c2me_natives_noise_perlin_sample, double, (const aligned_uint32_ptr permutations, const double originX, + const double originY, const double originZ, const double x, + const double y, const double z, const double yScale, + const double yMax) { + return math_noise_perlin_sample(permutations, originX, originY, originZ, x, y, z, yScale, yMax); +}) + +TARGET_IMPL(c2me_natives_noise_perlin_double, double, (const double_octave_sampler_data_t *const data, + const double x, const double y, const double z) { + return math_noise_perlin_double_octave_sample(data, x, y, z); +}) + +TARGET_IMPL(c2me_natives_noise_perlin_double_batch, void, (const double_octave_sampler_data_t *const data, + double *const res, const double *const x, + const double *const y, const double *const z, + const uint32_t length) { + math_noise_perlin_double_octave_sample_batch(data, res, x, y, z, length); +}) + +TARGET_IMPL(c2me_natives_noise_interpolated, double, (const interpolated_noise_sampler_t *const data, + const double x, const double y, const double z) { + return math_noise_perlin_interpolated_sample(data, x, y, z); +}) + +TARGET_IMPL(c2me_natives_end_islands_sample, float, (const aligned_uint32_ptr simplex_permutations, const int32_t x, const int32_t z) { + return math_end_islands_sample(simplex_permutations, x, z); +}) + +TARGET_IMPL(c2me_natives_biome_access_sample, uint32_t, (const int64_t theSeed, const int32_t x, const int32_t y, const int32_t z) { + return math_biome_access_sample(theSeed, x, y, z); +}) diff --git a/divinemc-server/src/c/exports_x86_64_nogather.c b/divinemc-server/src/c/exports_x86_64_nogather.c new file mode 100644 index 0000000..11ab8cd --- /dev/null +++ b/divinemc-server/src/c/exports_x86_64_nogather.c @@ -0,0 +1,9 @@ +#ifdef __x86_64__ + +#define GATHER_DISABLED 1 + +#include "exports.c" + +#endif + +typedef int make_iso_compiler_happy; diff --git a/divinemc-server/src/c/flibc.c b/divinemc-server/src/c/flibc.c new file mode 100644 index 0000000..0ae6de6 --- /dev/null +++ b/divinemc-server/src/c/flibc.c @@ -0,0 +1,672 @@ +#include +#include +#include + +typedef int make_iso_compilers_happy; + +#ifdef WIN32 + +// ld.lld: error: : undefined symbol: DllMainCRTStartup +int __stdcall DllMainCRTStartup(void* instance, unsigned reason, void* reserved) +{ + (void) instance; + (void) reason; + (void) reserved; + return 1; +} + +// ld.lld: error: undefined symbol: _fltused +int _fltused = 0; + +// ld.lld: error: undefined symbol: abort +void abort(void) +{ + __builtin_trap(); +} + +#endif // WIN32 + +/* +The following code is from musl, original license below: + +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + */ + +// src/internal/libm.h +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t m; + uint16_t se; + } i; +}; +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +/* This is the m68k variant of 80-bit long double, and this definition only works + * on archs where the alignment requirement of uint64_t is <= 4. */ +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t pad; + uint64_t m; + } i; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t lo; + uint32_t mid; + uint16_t top; + uint16_t se; + } i; + struct { + uint64_t lo; + uint64_t hi; + } i2; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t top; + uint32_t mid; + uint64_t lo; + } i; + struct { + uint64_t hi; + uint64_t lo; + } i2; +}; +#else +#error Unsupported long double representation +#endif + +/* Support non-nearest rounding mode. */ +#define WANT_ROUNDING 1 +/* Support signaling NaNs. */ +#define WANT_SNAN 0 + +#if WANT_SNAN +#error SNaN is unsupported +#else +#define issignalingf_inline(x) 0 +#define issignaling_inline(x) 0 +#endif + +#ifndef TOINT_INTRINSICS +#define TOINT_INTRINSICS 0 +#endif + +#if TOINT_INTRINSICS +/* Round x to nearest int in all rounding modes, ties have to be rounded + consistently with converttoint so the results match. If the result + would be outside of [-2^31, 2^31-1] then the semantics is unspecified. */ +static double_t roundtoint(double_t); + +/* Convert x to nearest int in all rounding modes, ties have to be rounded + consistently with roundtoint. If the result is not representible in an + int32_t then the semantics is unspecified. */ +static int32_t converttoint(double_t); +#endif + +/* Helps static branch prediction so hot path can be better optimized. */ +#ifdef __GNUC__ +#define predict_true(x) __builtin_expect(!!(x), 1) +#define predict_false(x) __builtin_expect(x, 0) +#else +#define predict_true(x) (x) +#define predict_false(x) (x) +#endif + +/* Evaluate an expression as the specified type. With standard excess + precision handling a type cast or assignment is enough (with + -ffloat-store an assignment is required, in old compilers argument + passing and return statement may not drop excess precision). */ + +static inline float eval_as_float(float x) { + float y = x; + return y; +} + +static inline double eval_as_double(double x) { + double y = x; + return y; +} + +/* fp_barrier returns its input, but limits code transformations + as if it had a side-effect (e.g. observable io) and returned + an arbitrary value. */ + +#ifndef fp_barrierf +#define fp_barrierf fp_barrierf + +static inline float fp_barrierf(float x) { + volatile float y = x; + return y; +} + +#endif + +#ifndef fp_barrier +#define fp_barrier fp_barrier + +static inline double fp_barrier(double x) { + volatile double y = x; + return y; +} + +#endif + +#ifndef fp_barrierl +#define fp_barrierl fp_barrierl + +static inline long double fp_barrierl(long double x) { + volatile long double y = x; + return y; +} + +#endif + +/* fp_force_eval ensures that the input value is computed when that's + otherwise unused. To prevent the constant folding of the input + expression, an additional fp_barrier may be needed or a compilation + mode that does so (e.g. -frounding-math in gcc). Then it can be + used to evaluate an expression for its fenv side-effects only. */ + +#ifndef fp_force_evalf +#define fp_force_evalf fp_force_evalf + +static inline void fp_force_evalf(float x) { + volatile float y; + y = x; +} + +#endif + +#ifndef fp_force_eval +#define fp_force_eval fp_force_eval + +static inline void fp_force_eval(double x) { + volatile double y; + y = x; +} + +#endif + +#ifndef fp_force_evall +#define fp_force_evall fp_force_evall + +static inline void fp_force_evall(long double x) { + volatile long double y; + y = x; +} + +#endif + +#define FORCE_EVAL(x) do { \ + if (sizeof(x) == sizeof(float)) { \ + fp_force_evalf(x); \ + } else if (sizeof(x) == sizeof(double)) { \ + fp_force_eval(x); \ + } else { \ + fp_force_evall(x); \ + } \ +} while(0) + +#define asuint(f) ((union{float _f; uint32_t _i;}){f})._i +#define asfloat(i) ((union{uint32_t _i; float _f;}){i})._f +#define asuint64(f) ((union{double _f; uint64_t _i;}){f})._i +#define asdouble(i) ((union{uint64_t _i; double _f;}){i})._f + +#define EXTRACT_WORDS(hi, lo, d) \ +do { \ + uint64_t __u = asuint64(d); \ + (hi) = __u >> 32; \ + (lo) = (uint32_t)__u; \ +} while (0) + +#define GET_HIGH_WORD(hi, d) \ +do { \ + (hi) = asuint64(d) >> 32; \ +} while (0) + +#define GET_LOW_WORD(lo, d) \ +do { \ + (lo) = (uint32_t)asuint64(d); \ +} while (0) + +#define INSERT_WORDS(d, hi, lo) \ +do { \ + (d) = asdouble(((uint64_t)(hi)<<32) | (uint32_t)(lo)); \ +} while (0) + +#define SET_HIGH_WORD(d, hi) \ + INSERT_WORDS(d, hi, (uint32_t)asuint64(d)) + +#define SET_LOW_WORD(d, lo) \ + INSERT_WORDS(d, asuint64(d)>>32, lo) + +#define GET_FLOAT_WORD(w, d) \ +do { \ + (w) = asuint(d); \ +} while (0) + +#define SET_FLOAT_WORD(d, w) \ +do { \ + (d) = asfloat(w); \ +} while (0) + +static int __rem_pio2_large(double *, double *, int, int, int); + +static int __rem_pio2(double, double *); + +static double __sin(double, double, int); + +static double __cos(double, double); + +static double __tan(double, double, int); + +static double __expo2(double, double); + +static int __rem_pio2f(float, double *); + +static float __sindf(double); + +static float __cosdf(double); + +static float __tandf(double, int); + +static float __expo2f(float, float); + +static int __rem_pio2l(long double, long double *); + +static long double __sinl(long double, long double, int); + +static long double __cosl(long double, long double); + +static long double __tanl(long double, long double, int); + +static long double __polevll(long double, const long double *, int); + +static long double __p1evll(long double, const long double *, int); + +//extern int __signgam; +static double __lgamma_r(double, int *); + +static float __lgammaf_r(float, int *); + +/* error handling functions */ +static float __math_xflowf(uint32_t, float); + +static float __math_uflowf(uint32_t); + +static float __math_oflowf(uint32_t); + +static float __math_divzerof(uint32_t); + +static float __math_invalidf(float); + +static double __math_xflow(uint32_t, double); + +static double __math_uflow(uint32_t); + +static double __math_oflow(uint32_t); + +static double __math_divzero(uint32_t); + +static double __math_invalid(double); + +#if LDBL_MANT_DIG != DBL_MANT_DIG + +static long double __math_invalidl(long double); + +#endif + +// src/math/__math_invalidf.c +static float __math_invalidf(float x) +{ + return (x - x) / (x - x); +} + +// src/math/truncf.c + +float truncf(float x) { + union { + float f; + uint32_t i; + } u = {x}; + int e = (int) (u.i >> 23 & 0xff) - 0x7f + 9; + uint32_t m; + + if (e >= 23 + 9) + return x; + if (e < 9) + e = 1; + m = -1U >> e; + if ((u.i & m) == 0) + return x; + FORCE_EVAL(x + 0x1p120f); + u.i &= ~m; + return u.f; +} + +// src/math/floor.c + +#if FLT_EVAL_METHOD == 0 || FLT_EVAL_METHOD == 1 +#define EPS DBL_EPSILON +#elif FLT_EVAL_METHOD == 2 +#define EPS LDBL_EPSILON +#endif +static const double toint = 1 / EPS; + +double floor(double x) { + union { + double f; + uint64_t i; + } u = {x}; + int e = u.i >> 52 & 0x7ff; + double y; + + if (e >= 0x3ff + 52 || x == 0) + return x; + /* y = int(x) - x, where int(x) is an integer neighbor of x */ + if (u.i >> 63) + y = x - toint + toint - x; + else + y = x + toint - toint - x; + /* special case because of non-nearest rounding modes */ + if (e <= 0x3ff - 1) { + FORCE_EVAL(y); + return u.i >> 63 ? -1 : 0; + } + if (y > 0) + return x + y - 1; + return x + y; +} + +// src/math/fmodf.c + +float fmodf(float x, float y) { + union { + float f; + uint32_t i; + } ux = {x}, uy = {y}; + int ex = ux.i >> 23 & 0xff; + int ey = uy.i >> 23 & 0xff; + uint32_t sx = ux.i & 0x80000000; + uint32_t i; + uint32_t uxi = ux.i; + + if (uy.i << 1 == 0 || __builtin_isnan(y) || ex == 0xff) + return (x * y) / (x * y); + if (uxi << 1 <= uy.i << 1) { + if (uxi << 1 == uy.i << 1) + return 0 * x; + return x; + } + + /* normalize x and y */ + if (!ex) { + for (i = uxi << 9; i >> 31 == 0; ex--, i <<= 1); + uxi <<= -ex + 1; + } else { + uxi &= -1U >> 9; + uxi |= 1U << 23; + } + if (!ey) { + for (i = uy.i << 9; i >> 31 == 0; ey--, i <<= 1); + uy.i <<= -ey + 1; + } else { + uy.i &= -1U >> 9; + uy.i |= 1U << 23; + } + + /* x mod y */ + for (; ex > ey; ex--) { + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0 * x; + uxi = i; + } + uxi <<= 1; + } + i = uxi - uy.i; + if (i >> 31 == 0) { + if (i == 0) + return 0 * x; + uxi = i; + } + for (; uxi >> 23 == 0; uxi <<= 1, ex--); + + /* scale result up */ + if (ex > 0) { + uxi -= 1U << 23; + uxi |= (uint32_t) ex << 23; + } else { + uxi >>= -ex + 1; + } + uxi |= sx; + ux.i = uxi; + return ux.f; +} + +// src/string/memset.c + +void *memset(void *dest, int c, size_t n) { + unsigned char *s = dest; + size_t k; + + /* Fill head and tail with minimal branching. Each + * conditional ensures that all the subsequently used + * offsets are well-defined and in the dest region. */ + + if (!n) return dest; + s[0] = c; + s[n - 1] = c; + if (n <= 2) return dest; + s[1] = c; + s[2] = c; + s[n - 2] = c; + s[n - 3] = c; + if (n <= 6) return dest; + s[3] = c; + s[n - 4] = c; + if (n <= 8) return dest; + + /* Advance pointer to align it at a 4-byte boundary, + * and truncate n to a multiple of 4. The previous code + * already took care of any head/tail that get cut off + * by the alignment. */ + + k = -(uintptr_t) s & 3; + s += k; + n -= k; + n &= -4; + +#ifdef __GNUC__ + typedef uint32_t __attribute__((__may_alias__)) u32; + typedef uint64_t __attribute__((__may_alias__)) u64; + + u32 c32 = ((u32) -1) / 255 * (unsigned char) c; + + /* In preparation to copy 32 bytes at a time, aligned on + * an 8-byte bounary, fill head/tail up to 28 bytes each. + * As in the initial byte-based head/tail fill, each + * conditional below ensures that the subsequent offsets + * are valid (e.g. !(n<=24) implies n>=28). */ + + *(u32 *) (s + 0) = c32; + *(u32 *) (s + n - 4) = c32; + if (n <= 8) return dest; + *(u32 *) (s + 4) = c32; + *(u32 *) (s + 8) = c32; + *(u32 *) (s + n - 12) = c32; + *(u32 *) (s + n - 8) = c32; + if (n <= 24) return dest; + *(u32 *) (s + 12) = c32; + *(u32 *) (s + 16) = c32; + *(u32 *) (s + 20) = c32; + *(u32 *) (s + 24) = c32; + *(u32 *) (s + n - 28) = c32; + *(u32 *) (s + n - 24) = c32; + *(u32 *) (s + n - 20) = c32; + *(u32 *) (s + n - 16) = c32; + + /* Align to a multiple of 8 so we can fill 64 bits at a time, + * and avoid writing the same bytes twice as much as is + * practical without introducing additional branching. */ + + k = 24 + ((uintptr_t) s & 4); + s += k; + n -= k; + + /* If this loop is reached, 28 tail bytes have already been + * filled, so any remainder when n drops below 32 can be + * safely ignored. */ + + u64 c64 = c32 | ((u64) c32 << 32); + for (; n >= 32; n -= 32, s += 32) { + *(u64 *) (s + 0) = c64; + *(u64 *) (s + 8) = c64; + *(u64 *) (s + 16) = c64; + *(u64 *) (s + 24) = c64; + } +#else + /* Pure C fallback with no aliasing violations. */ + for (; n; n--, s++) *s = c; +#endif + + return dest; +} + +// src/math/sqrt_data.[c|h] + +/* if x in [1,2): i = (int)(64*x); + if x in [2,4): i = (int)(32*x-64); + __rsqrt_tab[i]*2^-16 is estimating 1/sqrt(x) with small relative error: + |__rsqrt_tab[i]*0x1p-16*sqrt(x) - 1| < -0x1.fdp-9 < 2^-8 */ +extern const uint16_t __rsqrt_tab[128] = { + 0xb451, 0xb2f0, 0xb196, 0xb044, 0xaef9, 0xadb6, 0xac79, 0xab43, + 0xaa14, 0xa8eb, 0xa7c8, 0xa6aa, 0xa592, 0xa480, 0xa373, 0xa26b, + 0xa168, 0xa06a, 0x9f70, 0x9e7b, 0x9d8a, 0x9c9d, 0x9bb5, 0x9ad1, + 0x99f0, 0x9913, 0x983a, 0x9765, 0x9693, 0x95c4, 0x94f8, 0x9430, + 0x936b, 0x92a9, 0x91ea, 0x912e, 0x9075, 0x8fbe, 0x8f0a, 0x8e59, + 0x8daa, 0x8cfe, 0x8c54, 0x8bac, 0x8b07, 0x8a64, 0x89c4, 0x8925, + 0x8889, 0x87ee, 0x8756, 0x86c0, 0x862b, 0x8599, 0x8508, 0x8479, + 0x83ec, 0x8361, 0x82d8, 0x8250, 0x81c9, 0x8145, 0x80c2, 0x8040, + 0xff02, 0xfd0e, 0xfb25, 0xf947, 0xf773, 0xf5aa, 0xf3ea, 0xf234, + 0xf087, 0xeee3, 0xed47, 0xebb3, 0xea27, 0xe8a3, 0xe727, 0xe5b2, + 0xe443, 0xe2dc, 0xe17a, 0xe020, 0xdecb, 0xdd7d, 0xdc34, 0xdaf1, + 0xd9b3, 0xd87b, 0xd748, 0xd61a, 0xd4f1, 0xd3cd, 0xd2ad, 0xd192, + 0xd07b, 0xcf69, 0xce5b, 0xcd51, 0xcc4a, 0xcb48, 0xca4a, 0xc94f, + 0xc858, 0xc764, 0xc674, 0xc587, 0xc49d, 0xc3b7, 0xc2d4, 0xc1f4, + 0xc116, 0xc03c, 0xbf65, 0xbe90, 0xbdbe, 0xbcef, 0xbc23, 0xbb59, + 0xba91, 0xb9cc, 0xb90a, 0xb84a, 0xb78c, 0xb6d0, 0xb617, 0xb560, +}; + +// src/math/sqrtf.c +#define FENV_SUPPORT 1 + +static inline uint32_t mul32(uint32_t a, uint32_t b) { + return (uint64_t) a * b >> 32; +} + +/* see sqrt.c for more detailed comments. */ + +float sqrtf(float x) { + uint32_t ix, m, m1, m0, even, ey; + + ix = asuint(x); + if (predict_false(ix - 0x00800000 >= 0x7f800000 - 0x00800000)) { + /* x < 0x1p-126 or inf or nan. */ + if (ix * 2 == 0) + return x; + if (ix == 0x7f800000) + return x; + if (ix > 0x7f800000) + return __math_invalidf(x); + /* x is subnormal, normalize it. */ + ix = asuint(x * 0x1p23f); + ix -= 23 << 23; + } + + /* x = 4^e m; with int e and m in [1, 4). */ + even = ix & 0x00800000; + m1 = (ix << 8) | 0x80000000; + m0 = (ix << 7) & 0x7fffffff; + m = even ? m0 : m1; + + /* 2^e is the exponent part of the return value. */ + ey = ix >> 1; + ey += 0x3f800000 >> 1; + ey &= 0x7f800000; + + /* compute r ~ 1/sqrt(m), s ~ sqrt(m) with 2 goldschmidt iterations. */ + static const uint32_t three = 0xc0000000; + uint32_t r, s, d, u, i; + i = (ix >> 17) % 128; + r = (uint32_t) __rsqrt_tab[i] << 16; + /* |r*sqrt(m) - 1| < 0x1p-8 */ + s = mul32(m, r); + /* |s/sqrt(m) - 1| < 0x1p-8 */ + d = mul32(s, r); + u = three - d; + r = mul32(r, u) << 1; + /* |r*sqrt(m) - 1| < 0x1.7bp-16 */ + s = mul32(s, u) << 1; + /* |s/sqrt(m) - 1| < 0x1.7bp-16 */ + d = mul32(s, r); + u = three - d; + s = mul32(s, u); + /* -0x1.03p-28 < s/sqrt(m) - 1 < 0x1.fp-31 */ + s = (s - 1) >> 6; + /* s < sqrt(m) < s + 0x1.08p-23 */ + + /* compute nearest rounded result. */ + uint32_t d0, d1, d2; + float y, t; + d0 = (m << 16) - s * s; + d1 = s - d0; + d2 = d1 + s + 1; + s += d1 >> 31; + s &= 0x007fffff; + s |= ey; + y = asfloat(s); + if (FENV_SUPPORT) { + /* handle rounding and inexact exception. */ + uint32_t tiny = predict_false(d2 == 0) ? 0 : 0x01000000; + tiny |= (d1 ^ d2) & 0x80000000; + t = asfloat(tiny); + y = eval_as_float(y + t); + } + return y; +} + diff --git a/divinemc-server/src/c/includes/ext_math.h b/divinemc-server/src/c/includes/ext_math.h new file mode 100644 index 0000000..ba9e936 --- /dev/null +++ b/divinemc-server/src/c/includes/ext_math.h @@ -0,0 +1,744 @@ +#pragma once + +#include +#include +#include +#include + +__attribute__((aligned(64))) static const double FLAT_SIMPLEX_GRAD[] = { + 1, 1, 0, 0, + -1, 1, 0, 0, + 1, -1, 0, 0, + -1, -1, 0, 0, + 1, 0, 1, 0, + -1, 0, 1, 0, + 1, 0, -1, 0, + -1, 0, -1, 0, + 0, 1, 1, 0, + 0, -1, 1, 0, + 0, 1, -1, 0, + 0, -1, -1, 0, + 1, 1, 0, 0, + 0, -1, 1, 0, + -1, 1, 0, 0, + 0, -1, -1, 0, +}; + +static const double SQRT_3 = 1.7320508075688772; +// 0.5 * (SQRT_3 - 1.0) +static const double SKEW_FACTOR_2D = 0.3660254037844386; +// (3.0 - SQRT_3) / 6.0 +static const double UNSKEW_FACTOR_2D = 0.21132486540518713; + +typedef const double *aligned_double_ptr __attribute__((align_value(64))); +typedef const uint8_t *aligned_uint8_ptr __attribute__((align_value(64))); +typedef const uint32_t *aligned_uint32_ptr __attribute__((align_value(64))); + +#pragma clang attribute push (__attribute__((always_inline)), apply_to = function) + +static inline __attribute__((const)) float fminf(const float x, const float y) { + return __builtin_fminf(x, y); +} + +static inline __attribute__((const)) float fmaxf(const float x, const float y) { + return __builtin_fmaxf(x, y); +} + +static inline __attribute__((const)) float fabsf(const float x) { + union { + float f; + uint32_t i; + } u = {x}; + u.i &= 0x7fffffff; + return u.f; +} + +static inline __attribute__((const)) int64_t labs(const int64_t x) { + return __builtin_labs(x); +} + +static inline __attribute__((const)) double floor(double x) { + return __builtin_floor(x); +} + +static inline __attribute__((const)) float sqrtf(float x) { + return __builtin_sqrtf(x); +} + +static inline __attribute__((const)) float fmodf(float x, float y) { + return __builtin_fmodf(x, y); +} + +static inline __attribute__((const)) int32_t math_floorDiv(const int32_t x, const int32_t y) { + int r = x / y; + // if the signs are different and modulo not zero, round down + if ((x ^ y) < 0 && (r * y != x)) { + r--; + } + return r; +} + +static inline __attribute__((const)) float clampf(const float value, const float min, const float max) { + return fminf(fmaxf(value, min), max); +} + +static inline __attribute__((const)) double math_octave_maintainPrecision(const double value) { + return value - floor(value / 3.3554432E7 + 0.5) * 3.3554432E7; +} + +static inline __attribute__((const)) double math_simplex_grad(const int32_t hash, const double x, const double y, + const double z, const double distance) { + double d = distance - x * x - y * y - z * z; + if (d < 0.0) { + return 0.0; + } else { + int32_t i = hash << 2; + double var0 = FLAT_SIMPLEX_GRAD[i | 0] * x; + double var1 = FLAT_SIMPLEX_GRAD[i | 1] * y; + double var2 = FLAT_SIMPLEX_GRAD[i | 2] * z; + return d * d * d * d * (var0 + var1 + var2); + } +} + +static inline __attribute__((const)) double math_lerp(const double delta, const double start, const double end) { + return start + delta * (end - start); +} + +static inline __attribute__((const)) float math_lerpf(const float delta, const float start, const float end) { + return start + delta * (end - start); +} + +static inline __attribute__((const)) double math_clampedLerp(const double start, const double end, const double delta) { + if (delta < 0.0) { + return start; + } else { + return delta > 1.0 ? end : math_lerp(delta, start, end); + } +} + +static inline __attribute__((const)) double math_square(const double operand) { + return operand * operand; +} + +static inline __attribute__((const)) double math_lerp2(const double deltaX, const double deltaY, const double x0y0, + const double x1y0, const double x0y1, const double x1y1) { + return math_lerp(deltaY, math_lerp(deltaX, x0y0, x1y0), math_lerp(deltaX, x0y1, x1y1)); +} + +static inline __attribute__((const)) double math_lerp3( + const double deltaX, + const double deltaY, + const double deltaZ, + const double x0y0z0, + const double x1y0z0, + const double x0y1z0, + const double x1y1z0, + const double x0y0z1, + const double x1y0z1, + const double x0y1z1, + const double x1y1z1 +) { + return math_lerp(deltaZ, math_lerp2(deltaX, deltaY, x0y0z0, x1y0z0, x0y1z0, x1y1z0), + math_lerp2(deltaX, deltaY, x0y0z1, x1y0z1, x0y1z1, x1y1z1)); +} + +static inline __attribute__((const)) double math_getLerpProgress(const double value, const double start, + const double end) { + return (value - start) / (end - start); +} + +static inline __attribute__((const)) double +math_clampedLerpFromProgress(const double lerpValue, const double lerpStart, const double lerpEnd, const double start, + const double end) { + return math_clampedLerp(start, end, math_getLerpProgress(lerpValue, lerpStart, lerpEnd)); +} + +static inline __attribute__((const)) int32_t math_floorMod(const int32_t x, const int32_t y) { + int32_t mod = x % y; + // if the signs are different and modulo not zero, adjust result + if ((mod ^ y) < 0 && mod != 0) { + mod += y; + } + return mod; +} + +static inline __attribute__((const)) int32_t math_biome2block(const int32_t biomeCoord) { + return biomeCoord << 2; +} + +static inline __attribute__((const)) int32_t math_block2biome(const int32_t blockCoord) { + return blockCoord >> 2; +} + +static inline __attribute__((const)) uint32_t +__math_simplex_map(const aligned_uint32_ptr permutations, const int32_t input) { + return permutations[input & 0xFF]; +} + +static inline __attribute__((const)) double math_simplex_dot(const int32_t hash, const double x, const double y, + const double z) { + const int32_t loc = hash << 2; + return FLAT_SIMPLEX_GRAD[loc + 0] * x + FLAT_SIMPLEX_GRAD[loc + 1] * y + FLAT_SIMPLEX_GRAD[loc + 2] * z; +} + +static inline __attribute__((const)) double __math_simplex_grad(const int32_t hash, const double x, const double y, + const double z, const double distance) { + double d = distance - x * x - y * y - z * z; + double e; + if (d < 0.0) { + e = 0.0; + } else { + d *= d; + e = d * d * math_simplex_dot(hash, x, y, z); + } + return e; + // double tmp = d * d; // speculative execution + + // return d < 0.0 ? 0.0 : tmp * tmp * math_simplex_dot(hash, x, y, z); +} + +static inline double __attribute__((const)) +math_noise_simplex_sample2d(const aligned_uint32_ptr permutations, const double x, const double y) { + const double d = (x + y) * SKEW_FACTOR_2D; + const double i = floor(x + d); + const double j = floor(y + d); + const double e = (i + j) * UNSKEW_FACTOR_2D; + const double f = i - e; + const double g = j - e; + const double h = x - f; + const double k = y - g; + double l; + int32_t li; + double m; + int32_t mi; + if (h > k) { + l = 1; + li = 1; + m = 0; + mi = 0; + } else { + l = 0; + li = 1; + m = 1; + mi = 1; + } + + const double n = h - (double) l + UNSKEW_FACTOR_2D; + const double o = k - (double) m + UNSKEW_FACTOR_2D; + const double p = h - 1.0 + 2.0 * UNSKEW_FACTOR_2D; + const double q = k - 1.0 + 2.0 * UNSKEW_FACTOR_2D; + const int32_t r = (int32_t) i & 0xFF; + const int32_t s = (int32_t) j & 0xFF; + const int32_t t = __math_simplex_map(permutations, r + __math_simplex_map(permutations, s)) % 12; + const int32_t u = __math_simplex_map(permutations, r + li + __math_simplex_map(permutations, s + mi)) % 12; + const int32_t v = __math_simplex_map(permutations, r + 1 + __math_simplex_map(permutations, s + 1)) % 12; + const double w = __math_simplex_grad(t, h, k, 0.0, 0.5); + const double z = __math_simplex_grad(u, n, o, 0.0, 0.5); + const double aa = __math_simplex_grad(v, p, q, 0.0, 0.5); + return 70.0 * (w + z + aa); +} + +static inline __attribute__((const)) double math_perlinFade(const double value) { + return value * value * value * (value * (value * 6.0 - 15.0) + 10.0); +} + +static inline __attribute__((const)) double __math_perlin_grad(const aligned_uint32_ptr permutations, const int32_t px, + const int32_t py, const int32_t pz, const double fx, + const double fy, const double fz) { + const double f[3] = {fx, fy, fz}; + const int32_t p[3] = {px, py, pz}; + const uint32_t q[3] = {p[0] & 0xFF, p[1] & 0xFF, p[2] & 0xFF}; + const uint32_t hash = permutations[(permutations[(permutations[q[0]] + q[1]) & 0xFF] + q[2]) & 0xFF] & 0xF; + const double *const grad = FLAT_SIMPLEX_GRAD + (hash << 2); + return grad[0] * f[0] + grad[1] * f[1] + grad[2] * f[2]; +} + +static inline __attribute__((const)) double +math_noise_perlin_sampleScalar(const aligned_uint32_ptr permutations, + const int32_t px0, const int32_t py0, const int32_t pz0, + const double fx0, const double fy0, const double fz0, const double fadeLocalY) { + const int32_t px1 = px0 + 1; + const int32_t py1 = py0 + 1; + const int32_t pz1 = pz0 + 1; + const double fx1 = fx0 - 1; + const double fy1 = fy0 - 1; + const double fz1 = fz0 - 1; + + const double f000 = __math_perlin_grad(permutations, px0, py0, pz0, fx0, fy0, fz0); + const double f100 = __math_perlin_grad(permutations, px1, py0, pz0, fx1, fy0, fz0); + const double f010 = __math_perlin_grad(permutations, px0, py1, pz0, fx0, fy1, fz0); + const double f110 = __math_perlin_grad(permutations, px1, py1, pz0, fx1, fy1, fz0); + const double f001 = __math_perlin_grad(permutations, px0, py0, pz1, fx0, fy0, fz1); + const double f101 = __math_perlin_grad(permutations, px1, py0, pz1, fx1, fy0, fz1); + const double f011 = __math_perlin_grad(permutations, px0, py1, pz1, fx0, fy1, fz1); + const double f111 = __math_perlin_grad(permutations, px1, py1, pz1, fx1, fy1, fz1); + + const double dx = math_perlinFade(fx0); + const double dy = math_perlinFade(fadeLocalY); + const double dz = math_perlinFade(fz0); + return math_lerp3(dx, dy, dz, f000, f100, f010, f110, f001, f101, f011, f111); +} + + +static inline __attribute__((const)) double +math_noise_perlin_sample(const aligned_uint32_ptr permutations, + const double originX, const double originY, const double originZ, + const double x, const double y, const double z, + const double yScale, const double yMax) { + const double d = x + originX; + const double e = y + originY; + const double f = z + originZ; + const double i = floor(d); + const double j = floor(e); + const double k = floor(f); + const double g = d - i; + const double h = e - j; + const double l = f - k; + const double o = yScale != 0 ? floor(((yMax >= 0.0 && yMax < h) ? yMax : h) / yScale + 1.0E-7) * yScale : 0; + + return math_noise_perlin_sampleScalar(permutations, (int32_t) i, (int32_t) j, (int32_t) k, g, h - o, l, h); +} + + +typedef const struct double_octave_sampler_data { + const uint64_t length; + const double amplitude; + const bool *const need_shift; + const aligned_double_ptr lacunarity_powd; + const aligned_double_ptr persistence_powd; + const aligned_uint32_ptr sampler_permutations; + const aligned_double_ptr sampler_originX; + const aligned_double_ptr sampler_originY; + const aligned_double_ptr sampler_originZ; + const aligned_double_ptr amplitudes; +} double_octave_sampler_data_t; + +static inline __attribute__((const)) double +math_noise_perlin_double_octave_sample_impl(const double_octave_sampler_data_t *const data, + const double x, const double y, const double z, + const double yScale, const double yMax, const uint8_t useOrigin) { + double ds[data->length]; + +#pragma clang loop vectorize(enable) interleave(enable) interleave_count(2) + for (uint32_t i = 0; i < data->length; i++) { + const double e = data->lacunarity_powd[i]; + const double f = data->persistence_powd[i]; + const aligned_uint32_ptr permutations = data->sampler_permutations + 256 * i; + const double sampleX = data->need_shift[i] ? x * 1.0181268882175227 : x; + const double sampleY = data->need_shift[i] ? y * 1.0181268882175227 : y; + const double sampleZ = data->need_shift[i] ? z * 1.0181268882175227 : z; + const double g = math_noise_perlin_sample( + permutations, + data->sampler_originX[i], + data->sampler_originY[i], + data->sampler_originZ[i], + math_octave_maintainPrecision(sampleX * e), + useOrigin ? -(data->sampler_originY[i]) : math_octave_maintainPrecision(sampleY * e), + math_octave_maintainPrecision(sampleZ * e), + yScale * e, + yMax * e); + ds[i] = data->amplitudes[i] * g * f; + } + + double d1 = 0.0; + double d2 = 0.0; + for (uint32_t i = 0; i < data->length; i++) { + if (!data->need_shift[i]) { + d1 += ds[i]; + } else { + d2 += ds[i]; + } + } + + return (d1 + d2) * data->amplitude; +} + +//static inline void +//math_noise_perlin_double_octave_sample_impl_batch(const double_octave_sampler_data_t *const data, double *const res, +// const double *const x, const double *const y, const double *const z, +// const uint32_t length) { +// double ds[data->length][length]; +// +// for (uint32_t si = 0; si < data->length; si ++) { +//#pragma clang loop vectorize(enable) interleave(enable) interleave_count(2) +// for (uint32_t bi = 0; bi < length; bi++) { +// const double e = data->lacunarity_powd[si]; +// const double f = data->persistence_powd[si]; +// const aligned_uint32_ptr permutations = data->sampler_permutations + 256 * si; +// const double sampleX = data->need_shift[si] ? x[bi] * 1.0181268882175227 : x[bi]; +// const double sampleY = data->need_shift[si] ? y[bi] * 1.0181268882175227 : y[bi]; +// const double sampleZ = data->need_shift[si] ? z[bi] * 1.0181268882175227 : z[bi]; +// const double g = math_noise_perlin_sample( +// permutations, +// data->sampler_originX[si], +// data->sampler_originY[si], +// data->sampler_originZ[si], +// math_octave_maintainPrecision(sampleX * e), +// math_octave_maintainPrecision(sampleY * e), +// math_octave_maintainPrecision(sampleZ * e), +// 0.0, +// 0.0); +// ds[si][bi] = data->amplitudes[si] * g * f; +// } +// } +// +// double d1[length]; +// double d2[length]; +// for (uint32_t i = 0; i < length; i ++) { +// d1[i] = 0.0; +// d2[i] = 0.0; +// } +// for (uint32_t bi = 0; bi < length; bi++) { +// for (uint32_t si = 0; si < data->length; si ++) { +// if (!data->need_shift[si]) { +// d1[bi] += ds[si][bi]; +// } else { +// d2[bi] += ds[si][bi]; +// } +// } +// } +// for (uint32_t bi = 0; bi < length; bi++) { +// res[bi] = (d1[bi] + d2[bi]) * data->amplitude; +// } +//} + +//static inline void +//math_noise_perlin_double_octave_sample_impl_batch(const double_octave_sampler_data_t *restrict const data, +// double *restrict const res, const double *restrict const x, +// const double *restrict const y, const double *restrict const z, +// const uint32_t length) { +// const uint32_t total_len = data->length * length; +// +// double ds[total_len]; +// uint32_t sia[total_len]; // sampler index array +// uint32_t bia[total_len]; // batch index array +// double xa[total_len]; // x array +// double ya[total_len]; // y array +// double za[total_len]; // z array +// +// double lacunarity_powd[total_len]; +// double persistence_powd[total_len]; +// bool need_shift[total_len]; +// double sampler_originX[total_len]; +// double sampler_originY[total_len]; +// double sampler_originZ[total_len]; +// double amplitudes[total_len]; +// +// { +// uint32_t idx = 0; +// for (uint32_t si = 0; si < data->length; si++) { +// for (uint32_t bi = 0; bi < length; bi++) { +// sia[idx] = si; +// bia[idx] = bi; +// xa[idx] = x[bi]; +// ya[idx] = y[bi]; +// za[idx] = z[bi]; +// lacunarity_powd[idx] = data->lacunarity_powd[si]; +// persistence_powd[idx] = data->persistence_powd[si]; +// need_shift[idx] = data->need_shift[si]; +// sampler_originX[idx] = data->sampler_originX[si]; +// sampler_originY[idx] = data->sampler_originY[si]; +// sampler_originZ[idx] = data->sampler_originZ[si]; +// amplitudes[idx] = data->amplitudes[si]; +// idx++; +// } +// } +// } +// +//#pragma clang loop vectorize(enable) interleave(enable) interleave_count(2) +// for (uint32_t idx = 0; idx < total_len; idx++) { +// const uint32_t si = sia[idx]; +// const double xi = xa[idx]; +// const double yi = ya[idx]; +// const double zi = za[idx]; +// const double e = lacunarity_powd[idx]; +// const double f = persistence_powd[idx]; +// const aligned_uint32_ptr permutations = data->sampler_permutations + 256 * si; +// const double sampleX = need_shift[idx] ? xi * 1.0181268882175227 : xi; +// const double sampleY = need_shift[idx] ? yi * 1.0181268882175227 : yi; +// const double sampleZ = need_shift[idx] ? zi * 1.0181268882175227 : zi; +// const double g = math_noise_perlin_sample( +// permutations, +// sampler_originX[idx], +// sampler_originY[idx], +// sampler_originZ[idx], +// math_octave_maintainPrecision(sampleX * e), +// math_octave_maintainPrecision(sampleY * e), +// math_octave_maintainPrecision(sampleZ * e), +// 0.0, +// 0.0); +// ds[idx] = amplitudes[idx] * g * f; +// } +// +// double d1[length]; +// double d2[length]; +// for (uint32_t i = 0; i < length; i++) { +// d1[i] = 0.0; +// d2[i] = 0.0; +// } +// for (uint32_t idx = 0; idx < total_len; idx++) { +// const uint32_t si = sia[idx]; +// const uint32_t bi = bia[idx]; +// if (!data->need_shift[si]) { +// d1[bi] += ds[idx]; +// } else { +// d2[bi] += ds[idx]; +// } +// } +// for (uint32_t bi = 0; bi < length; bi++) { +// res[bi] = (d1[bi] + d2[bi]) * data->amplitude; +// } +//} + +static inline __attribute__((const)) double +math_noise_perlin_double_octave_sample(const double_octave_sampler_data_t *const data, + const double x, const double y, const double z) { + return math_noise_perlin_double_octave_sample_impl(data, x, y, z, 0.0, 0.0, 0); +} + +static inline void +math_noise_perlin_double_octave_sample_batch(const double_octave_sampler_data_t *const data, double *const res, + const double *const x, const double *const y, const double *const z, + const uint32_t length) { +// math_noise_perlin_double_octave_sample_impl_batch(data, res, x, y, z, length); + for (uint32_t i = 0; i < length; i ++) { + res[i] = math_noise_perlin_double_octave_sample_impl(data, x[i], y[i], z[i], 0.0, 0.0, 0); + } +} + +typedef const struct interpolated_noise_sub_sampler { + const aligned_uint32_ptr sampler_permutations; + const aligned_double_ptr sampler_originX; + const aligned_double_ptr sampler_originY; + const aligned_double_ptr sampler_originZ; + const aligned_double_ptr sampler_mulFactor; + const uint32_t length; +} interpolated_noise_sub_sampler_t; + +typedef const struct interpolated_noise_sampler { + const double scaledXzScale; + const double scaledYScale; + const double xzFactor; + const double yFactor; + const double smearScaleMultiplier; + const double xzScale; + const double yScale; + + const interpolated_noise_sub_sampler_t lower; + const interpolated_noise_sub_sampler_t upper; + const interpolated_noise_sub_sampler_t normal; +} interpolated_noise_sampler_t; + + +static inline __attribute__((const)) double +math_noise_perlin_interpolated_sample(const interpolated_noise_sampler_t *const data, + const double x, const double y, const double z) { + const double d = x * data->scaledXzScale; + const double e = y * data->scaledYScale; + const double f = z * data->scaledXzScale; + const double g = d / data->xzFactor; + const double h = e / data->yFactor; + const double i = f / data->xzFactor; + const double j = data->scaledYScale * data->smearScaleMultiplier; + const double k = j / data->yFactor; + double l = 0.0; + double m = 0.0; + double n = 0.0; + + double ns[data->normal.length]; +#pragma clang loop vectorize(enable) + for (uint32_t offset = 0; offset < data->normal.length; offset++) { + ns[offset] = math_noise_perlin_sample( + data->normal.sampler_permutations + 256 * offset, + data->normal.sampler_originX[offset], + data->normal.sampler_originY[offset], + data->normal.sampler_originZ[offset], + math_octave_maintainPrecision(g * data->normal.sampler_mulFactor[offset]), + math_octave_maintainPrecision(h * data->normal.sampler_mulFactor[offset]), + math_octave_maintainPrecision(i * data->normal.sampler_mulFactor[offset]), + k * data->normal.sampler_mulFactor[offset], + h * data->normal.sampler_mulFactor[offset] + ) / data->normal.sampler_mulFactor[offset]; + } + + for (uint32_t offset = 0; offset < data->normal.length; offset++) { + n += ns[offset]; + } + + const double q = (n / 10.0 + 1.0) / 2.0; + const uint8_t bl2 = q >= 1.0; + const uint8_t bl3 = q <= 0.0; + + if (!bl2) { + double ls[data->lower.length]; +#pragma clang loop vectorize(enable) interleave_count(2) + for (uint32_t offset = 0; offset < data->lower.length; offset++) { + ls[offset] = math_noise_perlin_sample( + data->lower.sampler_permutations + 256 * offset, + data->lower.sampler_originX[offset], + data->lower.sampler_originY[offset], + data->lower.sampler_originZ[offset], + math_octave_maintainPrecision(d * data->lower.sampler_mulFactor[offset]), + math_octave_maintainPrecision(e * data->lower.sampler_mulFactor[offset]), + math_octave_maintainPrecision(f * data->lower.sampler_mulFactor[offset]), + j * data->lower.sampler_mulFactor[offset], + e * data->lower.sampler_mulFactor[offset] + ) / data->lower.sampler_mulFactor[offset]; + } + + for (uint32_t offset = 0; offset < data->lower.length; offset++) { + l += ls[offset]; + } + } + + if (!bl3) { + double ms[data->upper.length]; +#pragma clang loop vectorize(enable) interleave_count(2) + for (uint32_t offset = 0; offset < data->upper.length; offset++) { + ms[offset] = math_noise_perlin_sample( + data->upper.sampler_permutations + 256 * offset, + data->upper.sampler_originX[offset], + data->upper.sampler_originY[offset], + data->upper.sampler_originZ[offset], + math_octave_maintainPrecision(d * data->upper.sampler_mulFactor[offset]), + math_octave_maintainPrecision(e * data->upper.sampler_mulFactor[offset]), + math_octave_maintainPrecision(f * data->upper.sampler_mulFactor[offset]), + j * data->upper.sampler_mulFactor[offset], + e * data->upper.sampler_mulFactor[offset] + ) / data->upper.sampler_mulFactor[offset]; + } + for (uint32_t offset = 0; offset < data->upper.length; offset++) { + m += ms[offset]; + } + } + + return math_clampedLerp(l / 512.0, m / 512.0, q) / 128.0; +} + +static inline __attribute__((const)) float +math_end_islands_sample(const aligned_uint32_ptr simplex_permutations, const int32_t x, const int32_t z) { + const int32_t i = x / 2; + const int32_t j = z / 2; + const int32_t k = x % 2; + const int32_t l = z % 2; + const int32_t muld = x * x + z * z; // int32_t intentionally + if (muld < 0) { + return __builtin_nanf(""); + } + float f = 100.0F - sqrtf((float) muld) * 8.0F; + f = clampf(f, -100.0F, 80.0F); + + int8_t ms[25 * 25], ns[25 * 25], hit[25 * 25]; + const int64_t omin = labs(i) - 12LL; + const int64_t pmin = labs(j) - 12LL; + const int64_t omax = labs(i) + 12LL; + const int64_t pmax = labs(j) + 12LL; + + { + uint32_t idx = 0; +#pragma clang loop vectorize(enable) + for (int8_t m = -12; m < 13; m++) { + for (int8_t n = -12; n < 13; n++) { + ms[idx] = m; + ns[idx] = n; + idx++; + } + } + if (idx != 25 * 25) { + __builtin_trap(); + } + } + + if (omin * omin + pmin * pmin > 4096LL) { + for (uint32_t idx = 0; idx < 25 * 25; idx++) { + const int64_t o = (int64_t) i + (int64_t) ms[idx]; + const int64_t p = (int64_t) j + (int64_t) ns[idx]; + hit[idx] = math_noise_simplex_sample2d(simplex_permutations, (double) o, (double) p) < -0.9F; + } + } else { + for (uint32_t idx = 0; idx < 25 * 25; idx++) { + const int64_t o = (int64_t) i + (int64_t) ms[idx]; + const int64_t p = (int64_t) j + (int64_t) ns[idx]; + hit[idx] = (o * o + p * p > 4096LL) && math_noise_simplex_sample2d( + simplex_permutations, (double) o, (double) p) < -0.9F; + } + } + +#pragma clang loop vectorize(enable) interleave(enable) + for (uint32_t idx = 0; idx < 25 * 25; idx++) { + if (hit[idx]) { + const int32_t m = ms[idx]; + const int32_t n = ns[idx]; + const int64_t o = (int64_t) i + (int64_t) m; + const int64_t p = (int64_t) j + (int64_t) n; + const float g1 = fabsf((float) o) * 3439.0F; + const float g2 = fabsf((float) p) * 147.0F; + const float g = fmodf((g1 + g2), 13.0F) + 9.0F; + const float h = (float) (k - m * 2); + const float q = (float) (l - n * 2); + float r = 100.0F - sqrtf(h * h + q * q) * g; + r = clampf(r, -100.0F, 80.0F); + f = fmaxf(f, r); + } + } + + return f; +} + +static inline __attribute__((const)) uint32_t +math_biome_access_sample(const int64_t theSeed, const int32_t x, const int32_t y, const int32_t z) { + const int32_t var0 = x - 2; + const int32_t var1 = y - 2; + const int32_t var2 = z - 2; + const int32_t var3 = var0 >> 2; + const int32_t var4 = var1 >> 2; + const int32_t var5 = var2 >> 2; + const double var6 = (double) (var0 & 3) / 4.0; + const double var7 = (double) (var1 & 3) / 4.0; + const double var8 = (double) (var2 & 3) / 4.0; + uint32_t var9 = 0; + double var10 = DBL_MAX; + + double var28s[8]; + +#pragma clang loop interleave_count(2) + for (uint32_t var11 = 0; var11 < 8; ++var11) { + uint32_t var12 = var11 & 4; + uint32_t var13 = var11 & 2; + uint32_t var14 = var11 & 1; + int64_t var15 = var12 ? var3 + 1 : var3; + int64_t var16 = var13 ? var4 + 1 : var4; + int64_t var17 = var14 ? var5 + 1 : var5; + double var18 = var12 ? var6 - 1.0 : var6; + double var19 = var13 ? var7 - 1.0 : var7; + double var20 = var14 ? var8 - 1.0 : var8; + int64_t var21 = theSeed * (theSeed * 6364136223846793005L + 1442695040888963407L) + var15; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var15; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var16; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + var17; + double var22 = (double) ((var21 >> 24) & 1023) / 1024.0; + double var23 = (var22 - 0.5) * 0.9; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + theSeed; + double var24 = (double) ((var21 >> 24) & 1023) / 1024.0; + double var25 = (var24 - 0.5) * 0.9; + var21 = var21 * (var21 * 6364136223846793005L + 1442695040888963407L) + theSeed; + double var26 = (double) ((var21 >> 24) & 1023) / 1024.0; + double var27 = (var26 - 0.5) * 0.9; + double var28 = math_square(var20 + var27) + math_square(var19 + var25) + math_square(var18 + var23); + var28s[var11] = var28; + } + + for (int i = 0; i < 8; ++i) { + if (var10 > var28s[i]) { + var9 = i; + var10 = var28s[i]; + } + } + + return var9; +} + +#pragma clang attribute pop + diff --git a/divinemc-server/src/c/includes/target_macros.h b/divinemc-server/src/c/includes/target_macros.h new file mode 100644 index 0000000..8883059 --- /dev/null +++ b/divinemc-server/src/c/includes/target_macros.h @@ -0,0 +1,28 @@ +#pragma once + +#define TARGET_IMPL_ARCH(suffix, func_prefix, func_ret, func_call) \ + func_ret func_prefix##_##suffix func_call + +#ifdef __x86_64__ + +#ifdef GATHER_DISABLED +#define TARGET_IMPL(func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=haswell"))) TARGET_IMPL_ARCH(avx2, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=skylake-avx512"))) TARGET_IMPL_ARCH(avx512skx, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=icelake-server"))) TARGET_IMPL_ARCH(avx512icl, func_prefix, func_ret, func_call) +#else +#define TARGET_IMPL(func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=x86-64"))) TARGET_IMPL_ARCH(sse2, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=x86-64-v2"))) TARGET_IMPL_ARCH(sse4_2, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=sandybridge"))) TARGET_IMPL_ARCH(avx, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=alderlake"))) TARGET_IMPL_ARCH(avx2adl, func_prefix, func_ret, func_call) \ + __attribute__((pure, target("arch=sapphirerapids"))) TARGET_IMPL_ARCH(avx512spr, func_prefix, func_ret, func_call) +#endif + +#else + +#define TARGET_IMPL(func_prefix, func_ret, func_call) \ + __attribute__((pure)) TARGET_IMPL_ARCH(generic, func_prefix, func_ret, func_call) + +#endif + diff --git a/divinemc-server/src/c/system_isa_aarch64.c b/divinemc-server/src/c/system_isa_aarch64.c new file mode 100644 index 0000000..effa824 --- /dev/null +++ b/divinemc-server/src/c/system_isa_aarch64.c @@ -0,0 +1,11 @@ +#ifdef __aarch64__ + +#include + +int32_t c2me_natives_get_system_isa(_Bool allowAVX512) { + return 0; +} + +#endif + +typedef int make_iso_compiler_happy; diff --git a/divinemc-server/src/c/system_isa_x86_64.c b/divinemc-server/src/c/system_isa_x86_64.c new file mode 100644 index 0000000..09ffd01 --- /dev/null +++ b/divinemc-server/src/c/system_isa_x86_64.c @@ -0,0 +1,154 @@ +#ifdef __x86_64__ + +#include + +static void __cpuid(int info[4], int infoType) { + __asm__ __volatile__("cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "0"(infoType)); +} + +static void __cpuidex(int info[4], int level, int count) { + __asm__ __volatile__("cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "0"(level), "2"(count)); +} + +static int __os_has_avx_support(void) { + // Check xgetbv; this uses a .byte sequence instead of the instruction + // directly because older assemblers do not include support for xgetbv and + // there is no easy way to conditionally compile based on the assembler used. + int rEAX, rEDX; + __asm__ __volatile__(".byte 0x0f, 0x01, 0xd0" : "=a"(rEAX), "=d"(rEDX) : "c"(0)); + return (rEAX & 6) == 6; +} + +#if !defined(__APPLE__) +static int __os_has_avx512_support(void) { + // Check if the OS saves the XMM, YMM and ZMM registers, i.e. it supports AVX2 and AVX512. + // See section 2.1 of software.intel.com/sites/default/files/managed/0d/53/319433-022.pdf + // Check xgetbv; this uses a .byte sequence instead of the instruction + // directly because older assemblers do not include support for xgetbv and + // there is no easy way to conditionally compile based on the assembler used. + int rEAX, rEDX; + __asm__ __volatile__(".byte 0x0f, 0x01, 0xd0" : "=a"(rEAX), "=d"(rEDX) : "c"(0)); + return (rEAX & 0xE6) == 0xE6; +} +#endif // !__APPLE__ + +// __get_system_isa should return a value corresponding to one of the +// Target::ISA enumerant values that gives the most capable ISA that the +// current system can run. +int32_t c2me_natives_get_system_isa(_Bool allowAVX512) { + int info[4]; + __cpuid(info, 1); + + // Call cpuid with eax=7, ecx=0 + int info2[4]; + __cpuidex(info2, 7, 0); + + int info3[4] = {0, 0, 0, 0}; + int max_subleaf = info2[0]; + // Call cpuid with eax=7, ecx=1 + if (max_subleaf >= 1) + __cpuidex(info3, 7, 1); + + // clang-format off + _Bool sse2 = (info[3] & (1 << 26)) != 0; + _Bool sse41 = (info[2] & (1 << 19)) != 0; + _Bool sse42 = (info[2] & (1 << 20)) != 0; + _Bool avx = (info[2] & (1 << 28)) != 0; + _Bool avx2 = (info2[1] & (1 << 5)) != 0; + _Bool avx_vnni = (info3[0] & (1 << 4)) != 0; + _Bool avx_f16c = (info[2] & (1 << 29)) != 0; + _Bool avx_rdrand = (info[2] & (1 << 30)) != 0; + _Bool osxsave = (info[2] & (1 << 27)) != 0; + _Bool avx512_f = (info2[1] & (1 << 16)) != 0; + // clang-format on + + // NOTE: the values returned below must be the same as the + // corresponding enumerant values in Target::ISA. + if (allowAVX512 && osxsave && avx2 && avx512_f +#if !defined(__APPLE__) + && __os_has_avx512_support() +#endif // !__APPLE__ + ) { + // We need to verify that AVX2 is also available, + // as well as AVX512, because our targets are supposed + // to use both. + + // clang-format off + _Bool avx512_dq = (info2[1] & (1 << 17)) != 0; + _Bool avx512_pf = (info2[1] & (1 << 26)) != 0; + _Bool avx512_er = (info2[1] & (1 << 27)) != 0; + _Bool avx512_cd = (info2[1] & (1 << 28)) != 0; + _Bool avx512_bw = (info2[1] & (1 << 30)) != 0; + _Bool avx512_vl = (info2[1] & (1 << 31)) != 0; +#if !defined(__APPLE__) + _Bool avx512_vbmi2 = (info2[2] & (1 << 6)) != 0; + _Bool avx512_gfni = (info2[2] & (1 << 8)) != 0; + _Bool avx512_vaes = (info2[2] & (1 << 9)) != 0; + _Bool avx512_vpclmulqdq = (info2[2] & (1 << 10)) != 0; + _Bool avx512_vnni = (info2[2] & (1 << 11)) != 0; + _Bool avx512_bitalg = (info2[2] & (1 << 12)) != 0; + _Bool avx512_vpopcntdq = (info2[2] & (1 << 14)) != 0; + _Bool avx512_bf16 = (info3[0] & (1 << 5)) != 0; + _Bool avx512_vp2intersect = (info2[3] & (1 << 8)) != 0; + _Bool avx512_amx_bf16 = (info2[3] & (1 << 22)) != 0; + _Bool avx512_amx_tile = (info2[3] & (1 << 24)) != 0; + _Bool avx512_amx_int8 = (info2[3] & (1 << 25)) != 0; + _Bool avx512_fp16 = (info2[3] & (1 << 23)) != 0; +#endif // !__APPLE__ + // clang-format on + + // Knights Landing: KNL = F + PF + ER + CD + // Skylake server: SKX = F + DQ + CD + BW + VL + // Cascade Lake server: CLX = SKX + VNNI + // Cooper Lake server: CPX = CLX + BF16 + // Ice Lake client & server: ICL = CLX + VBMI2 + GFNI + VAES + VPCLMULQDQ + BITALG + VPOPCNTDQ + // Tiger Lake: TGL = ICL + VP2INTERSECT + // Sapphire Rapids: SPR = ICL + BF16 + AMX_BF16 + AMX_TILE + AMX_INT8 + AVX_VNNI + FP16 + _Bool knl = avx512_pf && avx512_er && avx512_cd; + _Bool skx = avx512_dq && avx512_cd && avx512_bw && avx512_vl; +#if !defined(__APPLE__) + _Bool clx = skx && avx512_vnni; + _Bool cpx = clx && avx512_bf16; + _Bool icl = + clx && avx512_vbmi2 && avx512_gfni && avx512_vaes && avx512_vpclmulqdq && avx512_bitalg && avx512_vpopcntdq; + _Bool tgl = icl && avx512_vp2intersect; + _Bool spr = + icl && avx512_bf16 && avx512_amx_bf16 && avx512_amx_tile && avx512_amx_int8 && avx_vnni && avx512_fp16; + if (spr) { + return 9; // SPR + } else if (icl) { + return 8; // ICL + } +#endif // !__APPLE__ + if (skx) { + return 7; // SKX + } else if (knl) { + return 6; // KNL + } + // If it's unknown AVX512 target, fall through and use AVX2 + // or whatever is available in the machine. + } + + if (osxsave && avx && __os_has_avx_support()) { +// if (avx_vnni) { +// return 5; // ADL +// } + if (avx_f16c && avx_rdrand && avx2) { + return 4; + } + // Regular AVX + return 3; + } else if (sse42) { + return 2; // SSE4.2 + } else if (sse41) { + return 1; // SSE4.1 + } else if (sse2) { + return 0; // SSE2 + } else { + __builtin_trap(); + } +} + +#endif + +typedef int make_iso_compiler_happy; diff --git a/divinemc-server/src/c/targets/darwin-x86_64.cmake b/divinemc-server/src/c/targets/darwin-x86_64.cmake new file mode 100644 index 0000000..4bd3ee9 --- /dev/null +++ b/divinemc-server/src/c/targets/darwin-x86_64.cmake @@ -0,0 +1,11 @@ +set(CMAKE_SYSTEM_NAME Darwin) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +set(triple x86_64-apple-darwin) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_C_FLAGS "-march=x86-64 -Wl,-no_uuid") diff --git a/divinemc-server/src/c/targets/linux-aarch_64.cmake b/divinemc-server/src/c/targets/linux-aarch_64.cmake new file mode 100644 index 0000000..aa0bb36 --- /dev/null +++ b/divinemc-server/src/c/targets/linux-aarch_64.cmake @@ -0,0 +1,10 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm64) + +set(triple aarch64-unknown-linux-musl) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + diff --git a/divinemc-server/src/c/targets/linux-x86_64.cmake b/divinemc-server/src/c/targets/linux-x86_64.cmake new file mode 100644 index 0000000..eebd66a --- /dev/null +++ b/divinemc-server/src/c/targets/linux-x86_64.cmake @@ -0,0 +1,11 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +set(triple x86_64-unknown-linux-musl) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_C_FLAGS -march=x86-64) diff --git a/divinemc-server/src/c/targets/windows-x86_64.cmake b/divinemc-server/src/c/targets/windows-x86_64.cmake new file mode 100644 index 0000000..92adf50 --- /dev/null +++ b/divinemc-server/src/c/targets/windows-x86_64.cmake @@ -0,0 +1,11 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +set(triple x86_64-pc-windows-gnu) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_C_FLAGS "-march=x86-64 -Wl,--no-insert-timestamp") diff --git a/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java new file mode 100644 index 0000000..f57983f --- /dev/null +++ b/divinemc-server/src/main/java/net/caffeinemc/mods/lithium/common/util/math/CompactSineLUT.java @@ -0,0 +1,89 @@ +package net.caffeinemc.mods.lithium.common.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 + * @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/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java new file mode 100644 index 0000000..8a032ea --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java @@ -0,0 +1,372 @@ +package org.bxteam.divinemc; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; +import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +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.List; +import java.util.Map; +import java.util.logging.Level; + +@SuppressWarnings("unused") +@NullMarked +public final class DivineConfig { + private DivineConfig() { + throw new IllegalStateException("Utility class"); + } + + private static final String HEADER = "This is the main configuration file for DivineMC.\n" + + "If you need help with the configuration or have any questions related to DivineMC,\n" + + "join us in our Discord server.\n" + + "\n" + + "Discord: https://discord.gg/p7cxhw7E2M \n" + + "Docs: https://bxteam.org/docs/divinemc \n" + + "New builds: https://github.com/BX-Team/DivineMC/releases/latest"; + private static File configFile; + public static YamlConfiguration config; + + private static Map commands; + + public static int version; + static boolean verbose; + + public static void init(File configFile) { + DivineConfig.configFile = configFile; + config = new YamlConfiguration(); + try { + config.load(configFile); + } catch (IOException ignored) { + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load divinemc.yml, please correct your syntax errors", ex); + throw Throwables.propagate(ex); + } + config.options().header(HEADER); + config.options().copyDefaults(true); + verbose = getBoolean(config, "verbose", false); + + version = getInt(config, "config-version", 5); + set(config, "config-version", 5); + + readConfig(DivineConfig.class, null); + } + + public static void log(String s) { + if (verbose) { + log(Level.INFO, s); + } + } + + public static void log(Level level, String s) { + Bukkit.getLogger().log(level, s); + } + + static void readConfig(Class clazz, @Nullable Object instance) { + readConfig(configFile, config, clazz, instance); + } + + public static void readConfig(File configFile, YamlConfiguration config, Class clazz, @Nullable Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (!Modifier.isPrivate(method.getModifiers())) continue; + if (method.getParameterTypes().length != 0) continue; + if (method.getReturnType() != Void.TYPE) continue; + + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException ex) { + throw Throwables.propagate(ex.getCause()); + } catch (Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); + } + } + + try { + config.save(configFile); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not save " + configFile, ex); + } + } + + private static void set(YamlConfiguration config, String path, Object val, String... comments) { + config.addDefault(path, val); + config.set(path, val); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + } + + private static String getString(YamlConfiguration config, String path, String def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getString(path, config.getString(path)); + } + + private static boolean getBoolean(YamlConfiguration config, String path, boolean def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getBoolean(path, config.getBoolean(path)); + } + + private static double getDouble(YamlConfiguration config, String path, double def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getDouble(path, config.getDouble(path)); + } + + private static int getInt(YamlConfiguration config, String path, int def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getInt(path, config.getInt(path)); + } + + private static long getLong(YamlConfiguration config, String path, long def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return config.getLong(path, config.getLong(path)); + } + + private static List getList(YamlConfiguration config, String path, List def, String... comments) { + config.addDefault(path, def); + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return (List) config.getList(path, def); + } + + static Map getMap(YamlConfiguration config, String path, Map def, String... comments) { + if (def != null && config.getConfigurationSection(path) == null) { + config.addDefault(path, def); + return def; + } + if (comments.length > 0) { + config.setComments(path, List.of(comments)); + } + return toMap(config.getConfigurationSection(path)); + } + + private static Map toMap(ConfigurationSection section) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (section != null) { + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj != null) { + builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); + } + } + } + return builder.build(); + } + + public static int parallelThreadCount = 4; + public static boolean logContainerCreationStacktraces = false; + private static void parallelWorldTicking() { + parallelThreadCount = getInt(config, "settings.parallel-world-ticking.thread-count", parallelThreadCount); + logContainerCreationStacktraces = getBoolean(config, "settings.parallel-world-ticking.log-container-creation-stacktraces", logContainerCreationStacktraces); + } + + public static boolean nativeAccelerationEnabled = true; + public static boolean allowAVX512 = false; + public static int isaTargetLevelOverride = -1; + public static long chunkDataCacheSoftLimit = 8192L; + public static long chunkDataCacheLimit = 32678L; + public static int maxViewDistance = 32; + public static ChunkSystemAlgorithms chunkWorkerAlgorithm = ChunkSystemAlgorithms.C2ME; + public static boolean enableSecureSeed = false; + public static boolean enableDensityFunctionCompiler = false; + public static boolean enableStructureLayoutOptimizer = true; + public static boolean deduplicateShuffledTemplatePoolElementList = false; + + private static void chunkGeneration() { + nativeAccelerationEnabled = getBoolean(config, "settings.chunk-generation.native-acceleration-enabled", nativeAccelerationEnabled); + + allowAVX512 = getBoolean(config, "settings.chunk-generation.allow-avx512", allowAVX512, + "Enables AVX512 support for natives-math optimizations", + "", + "Read more about AVX512: https://en.wikipedia.org/wiki/AVX-512"); + isaTargetLevelOverride = getInt(config, "settings.chunk-generation.isa-target-level-override", isaTargetLevelOverride, + "Overrides the ISA target located by the native loader, which allows forcing AVX512 (must be a value between 6-9 for AVX512 support).", + "Value must be between 1-9, and -1 to disable override"); + + if (isaTargetLevelOverride < -1 || isaTargetLevelOverride > 9) { + log(Level.WARNING, "Invalid ISA target level override: " + isaTargetLevelOverride + ", resetting to -1"); + isaTargetLevelOverride = -1; + } + + chunkDataCacheSoftLimit = getLong(config, "settings.chunk-generation.chunk-data-cache-soft-limit", chunkDataCacheSoftLimit); + chunkDataCacheLimit = getLong(config, "settings.chunk-generation.chunk-data-cache-limit", chunkDataCacheLimit); + maxViewDistance = getInt(config, "settings.chunk-generation.max-view-distance", maxViewDistance, + "Changes the maximum view distance for the server, allowing clients to have render distances higher than 32"); + + chunkWorkerAlgorithm = ChunkSystemAlgorithms.valueOf(getString(config, "settings.chunk-generation.chunk-worker-algorithm", chunkWorkerAlgorithm.name(), + "Modifies what algorithm the chunk system will use to define thread counts. values: MOONRISE, C2ME, C2ME_AGGRESSIVE")); + + enableSecureSeed = getBoolean(config, "settings.misc.enable-secure-seed", enableSecureSeed, + "This feature is based on Secure Seed mod by Earthcomputer.", + "", + "Terrain and biome generation remains the same, but all the ores and structures are generated with 1024-bit seed, instead of the usual 64-bit seed.", + "This seed is almost impossible to crack, and there are no weird links between structures."); + + enableDensityFunctionCompiler = getBoolean(config, "settings.chunk-generation.experimental.enable-density-function-compiler", enableDensityFunctionCompiler, + "Whether to use density function compiler to accelerate world generation", + "", + "Density function: https://minecraft.wiki/w/Density_function", + "", + "This functionality compiles density functions from world generation", + "datapacks (including vanilla generation) to JVM bytecode to increase", + "performance by allowing JVM JIT to better optimize the code.", + "All functions provided by vanilla are implemented.", + "", + "Please test if this optimization actually benefits your server, as", + "it can sometimes slow down chunk performance than speed it up."); + + enableStructureLayoutOptimizer = getBoolean(config, "settings.chunk-generation.experimental.enable-structure-layout-optimizer", enableStructureLayoutOptimizer, + "Enables a port of the mod StructureLayoutOptimizer, which optimizes general Jigsaw structure generation"); + deduplicateShuffledTemplatePoolElementList = getBoolean(config, "settings.chunk-generation.experimental.deduplicate-shuffled-template-pool-element-list", deduplicateShuffledTemplatePoolElementList, + "Whether to use an alternative strategy to make structure layouts generate slightly even faster than", + "the default optimization this mod has for template pool weights. This alternative strategy works by", + "changing the list of pieces that structures collect from the template pool to not have duplicate entries.", + "", + "This will not break the structure generation, but it will make the structure layout different than", + "if this config was off (breaking vanilla seed parity). The cost of speed may be worth it in large", + "modpacks where many structure mods are using very high weight values in their template pools."); + } + + public static boolean skipUselessSecondaryPoiSensor = true; + public static boolean clumpOrbs = true; + public static boolean ignoreMovedTooQuicklyWhenLagging = true; + public static boolean alwaysAllowWeirdMovement = true; + + private static void miscSettings() { + skipUselessSecondaryPoiSensor = getBoolean(config, "settings.misc.skip-useless-secondary-poi-sensor", skipUselessSecondaryPoiSensor); + clumpOrbs = getBoolean(config, "settings.misc.clump-orbs", clumpOrbs, + "Clumps experience orbs together to reduce entity count"); + ignoreMovedTooQuicklyWhenLagging = getBoolean(config, "settings.misc.ignore-moved-too-quickly-when-lagging", ignoreMovedTooQuicklyWhenLagging, + "Improves general gameplay experience of the player when the server is lagging, as they won't get lagged back (message 'moved too quickly')"); + alwaysAllowWeirdMovement = getBoolean(config, "settings.misc.always-allow-weird-movement", alwaysAllowWeirdMovement, + "Means ignoring messages like 'moved too quickly' and 'moved wrongly'"); + } + + public static boolean enableFasterTntOptimization = true; + public static boolean explosionNoBlockDamage = false; + public static double tntRandomRange = -1; + + private static void tntOptimization() { + enableFasterTntOptimization = getBoolean(config, "settings.tnt-optimization.enable-faster-tnt-optimization", enableFasterTntOptimization); + explosionNoBlockDamage = getBoolean(config, "settings.tnt-optimization.explosion-no-block-damage", explosionNoBlockDamage); + tntRandomRange = getDouble(config, "settings.tnt-optimization.tnt-random-range", tntRandomRange); + } + + public static boolean lagCompensationEnabled = true; + public static boolean blockEntityAcceleration = false; + public static boolean blockBreakingAcceleration = true; + public static boolean eatingAcceleration = true; + public static boolean potionEffectAcceleration = true; + public static boolean fluidAcceleration = true; + public static boolean pickupAcceleration = true; + public static boolean portalAcceleration = true; + public static boolean timeAcceleration = true; + public static boolean randomTickSpeedAcceleration = true; + + private static void lagCompensation() { + lagCompensationEnabled = getBoolean(config, "settings.lag-compensation.enabled", lagCompensationEnabled, "Improves the player experience when TPS is low"); + blockEntityAcceleration = getBoolean(config, "settings.lag-compensation.block-entity-acceleration", blockEntityAcceleration); + blockBreakingAcceleration = getBoolean(config, "settings.lag-compensation.block-breaking-acceleration", blockBreakingAcceleration); + eatingAcceleration = getBoolean(config, "settings.lag-compensation.eating-acceleration", eatingAcceleration); + potionEffectAcceleration = getBoolean(config, "settings.lag-compensation.potion-effect-acceleration", potionEffectAcceleration); + fluidAcceleration = getBoolean(config, "settings.lag-compensation.fluid-acceleration", fluidAcceleration); + pickupAcceleration = getBoolean(config, "settings.lag-compensation.pickup-acceleration", pickupAcceleration); + portalAcceleration = getBoolean(config, "settings.lag-compensation.portal-acceleration", portalAcceleration); + timeAcceleration = getBoolean(config, "settings.lag-compensation.time-acceleration", timeAcceleration); + randomTickSpeedAcceleration = getBoolean(config, "settings.lag-compensation.random-tick-speed-acceleration", randomTickSpeedAcceleration); + } + + public static boolean noChatReportsEnabled = false; + public static boolean noChatReportsAddQueryData = true; + public static boolean noChatReportsConvertToGameMessage = true; + public static boolean noChatReportsDebugLog = false; + public static boolean noChatReportsDemandOnClient = false; + public static String noChatReportsDisconnectDemandOnClientMessage = "You do not have No Chat Reports, and this server is configured to require it on client!"; + + private static void noChatReports() { + noChatReportsEnabled = getBoolean(config, "settings.no-chat-reports.enabled", noChatReportsEnabled, + "Enables or disables the No Chat Reports feature"); + noChatReportsAddQueryData = getBoolean(config, "settings.no-chat-reports.add-query-data", noChatReportsAddQueryData, + "Should server include extra query data to help clients know that your server is secure"); + noChatReportsConvertToGameMessage = getBoolean(config, "settings.no-chat-reports.convert-to-game-message", noChatReportsConvertToGameMessage, + "Should the server convert all player messages to system messages"); + noChatReportsDebugLog = getBoolean(config, "settings.no-chat-reports.debug-log", noChatReportsDebugLog); + noChatReportsDemandOnClient = getBoolean(config, "settings.no-chat-reports.demand-on-client", noChatReportsDemandOnClient, + "Should the server require No Chat Reports on the client side"); + noChatReportsDisconnectDemandOnClientMessage = getString(config, "settings.no-chat-reports.disconnect-demand-on-client-message", noChatReportsDisconnectDemandOnClientMessage, + "Message to send to the client when they are disconnected for not having No Chat Reports"); + } + + public static boolean asyncPathfinding = true; + public static int asyncPathfindingMaxThreads = 2; + public static int asyncPathfindingKeepalive = 60; + + private static void asyncPathfinding() { + asyncPathfinding = getBoolean(config, "settings.async-pathfinding.enable", asyncPathfinding); + asyncPathfindingMaxThreads = getInt(config, "settings.async-pathfinding.max-threads", asyncPathfindingMaxThreads); + asyncPathfindingKeepalive = getInt(config, "settings.async-pathfinding.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 (!asyncPathfinding) { + asyncPathfindingMaxThreads = 0; + } else { + Bukkit.getLogger().log(Level.INFO, "Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding"); + } + } + + public static boolean multithreadedEnabled = true; + public static boolean multithreadedCompatModeEnabled = false; + public static int asyncEntityTrackerMaxThreads = 1; + public static int asyncEntityTrackerKeepalive = 60; + + private static void multithreadedTracker() { + multithreadedEnabled = getBoolean(config, "settings.multithreaded-tracker.enable", multithreadedEnabled); + multithreadedCompatModeEnabled = getBoolean(config, "settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled); + asyncEntityTrackerMaxThreads = getInt(config, "settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads); + asyncEntityTrackerKeepalive = getInt(config, "settings.multithreaded-tracker.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 (!multithreadedEnabled) { + asyncEntityTrackerMaxThreads = 0; + } else { + Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker"); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java new file mode 100644 index 0000000..2ef9680 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineWorldConfig.java @@ -0,0 +1,82 @@ +package org.bxteam.divinemc; + +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jspecify.annotations.NullMarked; + +import java.util.List; +import java.util.Map; + +import static org.bxteam.divinemc.DivineConfig.log; + +@NullMarked +public final class DivineWorldConfig { + private final YamlConfiguration config; + private final String worldName; + private final World.Environment environment; + + public DivineWorldConfig(String worldName, World.Environment environment) { + this.config = DivineConfig.config; + this.worldName = worldName; + this.environment = environment; + init(); + } + + public void init() { + log("-------- World Settings For [" + worldName + "] --------"); + DivineConfig.readConfig(DivineWorldConfig.class, this); + } + + private void set(String path, Object val) { + this.config.addDefault("world-settings.default." + path, val); + this.config.set("world-settings.default." + path, val); + if (this.config.get("world-settings." + worldName + "." + path) != null) { + this.config.addDefault("world-settings." + worldName + "." + path, val); + this.config.set("world-settings." + worldName + "." + path, val); + } + } + + private ConfigurationSection getConfigurationSection(String path) { + ConfigurationSection section = this.config.getConfigurationSection("world-settings." + worldName + "." + path); + return section != null ? section : this.config.getConfigurationSection("world-settings.default." + path); + } + + private String getString(String path, String def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getString("world-settings." + worldName + "." + path, this.config.getString("world-settings.default." + path)); + } + + private boolean getBoolean(String path, boolean def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getBoolean("world-settings." + worldName + "." + path, this.config.getBoolean("world-settings.default." + path)); + } + + private double getDouble(String path, double def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getDouble("world-settings." + worldName + "." + path, this.config.getDouble("world-settings.default." + path)); + } + + private int getInt(String path, int def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getInt("world-settings." + worldName + "." + path, this.config.getInt("world-settings.default." + path)); + } + + private List getList(String path, T def) { + this.config.addDefault("world-settings.default." + path, def); + return this.config.getList("world-settings." + worldName + "." + path, this.config.getList("world-settings.default." + path)); + } + + private Map getMap(String path, Map def) { + final Map fallback = this.getMap("world-settings.default." + path, def); + final Map value = this.getMap("world-settings." + worldName + "." + path, null); + return value.isEmpty() ? fallback : value; + } + + public boolean snowballCanKnockback = true; + public boolean eggCanKnockback = true; + private void setSnowballAndEggKnockback() { + snowballCanKnockback = getBoolean("gameplay-mechanics.projectiles.snowball.knockback", snowballCanKnockback); + eggCanKnockback = getBoolean("gameplay-mechanics.projectiles.egg.knockback", eggCanKnockback); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java b/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java index 7949f98..c2cd80e 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/command/DivineCommands.java @@ -2,7 +2,6 @@ package org.bxteam.divinemc.command; import net.minecraft.server.MinecraftServer; import org.bukkit.command.Command; -import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.framework.qual.DefaultQualifier; @@ -11,7 +10,7 @@ import java.util.Map; @DefaultQualifier(NonNull.class) public final class DivineCommands { - public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.DIVINEMC_ROOT + ".command"; + public static final String COMMAND_BASE_PERM = "divinemc.command"; private DivineCommands() {} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java b/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java index 9999658..9efae38 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/command/subcommands/ReloadCommand.java @@ -8,7 +8,7 @@ import org.bukkit.craftbukkit.CraftServer; import org.bukkit.permissions.PermissionDefault; import org.bxteam.divinemc.command.DivineCommand; import org.bxteam.divinemc.command.DivineSubCommandPermission; -import org.bxteam.divinemc.configuration.DivineConfig; +import org.bxteam.divinemc.DivineConfig; import java.io.File; @@ -37,7 +37,7 @@ public final class ReloadCommand extends DivineSubCommandPermission { MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); DivineConfig.init((File) server.options.valueOf("divinemc-settings")); for (ServerLevel level : server.getAllLevels()) { - level.divinemcConfig.init(); + level.divineConfig.init(); level.resetBreedingCooldowns(); } server.server.reloadCount++; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java deleted file mode 100644 index 2e6a9d0..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineConfig.java +++ /dev/null @@ -1,218 +0,0 @@ -package org.bxteam.divinemc.configuration; - -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableMap; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockBehaviour; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; - -import 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.List; -import java.util.Map; -import java.util.logging.Level; - -@SuppressWarnings("unused") -public class DivineConfig { - private static final String HEADER = "This is the main configuration file for DivineMC.\n" - + "If you need help with the configuration or have any questions related to DivineMC,\n" - + "join us in our Discord server.\n" - + "\n" - + "Discord: https://discord.gg/p7cxhw7E2M \n" - + "Docs: https://bxteam.org/docs/divinemc \n" - + "New builds: https://github.com/BX-Team/DivineMC/releases/latest"; - private static File CONFIG_FILE; - public static YamlConfiguration config; - - private static Map commands; - - public static int version; - static boolean verbose; - - public static void init(File configFile) { - CONFIG_FILE = configFile; - config = new YamlConfiguration(); - try { - config.load(CONFIG_FILE); - } catch (IOException ignore) { - } catch (InvalidConfigurationException ex) { - Bukkit.getLogger().log(Level.SEVERE, "Could not load divinemc.yml, please correct your syntax errors", ex); - throw Throwables.propagate(ex); - } - config.options().header(HEADER); - config.options().copyDefaults(true); - verbose = getBoolean("verbose", false); - - version = getInt("config-version", 4); - set("config-version", 4); - - readConfig(DivineConfig.class, null); - - Block.BLOCK_STATE_REGISTRY.forEach(BlockBehaviour.BlockStateBase::initCache); - } - - protected static void log(String s) { - if (verbose) { - log(Level.INFO, s); - } - } - - protected static void log(Level level, String s) { - Bukkit.getLogger().log(level, s); - } - - static void readConfig(Class clazz, Object instance) { - for (Method method : clazz.getDeclaredMethods()) { - if (Modifier.isPrivate(method.getModifiers())) { - if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { - try { - method.setAccessible(true); - method.invoke(instance); - } catch (InvocationTargetException ex) { - throw Throwables.propagate(ex.getCause()); - } catch (Exception ex) { - Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); - } - } - } - } - - try { - config.save(CONFIG_FILE); - } catch (IOException ex) { - Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); - } - } - - private static void set(String path, Object val) { - config.addDefault(path, val); - config.set(path, val); - } - - private static String getString(String path, String def) { - config.addDefault(path, def); - return config.getString(path, config.getString(path)); - } - - private static boolean getBoolean(String path, boolean def) { - config.addDefault(path, def); - return config.getBoolean(path, config.getBoolean(path)); - } - - private static double getDouble(String path, double def) { - config.addDefault(path, def); - return config.getDouble(path, config.getDouble(path)); - } - - private static int getInt(String path, int def) { - config.addDefault(path, def); - return config.getInt(path, config.getInt(path)); - } - - private static List getList(String path, T def) { - config.addDefault(path, def); - return config.getList(path, config.getList(path)); - } - - static Map getMap(String path, Map def) { - if (def != null && config.getConfigurationSection(path) == null) { - config.addDefault(path, def); - return def; - } - return toMap(config.getConfigurationSection(path)); - } - - private static Map toMap(ConfigurationSection section) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - if (section != null) { - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj != null) { - builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); - } - } - } - return builder.build(); - } - - public static boolean noChatSign = true; - private static void chatMessageSignatures() { - noChatSign = getBoolean("settings.no-chat-sign", noChatSign); - } - - public static boolean optimizedDragonRespawn = true; - public static boolean lagCompensationEnabled = false; - public static boolean lagCompensationEnableForWater = false; - public static boolean lagCompensationEnableForLava = false; - private static void optimizations() { - optimizedDragonRespawn = getBoolean("settings.optimizations.optimized-dragon-respawn", optimizedDragonRespawn); - lagCompensationEnabled = getBoolean("settings.optimizations.lag-compensation.enabled", lagCompensationEnabled); - lagCompensationEnableForWater = getBoolean("settings.optimizations.lag-compensation.enable-for-water", lagCompensationEnableForWater); - lagCompensationEnableForLava = getBoolean("settings.optimizations.lag-compensation.enable-for-lava", lagCompensationEnableForLava); - } - - public static boolean disableNonEditableSignWarning = true; - public static boolean removeVanillaUsernameCheck = false; - public static boolean disableMovedWronglyThreshold = false; - public static boolean enableSecureSeed = false; - public static boolean asyncPlayerDataSaveEnabled = false; - private static void miscSettings() { - disableNonEditableSignWarning = getBoolean("settings.misc.disable-non-editable-sign-warning", disableNonEditableSignWarning); - removeVanillaUsernameCheck = getBoolean("settings.misc.remove-vanilla-username-check", removeVanillaUsernameCheck); - disableMovedWronglyThreshold = getBoolean("settings.misc.disable-moved-wrongly-threshold", disableMovedWronglyThreshold); - enableSecureSeed = getBoolean("settings.misc.enable-secure-seed", enableSecureSeed); - asyncPlayerDataSaveEnabled = getBoolean("settings.misc.experimental.async-player-data-save-enabled", asyncPlayerDataSaveEnabled); - } - - public static int linearFlushFrequency = 5; - public static boolean throwOnUnknownExtension = false; - private static void linearSettings() { - linearFlushFrequency = getInt("settings.region-format.linear.flush-frequency", linearFlushFrequency); - throwOnUnknownExtension = getBoolean("settings.region-format.linear.throw-on-unknown-extension", throwOnUnknownExtension); - } - - public static boolean asyncPathfinding = true; - public static int asyncPathfindingMaxThreads = 0; - public static int asyncPathfindingKeepalive = 60; - private static void asyncPathfinding() { - asyncPathfinding = getBoolean("settings.async-pathfinding.enable", asyncPathfinding); - asyncPathfindingMaxThreads = getInt("settings.async-pathfinding.max-threads", asyncPathfindingMaxThreads); - asyncPathfindingKeepalive = getInt("settings.async-pathfinding.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 (!asyncPathfinding) - asyncPathfindingMaxThreads = 0; - else - Bukkit.getLogger().log(Level.INFO, "Using " + asyncPathfindingMaxThreads + " threads for Async Pathfinding"); - } - - public static boolean multithreadedEnabled = false; - public static boolean multithreadedCompatModeEnabled = false; - public static int asyncEntityTrackerMaxThreads = 0; - public static int asyncEntityTrackerKeepalive = 60; - private static void multithreadedTracker() { - multithreadedEnabled = getBoolean("settings.multithreaded-tracker.enable", multithreadedEnabled); - multithreadedCompatModeEnabled = getBoolean("settings.multithreaded-tracker.compat-mode", multithreadedCompatModeEnabled); - asyncEntityTrackerMaxThreads = getInt("settings.multithreaded-tracker.max-threads", asyncEntityTrackerMaxThreads); - asyncEntityTrackerKeepalive = getInt("settings.multithreaded-tracker.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 (!multithreadedEnabled) - asyncEntityTrackerMaxThreads = 0; - else - Bukkit.getLogger().log(Level.INFO, "Using " + asyncEntityTrackerMaxThreads + " threads for Async Entity Tracker"); - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java deleted file mode 100644 index f68f4b4..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/configuration/DivineWorldConfig.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.bxteam.divinemc.configuration; - -import org.apache.commons.lang.BooleanUtils; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bxteam.divinemc.region.RegionFileFormat; - -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.logging.Level; - -import static org.bxteam.divinemc.configuration.DivineConfig.log; - -@SuppressWarnings("unused") -public class DivineWorldConfig { - private final String worldName; - private final World.Environment environment; - - public DivineWorldConfig(String worldName, World.Environment environment) { - this.worldName = worldName; - this.environment = environment; - init(); - } - - public void init() { - log("-------- World Settings For [" + worldName + "] --------"); - DivineConfig.readConfig(DivineWorldConfig.class, this); - } - - private void set(String path, Object val) { - DivineConfig.config.addDefault("world-settings.default." + path, val); - DivineConfig.config.set("world-settings.default." + path, val); - if (DivineConfig.config.get("world-settings." + worldName + "." + path) != null) { - DivineConfig.config.addDefault("world-settings." + worldName + "." + path, val); - DivineConfig.config.set("world-settings." + worldName + "." + path, val); - } - } - - private ConfigurationSection getConfigurationSection(String path) { - ConfigurationSection section = DivineConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); - return section != null ? section : DivineConfig.config.getConfigurationSection("world-settings.default." + path); - } - - private String getString(String path, String def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getString("world-settings." + worldName + "." + path, DivineConfig.config.getString("world-settings.default." + path)); - } - - private boolean getBoolean(String path, boolean def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getBoolean("world-settings." + worldName + "." + path, DivineConfig.config.getBoolean("world-settings.default." + path)); - } - - private boolean getBoolean(String path, Predicate predicate) { - String val = getString(path, "default").toLowerCase(); - Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default"); - return predicate.test(bool); - } - - private double getDouble(String path, double def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getDouble("world-settings." + worldName + "." + path, DivineConfig.config.getDouble("world-settings.default." + path)); - } - - private int getInt(String path, int def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getInt("world-settings." + worldName + "." + path, DivineConfig.config.getInt("world-settings.default." + path)); - } - - private List getList(String path, T def) { - DivineConfig.config.addDefault("world-settings.default." + path, def); - return DivineConfig.config.getList("world-settings." + worldName + "." + path, DivineConfig.config.getList("world-settings.default." + path)); - } - - private Map getMap(String path, Map def) { - final Map fallback = DivineConfig.getMap("world-settings.default." + path, def); - final Map value = DivineConfig.getMap("world-settings." + worldName + "." + path, null); - return value.isEmpty() ? fallback : value; - } - - public boolean despawnShulkerBulletsOnOwnerDeath = true; - private void despawnShulkerBulletsOnOwnerDeath() { - despawnShulkerBulletsOnOwnerDeath = getBoolean("gameplay-mechanics.mob.shulker.despawn-bullets-on-player-death", despawnShulkerBulletsOnOwnerDeath); - } - - public boolean saveFireworks = false; - private void projectiles() { - saveFireworks = getBoolean("gameplay-mechanics.should-save-fireworks", saveFireworks); - } - - public boolean suppressErrorsFromDirtyAttributes = true; - private void suppressErrorsFromDirtyAttributes() { - suppressErrorsFromDirtyAttributes = getBoolean("suppress-errors-from-dirty-attributes", suppressErrorsFromDirtyAttributes); - } - - public boolean snowballCanKnockback = true; - public boolean eggCanKnockback = true; - private void setSnowballAndEggKnockback() { - snowballCanKnockback = getBoolean("gameplay-mechanics.projectiles.snowball.knockback", snowballCanKnockback); - eggCanKnockback = getBoolean("gameplay-mechanics.projectiles.egg.knockback", eggCanKnockback); - } - - public RegionFileFormat regionFormatName = RegionFileFormat.MCA; - public int linearCompressionLevel = 1; - private void regionFormatSettings() { - regionFormatName = RegionFileFormat.fromExtension(getString("region-format.format", regionFormatName.name())); - if (regionFormatName.equals(RegionFileFormat.UNKNOWN)) { - log(Level.SEVERE, "Unknown region file type!"); - log(Level.SEVERE, "Falling back to ANVIL region file format."); - regionFormatName = RegionFileFormat.MCA; - } - - linearCompressionLevel = getInt("region-format.linear.compression-level", linearCompressionLevel); - if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { - log(Level.SEVERE, "Linear region compression level should be between 1 and 22 in config: " + linearCompressionLevel); - log(Level.SEVERE, "Falling back to compression level 1."); - linearCompressionLevel = 1; - } - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java new file mode 100644 index 0000000..1fa43ca --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/IDensityFunctionsCaveScaler.java @@ -0,0 +1,13 @@ +package org.bxteam.divinemc.dfc.common; + +import net.minecraft.world.level.levelgen.NoiseRouterData; + +public interface IDensityFunctionsCaveScaler { + static double invokeScaleCaves(double value) { + return NoiseRouterData.QuantizedSpaghettiRarity.getSphaghettiRarity2D(value); + } + + static double invokeScaleTunnels(double value) { + return NoiseRouterData.QuantizedSpaghettiRarity.getSpaghettiRarity3D(value); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java new file mode 100644 index 0000000..c61fcb7 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstNode.java @@ -0,0 +1,22 @@ +package org.bxteam.divinemc.dfc.common.ast; + +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.commons.InstructionAdapter; + +public interface AstNode { + double evalSingle(int var1, int var2, int var3, EvalType var4); + + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + + AstNode[] getChildren(); + + AstNode transform(AstTransformer var1); + + void doBytecodeGenSingle(BytecodeGen.Context var1, InstructionAdapter var2, BytecodeGen.Context.LocalVarConsumer var3); + + void doBytecodeGenMulti(BytecodeGen.Context var1, InstructionAdapter var2, BytecodeGen.Context.LocalVarConsumer var3); + + boolean relaxedEquals(AstNode var1); + + int relaxedHashCode(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java new file mode 100644 index 0000000..81017d3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/AstTransformer.java @@ -0,0 +1,5 @@ +package org.bxteam.divinemc.dfc.common.ast; + +public interface AstTransformer { + AstNode transform(AstNode var1); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java new file mode 100644 index 0000000..3e19f35 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/EvalType.java @@ -0,0 +1,25 @@ +package org.bxteam.divinemc.dfc.common.ast; + +import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseChunk; + +public enum EvalType { + NORMAL, + INTERPOLATION; + + private EvalType() { + } + + public static EvalType from(DensityFunction.FunctionContext pos) { + return pos instanceof NoiseChunk ? INTERPOLATION : NORMAL; + } + + public static EvalType from(DensityFunction.ContextProvider applier) { + if (applier instanceof EachApplierVanillaInterface vif) { + return vif.getType(); + } else { + return applier instanceof NoiseChunk ? INTERPOLATION : NORMAL; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java new file mode 100644 index 0000000..532e6fb --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/McToAst.java @@ -0,0 +1,106 @@ +package org.bxteam.divinemc.dfc.common.ast; + +import org.bxteam.divinemc.dfc.common.ast.binary.AddNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MaxNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MaxShortNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MinNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MinShortNode; +import org.bxteam.divinemc.dfc.common.ast.binary.MulNode; +import org.bxteam.divinemc.dfc.common.ast.misc.CacheLikeNode; +import org.bxteam.divinemc.dfc.common.ast.misc.ConstantNode; +import org.bxteam.divinemc.dfc.common.ast.misc.DelegateNode; +import org.bxteam.divinemc.dfc.common.ast.misc.RangeChoiceNode; +import org.bxteam.divinemc.dfc.common.ast.misc.YClampedGradientNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTNoiseNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTShiftANode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTShiftBNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTShiftNode; +import org.bxteam.divinemc.dfc.common.ast.noise.DFTWeirdScaledSamplerNode; +import org.bxteam.divinemc.dfc.common.ast.noise.ShiftedNoiseNode; +import org.bxteam.divinemc.dfc.common.ast.spline.SplineAstNode; +import org.bxteam.divinemc.dfc.common.ast.unary.AbsNode; +import org.bxteam.divinemc.dfc.common.ast.unary.CubeNode; +import org.bxteam.divinemc.dfc.common.ast.unary.NegMulNode; +import org.bxteam.divinemc.dfc.common.ast.unary.SquareNode; +import org.bxteam.divinemc.dfc.common.ast.unary.SqueezeNode; +import org.bxteam.divinemc.dfc.common.ducks.IEqualityOverriding; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import org.bxteam.divinemc.dfc.common.vif.AstVanillaInterface; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import net.minecraft.world.level.levelgen.NoiseChunk; +import org.jetbrains.annotations.NotNull; + +public class McToAst { + public McToAst() { + } + + public static AstNode toAst(DensityFunction df) { + Objects.requireNonNull(df); + return switch (df) { + case AstVanillaInterface f -> f.getAstNode(); + + case NoiseChunk.BlendAlpha f -> new ConstantNode(1.0); + case NoiseChunk.BlendOffset f -> new ConstantNode(0.0); + case DensityFunctions.BlendAlpha f -> new ConstantNode(1.0); + case DensityFunctions.BlendOffset f -> new ConstantNode(0.0); + case DensityFunctions.TwoArgumentSimpleFunction f -> switch (f.type()) { + case ADD -> new AddNode(toAst(f.argument1()), toAst(f.argument2())); + case MUL -> new MulNode(toAst(f.argument1()), toAst(f.argument2())); + case MIN -> { + double rightMin = f.argument2().minValue(); + if (f.argument1().minValue() < rightMin) { + yield new MinShortNode(toAst(f.argument1()), toAst(f.argument2()), rightMin); + } else { + yield new MinNode(toAst(f.argument1()), toAst(f.argument2())); + } + } + case MAX -> { + double rightMax = f.argument2().maxValue(); + if (f.argument1().maxValue() > rightMax) { + yield new MaxShortNode(toAst(f.argument1()), toAst(f.argument2()), rightMax); + } else { + yield new MaxNode(toAst(f.argument1()), toAst(f.argument2())); + } + } + }; + case DensityFunctions.BlendDensity f -> toAst(f.input()); + case DensityFunctions.Clamp f -> new MaxNode(new ConstantNode(f.minValue()), new MinNode(new ConstantNode(f.maxValue()), toAst(f.input()))); + case DensityFunctions.Constant f -> new ConstantNode(f.value()); + case DensityFunctions.HolderHolder f -> toAst(f.function().value()); + case DensityFunctions.Mapped f -> switch (f.type()) { + case ABS -> new AbsNode(toAst(f.input())); + case SQUARE -> new SquareNode(toAst(f.input())); + case CUBE -> new CubeNode(toAst(f.input())); + case HALF_NEGATIVE -> new NegMulNode(toAst(f.input()), 0.5); + case QUARTER_NEGATIVE -> new NegMulNode(toAst(f.input()), 0.25); + case SQUEEZE -> new SqueezeNode(toAst(f.input())); + }; + case DensityFunctions.RangeChoice f -> new RangeChoiceNode(toAst(f.input()), f.minInclusive(), f.maxExclusive(), toAst(f.whenInRange()), toAst(f.whenOutOfRange())); + case DensityFunctions.Marker f -> { + DensityFunctions.Marker wrapping = new DensityFunctions.Marker(f.type(), new AstVanillaInterface(toAst(f.wrapped()), null)); + ((IEqualityOverriding) (Object) wrapping).c2me$overrideEquality(wrapping); + yield new DelegateNode(wrapping); + } + case IFastCacheLike f -> new CacheLikeNode(f, toAst(f.c2me$getDelegate())); + case DensityFunctions.ShiftedNoise f -> new ShiftedNoiseNode(toAst(f.shiftX()), toAst(f.shiftY()), toAst(f.shiftZ()), f.xzScale(), f.yScale(), f.noise()); + case DensityFunctions.Noise f -> new DFTNoiseNode(f.noise(), f.xzScale(), f.yScale()); + case DensityFunctions.Shift f -> new DFTShiftNode(f.offsetNoise()); + case DensityFunctions.ShiftA f -> new DFTShiftANode(f.offsetNoise()); + case DensityFunctions.ShiftB f -> new DFTShiftBNode(f.offsetNoise()); + case DensityFunctions.YClampedGradient f -> new YClampedGradientNode(f.fromY(), f.toY(), f.fromValue(), f.toValue()); + case DensityFunctions.WeirdScaledSampler f -> new DFTWeirdScaledSamplerNode(toAst(f.input()), f.noise(), f.rarityValueMapper()); + case DensityFunctions.Spline f -> new SplineAstNode(f.spline()); + + default -> { +// delegateStatistics.computeIfAbsent(df.getClass(), unused -> new LongAdder()).increment();; + yield new DelegateNode(df); + } + }; + } + + public static @NotNull DensityFunction wrapVanilla(DensityFunction densityFunction) { + return new AstVanillaInterface(toAst(densityFunction), densityFunction); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java new file mode 100644 index 0000000..7c01c36 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AbstractBinaryNode.java @@ -0,0 +1,106 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.Objects; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public abstract class AbstractBinaryNode implements AstNode { + protected final AstNode left; + protected final AstNode right; + + public AbstractBinaryNode(AstNode left, AstNode right) { + this.left = (AstNode)Objects.requireNonNull(left); + this.right = (AstNode)Objects.requireNonNull(right); + } + + public AstNode[] getChildren() { + return new AstNode[]{this.left, this.right}; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractBinaryNode that = (AbstractBinaryNode)o; + return Objects.equals(this.left, that.left) && Objects.equals(this.right, that.right); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.left.hashCode(); + result = 31 * result + this.right.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractBinaryNode that = (AbstractBinaryNode)o; + return this.left.relaxedEquals(that.left) && this.right.relaxedEquals(that.right); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.left.relaxedHashCode(); + result = 31 * result + this.right.relaxedHashCode(); + return result; + } + + protected abstract AstNode newInstance(AstNode var1, AstNode var2); + + public AstNode transform(AstTransformer transformer) { + AstNode left = this.left.transform(transformer); + AstNode right = this.right.transform(transformer); + return left == this.left && right == this.right ? transformer.transform(this) : transformer.transform(this.newInstance(left, right)); + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + context.callDelegateSingle(m, leftMethod); + context.callDelegateSingle(m, rightMethod); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethod = context.newMultiMethod(this.right); + int res1 = localVarConsumer.createLocalVariable("res1", Type.getDescriptor(double[].class)); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(0); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "getDoubleArray", Type.getMethodDescriptor(Type.getType(double[].class), new Type[]{Type.INT_TYPE, Type.BOOLEAN_TYPE}), false); + m.store(res1, InstructionAdapter.OBJECT_TYPE); + context.callDelegateMulti(m, leftMethod); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethod, Context.MULTI_DESC, false); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + this.bytecodeGenMultiBody(m, idx, res1); + }); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "recycle", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class)}), false); + m.areturn(Type.VOID_TYPE); + } + + protected abstract void bytecodeGenMultiBody(InstructionAdapter var1, int var2, int var3); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java new file mode 100644 index 0000000..53b50d6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/AddNode.java @@ -0,0 +1,50 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class AddNode extends AbstractBinaryNode { + public AddNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new AddNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.left.evalSingle(x, y, z, type) + this.right.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + this.left.evalMulti(res, x, y, z, type); + this.right.evalMulti(res1, x, y, z, type); + + for(int i = 0; i < res1.length; ++i) { + res[i] += res1[i]; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.add(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java new file mode 100644 index 0000000..bed1a4c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxNode.java @@ -0,0 +1,50 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MaxNode extends AbstractBinaryNode { + public MaxNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MaxNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Math.max(this.left.evalSingle(x, y, z, type), this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + this.left.evalMulti(res, x, y, z, type); + this.right.evalMulti(res1, x, y, z, type); + + for(int i = 0; i < res1.length; ++i) { + res[i] = Math.max(res[i], res1[i]); + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.astore(Type.DOUBLE_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java new file mode 100644 index 0000000..8cab854 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MaxShortNode.java @@ -0,0 +1,92 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MaxShortNode extends AbstractBinaryNode { + private final double rightMax; + + public MaxShortNode(AstNode left, AstNode right, double rightMax) { + super(left, right); + this.rightMax = rightMax; + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MaxShortNode(left, right, this.rightMax); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double evaled = this.left.evalSingle(x, y, z, type); + return evaled >= this.rightMax ? evaled : Math.max(evaled, this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.left.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] >= this.rightMax ? res[i] : Math.max(res[i], this.right.evalSingle(x[i], y[i], z[i], type)); + } + + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + Label minLabel = new Label(); + context.callDelegateSingle(m, leftMethod); + m.dup2(); + m.dconst(this.rightMax); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(minLabel); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(minLabel); + context.callDelegateSingle(m, rightMethod); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethodSingle = context.newSingleMethod(this.right); + context.callDelegateMulti(m, leftMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label minLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dconst(this.rightMax); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(minLabel); + m.goTo(end); + m.visitLabel(minLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethodSingle, Context.SINGLE_DESC, false); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + throw new UnsupportedOperationException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java new file mode 100644 index 0000000..909180b --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinNode.java @@ -0,0 +1,50 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MinNode extends AbstractBinaryNode { + public MinNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MinNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Math.min(this.left.evalSingle(x, y, z, type), this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + this.left.evalMulti(res, x, y, z, type); + this.right.evalMulti(res1, x, y, z, type); + + for(int i = 0; i < res1.length; ++i) { + res[i] = Math.min(res[i], res1[i]); + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.astore(Type.DOUBLE_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java new file mode 100644 index 0000000..615ca64 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MinShortNode.java @@ -0,0 +1,92 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MinShortNode extends AbstractBinaryNode { + private final double rightMin; + + public MinShortNode(AstNode left, AstNode right, double rightMin) { + super(left, right); + this.rightMin = rightMin; + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MinShortNode(left, right, this.rightMin); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double evaled = this.left.evalSingle(x, y, z, type); + return evaled <= this.rightMin ? evaled : Math.min(evaled, this.right.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.left.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] <= this.rightMin ? res[i] : Math.min(res[i], this.right.evalSingle(x[i], y[i], z[i], type)); + } + + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + Label minLabel = new Label(); + context.callDelegateSingle(m, leftMethod); + m.dup2(); + m.dconst(this.rightMin); + m.cmpg(Type.DOUBLE_TYPE); + m.ifgt(minLabel); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(minLabel); + context.callDelegateSingle(m, rightMethod); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethodSingle = context.newSingleMethod(this.right); + context.callDelegateMulti(m, leftMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label minLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dconst(this.rightMin); + m.cmpg(Type.DOUBLE_TYPE); + m.ifgt(minLabel); + m.goTo(end); + m.visitLabel(minLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethodSingle, Context.SINGLE_DESC, false); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + throw new UnsupportedOperationException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java new file mode 100644 index 0000000..87cc44c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/binary/MulNode.java @@ -0,0 +1,92 @@ +package org.bxteam.divinemc.dfc.common.ast.binary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class MulNode extends AbstractBinaryNode { + public MulNode(AstNode left, AstNode right) { + super(left, right); + } + + protected AstNode newInstance(AstNode left, AstNode right) { + return new MulNode(left, right); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double evaled = this.left.evalSingle(x, y, z, type); + return evaled == 0.0 ? 0.0 : evaled * this.right.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.left.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] == 0.0 ? 0.0 : res[i] * this.right.evalSingle(x[i], y[i], z[i], type); + } + + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newSingleMethod(this.left); + String rightMethod = context.newSingleMethod(this.right); + Label notZero = new Label(); + context.callDelegateSingle(m, leftMethod); + m.dup2(); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifne(notZero); + m.dconst(0.0); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(notZero); + context.callDelegateSingle(m, rightMethod); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String leftMethod = context.newMultiMethod(this.left); + String rightMethodSingle = context.newSingleMethod(this.right); + context.callDelegateMulti(m, leftMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label minLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifne(minLabel); + m.pop2(); + m.dconst(0.0); + m.goTo(end); + m.visitLabel(minLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, rightMethodSingle, Context.SINGLE_DESC, false); + m.mul(Type.DOUBLE_TYPE); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + protected void bytecodeGenMultiBody(InstructionAdapter m, int idx, int res1) { + throw new UnsupportedOperationException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java new file mode 100644 index 0000000..1144bfa --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/dfvisitor/StripBlending.java @@ -0,0 +1,23 @@ +package org.bxteam.divinemc.dfc.common.ast.dfvisitor; + +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import net.minecraft.world.level.levelgen.NoiseChunk; +import org.jetbrains.annotations.NotNull; + +public class StripBlending implements DensityFunction.Visitor { + public static final StripBlending INSTANCE = new StripBlending(); + + private StripBlending() { + } + + public @NotNull DensityFunction apply(@NotNull DensityFunction densityFunction) { + return switch (densityFunction) { + case NoiseChunk.BlendAlpha _ -> DensityFunctions.constant(1.0); + case NoiseChunk.BlendOffset _ -> DensityFunctions.constant(0.0); + case DensityFunctions.BlendAlpha _ -> DensityFunctions.constant(1.0); + case DensityFunctions.BlendOffset _ -> DensityFunctions.constant(0.0); + default -> densityFunction; + }; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java new file mode 100644 index 0000000..d9a2689 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/CacheLikeNode.java @@ -0,0 +1,256 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.bxteam.divinemc.dfc.common.gen.IMultiMethod; +import org.bxteam.divinemc.dfc.common.gen.ISingleMethod; +import org.bxteam.divinemc.dfc.common.gen.SubCompiledDensityFunction; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class CacheLikeNode implements AstNode { + private final IFastCacheLike cacheLike; + private final AstNode delegate; + + public CacheLikeNode(IFastCacheLike cacheLike, AstNode delegate) { + this.cacheLike = cacheLike; + this.delegate = (AstNode)Objects.requireNonNull(delegate); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + if (this.cacheLike == null) { + return this.delegate.evalSingle(x, y, z, type); + } else { + double cached = this.cacheLike.c2me$getCached(x, y, z, type); + if (Double.doubleToRawLongBits(cached) != 9222769054270909007L) { + return cached; + } else { + double eval = this.delegate.evalSingle(x, y, z, type); + this.cacheLike.c2me$cache(x, y, z, type, eval); + return eval; + } + } + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (this.cacheLike == null) { + this.delegate.evalMulti(res, x, y, z, type); + } else { + boolean cached = this.cacheLike.c2me$getCached(res, x, y, z, type); + if (!cached) { + this.delegate.evalMulti(res, x, y, z, type); + this.cacheLike.c2me$cache(res, x, y, z, type); + } + + } + } + + public AstNode[] getChildren() { + return new AstNode[]{this.delegate}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode delegate = this.delegate.transform(transformer); + return this.delegate == delegate ? transformer.transform(this) : transformer.transform(new CacheLikeNode(this.cacheLike, delegate)); + } + + public void doBytecodeGenSingle(@NotNull Context context, @NotNull InstructionAdapter m, Context.@NotNull LocalVarConsumer localVarConsumer) { + String delegateMethod = context.newSingleMethod(this.delegate); + String cacheLikeField = context.newField(IFastCacheLike.class, this.cacheLike); + this.genPostprocessingMethod(context, cacheLikeField); + int eval = localVarConsumer.createLocalVariable("eval", Type.DOUBLE_TYPE.getDescriptor()); + Label cacheExists = new Label(); + Label cacheMiss = new Label(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.ifnonnull(cacheExists); + context.callDelegateSingle(m, delegateMethod); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(cacheExists); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$getCached", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class)})); + m.dup2(); + m.invokestatic(Type.getInternalName(Double.class), "doubleToRawLongBits", Type.getMethodDescriptor(Type.LONG_TYPE, new Type[]{Type.DOUBLE_TYPE}), false); + m.lconst(9222769054270909007L); + m.lcmp(); + m.ifeq(cacheMiss); + m.areturn(Type.DOUBLE_TYPE); + m.visitLabel(cacheMiss); + m.pop2(); + context.callDelegateSingle(m, delegateMethod); + m.store(eval, Type.DOUBLE_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(eval, Type.DOUBLE_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$cache", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class), Type.DOUBLE_TYPE})); + m.load(eval, Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(@NotNull Context context, @NotNull InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String delegateMethod = context.newMultiMethod(this.delegate); + String cacheLikeField = context.newField(IFastCacheLike.class, this.cacheLike); + this.genPostprocessingMethod(context, cacheLikeField); + Label cacheExists = new Label(); + Label cacheMiss = new Label(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.ifnonnull(cacheExists); + context.callDelegateMulti(m, delegateMethod); + m.areturn(Type.VOID_TYPE); + m.visitLabel(cacheExists); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$getCached", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[]{Type.getType(double[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class)})); + m.ifeq(cacheMiss); + m.areturn(Type.VOID_TYPE); + m.visitLabel(cacheMiss); + context.callDelegateMulti(m, delegateMethod); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$cache", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class)})); + m.areturn(Type.VOID_TYPE); + } + + private void genPostprocessingMethod(@NotNull Context context, String cacheLikeField) { + String methodName = String.format("postProcessing_%s", cacheLikeField); + String delegateSingle = context.newSingleMethod(this.delegate); + String delegateMulti = context.newMultiMethod(this.delegate); + context.genPostprocessingMethod(methodName, (m) -> { + Label cacheExists = new Label(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.dup(); + m.ifnonnull(cacheExists); + m.pop(); + m.pop(); + m.areturn(Type.VOID_TYPE); + m.visitLabel(cacheExists); + m.anew(Type.getType(SubCompiledDensityFunction.class)); + m.dup(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokedynamic("evalSingle", Type.getMethodDescriptor(Type.getType(ISingleMethod.class), new Type[]{Type.getType(context.classDesc)}), new Handle(6, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false), new Object[]{Type.getMethodType(Context.SINGLE_DESC), new Handle(5, context.className, delegateSingle, Context.SINGLE_DESC, false), Type.getMethodType(Context.SINGLE_DESC)}); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokedynamic("evalMulti", Type.getMethodDescriptor(Type.getType(IMultiMethod.class), new Type[]{Type.getType(context.classDesc)}), new Handle(6, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false), new Object[]{Type.getMethodType(Context.MULTI_DESC), new Handle(5, context.className, delegateMulti, Context.MULTI_DESC, false), Type.getMethodType(Context.MULTI_DESC)}); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.checkcast(Type.getType(DensityFunction.class)); + m.invokespecial(Type.getInternalName(SubCompiledDensityFunction.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(ISingleMethod.class), Type.getType(IMultiMethod.class), Type.getType(DensityFunction.class)}), false); + m.checkcast(Type.getType(DensityFunction.class)); + m.invokeinterface(Type.getInternalName(IFastCacheLike.class), "c2me$withDelegate", Type.getMethodDescriptor(Type.getType(DensityFunction.class), new Type[]{Type.getType(DensityFunction.class)})); + m.putfield(context.className, cacheLikeField, Type.getDescriptor(IFastCacheLike.class)); + m.areturn(Type.VOID_TYPE); + }); + } + + public IFastCacheLike getCacheLike() { + return this.cacheLike; + } + + public AstNode getDelegate() { + return this.delegate; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CacheLikeNode that = (CacheLikeNode)o; + return equals(this.cacheLike, that.cacheLike) && Objects.equals(this.delegate, that.delegate); + } else { + return false; + } + } + + private static boolean equals(IFastCacheLike a, IFastCacheLike b) { + if (a instanceof DensityFunctions.Marker wrappingA) { + if (b instanceof DensityFunctions.Marker wrappingB) { + return wrappingA.type() == wrappingB.type(); + } + } + + return a.equals(b); + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + hashCode(this.cacheLike); + result = 31 * result + this.delegate.hashCode(); + return result; + } + + private static int hashCode(IFastCacheLike o) { + if (o instanceof DensityFunctions.Marker wrapping) { + return wrapping.type().hashCode(); + } else { + return o.hashCode(); + } + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + CacheLikeNode that = (CacheLikeNode)o; + return relaxedEquals(this.cacheLike, that.cacheLike) && this.delegate.relaxedEquals(that.delegate); + } else { + return false; + } + } + + private static boolean relaxedEquals(IFastCacheLike a, IFastCacheLike b) { + if (a instanceof DensityFunctions.Marker wrappingA) { + if (b instanceof DensityFunctions.Marker wrappingB) { + return wrappingA.type() == wrappingB.type(); + } + } + + return a.getClass() == b.getClass(); + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + relaxedHashCode(this.cacheLike); + result = 31 * result + this.delegate.relaxedHashCode(); + return result; + } + + private static int relaxedHashCode(IFastCacheLike o) { + if (o instanceof DensityFunctions.Marker wrapping) { + return wrapping.type().hashCode(); + } else { + return o.getClass().hashCode(); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java new file mode 100644 index 0000000..558bc3a --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/ConstantNode.java @@ -0,0 +1,72 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Arrays; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class ConstantNode implements AstNode { + private final double value; + + public ConstantNode(double value) { + this.value = value; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.value; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + Arrays.fill(res, this.value); + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + m.dconst(this.value); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.dconst(this.value); + m.invokestatic(Type.getInternalName(Arrays.class), "fill", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class), Type.DOUBLE_TYPE}), false); + m.areturn(Type.VOID_TYPE); + } + + public double getValue() { + return this.value; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ConstantNode that = (ConstantNode)o; + return Double.compare(this.value, that.value) == 0; + } else { + return false; + } + } + + public int hashCode() { + return Double.hashCode(this.value); + } + + public boolean relaxedEquals(AstNode o) { + return this.equals(o); + } + + public int relaxedHashCode() { + return this.hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java new file mode 100644 index 0000000..050d9ff --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/DelegateNode.java @@ -0,0 +1,140 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; +import org.bxteam.divinemc.dfc.common.vif.NoisePosVanillaInterface; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DelegateNode implements AstNode { + private final DensityFunction densityFunction; + + public DelegateNode(DensityFunction densityFunction) { + this.densityFunction = Objects.requireNonNull(densityFunction); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.densityFunction.compute(new NoisePosVanillaInterface(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (res.length == 1) { + res[0] = this.evalSingle(x[0], y[0], z[0], type); + } else { + this.densityFunction.fillArray(res, new EachApplierVanillaInterface(x, y, z, type)); + } + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String newField = context.newField(DensityFunction.class, this.densityFunction); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, newField, Type.getDescriptor(DensityFunction.class)); + m.anew(Type.getType(NoisePosVanillaInterface.class)); + m.dup(); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(NoisePosVanillaInterface.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class)), false); + m.invokeinterface(Type.getInternalName(DensityFunction.class), "compute", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.getType(DensityFunction.FunctionContext.class))); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String newField = context.newField(DensityFunction.class, this.densityFunction); + Label moreThanTwoLabel = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(1); + m.ificmpgt(moreThanTwoLabel); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, newField, Type.getDescriptor(DensityFunction.class)); + m.anew(Type.getType(NoisePosVanillaInterface.class)); + m.dup(); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.iconst(0); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(NoisePosVanillaInterface.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.getType(EvalType.class)), false); + m.invokeinterface(Type.getInternalName(DensityFunction.class), "compute", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.getType(DensityFunction.FunctionContext.class))); + m.astore(Type.DOUBLE_TYPE); + m.areturn(Type.VOID_TYPE); + m.visitLabel(moreThanTwoLabel); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, newField, Type.getDescriptor(DensityFunction.class)); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.anew(Type.getType(EachApplierVanillaInterface.class)); + m.dup(); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(EachApplierVanillaInterface.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class), Type.getType(ArrayCache.class)), false); + m.invokeinterface(Type.getInternalName(DensityFunction.class), "fillArray", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(double[].class), Type.getType(DensityFunction.ContextProvider.class))); + m.areturn(Type.VOID_TYPE); + } + + public DensityFunction getDelegate() { + return this.densityFunction; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DelegateNode that = (DelegateNode)o; + return Objects.equals(this.densityFunction, that.densityFunction); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.getClass()); + result = 31 * result + Objects.hashCode(this.densityFunction); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DelegateNode that = (DelegateNode)o; + return this.densityFunction.getClass() == that.densityFunction.getClass(); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + Objects.hashCode(this.getClass()); + result = 31 * result + Objects.hashCode(this.densityFunction.getClass()); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java new file mode 100644 index 0000000..b3ce05b --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RangeChoiceNode.java @@ -0,0 +1,337 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import java.util.Objects; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class RangeChoiceNode implements AstNode { + private final AstNode input; + private final double minInclusive; + private final double maxExclusive; + private final AstNode whenInRange; + private final AstNode whenOutOfRange; + + public RangeChoiceNode(AstNode input, double minInclusive, double maxExclusive, AstNode whenInRange, AstNode whenOutOfRange) { + this.input = (AstNode)Objects.requireNonNull(input); + this.minInclusive = minInclusive; + this.maxExclusive = maxExclusive; + this.whenInRange = (AstNode)Objects.requireNonNull(whenInRange); + this.whenOutOfRange = (AstNode)Objects.requireNonNull(whenOutOfRange); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.input.evalSingle(x, y, z, type); + return v >= this.minInclusive && v < this.maxExclusive ? this.whenInRange.evalSingle(x, y, z, type) : this.whenOutOfRange.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.input.evalMulti(res, x, y, z, type); + int numInRange = 0; + + int numOutOfRange; + for(numOutOfRange = 0; numOutOfRange < x.length; ++numOutOfRange) { + double v = res[numOutOfRange]; + if (v >= this.minInclusive && v < this.maxExclusive) { + ++numInRange; + } + } + + numOutOfRange = res.length - numInRange; + if (numInRange == 0) { + this.evalChildMulti(this.whenOutOfRange, res, x, y, z, type); + } else if (numInRange == res.length) { + this.evalChildMulti(this.whenInRange, res, x, y, z, type); + } else { + int idx1 = 0; + int[] i1 = new int[numInRange]; + double[] res1 = new double[numInRange]; + int[] x1 = new int[numInRange]; + int[] y1 = new int[numInRange]; + int[] z1 = new int[numInRange]; + int idx2 = 0; + int[] i2 = new int[numOutOfRange]; + double[] res2 = new double[numOutOfRange]; + int[] x2 = new int[numOutOfRange]; + int[] y2 = new int[numOutOfRange]; + int[] z2 = new int[numOutOfRange]; + + int i; + for(i = 0; i < res.length; ++i) { + double v = res[i]; + int index; + if (v >= this.minInclusive && v < this.maxExclusive) { + index = idx1++; + i1[index] = i; + x1[index] = x[i]; + y1[index] = y[i]; + z1[index] = z[i]; + } else { + index = idx2++; + i2[index] = i; + x2[index] = x[i]; + y2[index] = y[i]; + z2[index] = z[i]; + } + } + + this.evalChildMulti(this.whenInRange, res1, x1, y1, z1, type); + this.evalChildMulti(this.whenOutOfRange, res2, x2, y2, z2, type); + + for(i = 0; i < numInRange; ++i) { + res[i1[i]] = res1[i]; + } + + for(i = 0; i < numOutOfRange; ++i) { + res[i2[i]] = res2[i]; + } + } + + } + + public static void evalMultiStatic(double[] res, int[] x, int[] y, int[] z, EvalType type, double minInclusive, double maxExclusive, BytecodeGen.EvalSingleInterface whenInRangeSingle, BytecodeGen.EvalSingleInterface whenOutOfRangeSingle, BytecodeGen.EvalMultiInterface inputMulti, BytecodeGen.EvalMultiInterface whenInRangeMulti, BytecodeGen.EvalMultiInterface whenOutOfRangeMulti) { + inputMulti.evalMulti(res, x, y, z, type); + int numInRange = 0; + + int numOutOfRange; + for(numOutOfRange = 0; numOutOfRange < x.length; ++numOutOfRange) { + double v = res[numOutOfRange]; + if (v >= minInclusive && v < maxExclusive) { + ++numInRange; + } + } + + numOutOfRange = res.length - numInRange; + if (numInRange == 0) { + evalChildMulti(whenOutOfRangeSingle, whenOutOfRangeMulti, res, x, y, z, type); + } else if (numInRange == res.length) { + evalChildMulti(whenInRangeSingle, whenInRangeMulti, res, x, y, z, type); + } else { + int idx1 = 0; + int[] i1 = new int[numInRange]; + double[] res1 = new double[numInRange]; + int[] x1 = new int[numInRange]; + int[] y1 = new int[numInRange]; + int[] z1 = new int[numInRange]; + int idx2 = 0; + int[] i2 = new int[numOutOfRange]; + double[] res2 = new double[numOutOfRange]; + int[] x2 = new int[numOutOfRange]; + int[] y2 = new int[numOutOfRange]; + int[] z2 = new int[numOutOfRange]; + + int i; + for(i = 0; i < res.length; ++i) { + double v = res[i]; + int index; + if (v >= minInclusive && v < maxExclusive) { + index = idx1++; + i1[index] = i; + x1[index] = x[i]; + y1[index] = y[i]; + z1[index] = z[i]; + } else { + index = idx2++; + i2[index] = i; + x2[index] = x[i]; + y2[index] = y[i]; + z2[index] = z[i]; + } + } + + evalChildMulti(whenInRangeSingle, whenInRangeMulti, res1, x1, y1, z1, type); + evalChildMulti(whenOutOfRangeSingle, whenOutOfRangeMulti, res2, x2, y2, z2, type); + + for(i = 0; i < numInRange; ++i) { + res[i1[i]] = res1[i]; + } + + for(i = 0; i < numOutOfRange; ++i) { + res[i2[i]] = res2[i]; + } + } + + } + + private static void evalChildMulti(BytecodeGen.EvalSingleInterface single, BytecodeGen.EvalMultiInterface multi, double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (res.length == 1) { + res[0] = single.evalSingle(x[0], y[0], z[0], type); + } else { + multi.evalMulti(res, x, y, z, type); + } + + } + + private void evalChildMulti(AstNode child, double[] res, int[] x, int[] y, int[] z, EvalType type) { + if (res.length == 1) { + res[0] = child.evalSingle(x[0], y[0], z[0], type); + } else { + child.evalMulti(res, x, y, z, type); + } + + } + + public AstNode[] getChildren() { + return new AstNode[]{this.input, this.whenInRange, this.whenOutOfRange}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode input = this.input.transform(transformer); + AstNode whenInRange = this.whenInRange.transform(transformer); + AstNode whenOutOfRange = this.whenOutOfRange.transform(transformer); + return this.input == input && this.whenInRange == whenInRange && this.whenOutOfRange == whenOutOfRange ? transformer.transform(this) : transformer.transform(new RangeChoiceNode(input, this.minInclusive, this.maxExclusive, whenInRange, whenOutOfRange)); + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String inputMethod = context.newSingleMethod(this.input); + String whenInRangeMethod = context.newSingleMethod(this.whenInRange); + String whenOutOfRangeMethod = context.newSingleMethod(this.whenOutOfRange); + int inputValue = localVarConsumer.createLocalVariable("inputValue", Type.DOUBLE_TYPE.getDescriptor()); + context.callDelegateSingle(m, inputMethod); + m.store(inputValue, Type.DOUBLE_TYPE); + Label whenOutOfRangeLabel = new Label(); + Label end = new Label(); + m.load(inputValue, Type.DOUBLE_TYPE); + m.dconst(this.minInclusive); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(whenOutOfRangeLabel); + m.load(inputValue, Type.DOUBLE_TYPE); + m.dconst(this.maxExclusive); + m.cmpg(Type.DOUBLE_TYPE); + m.ifge(whenOutOfRangeLabel); + if (whenInRangeMethod.equals(inputMethod)) { + m.load(inputValue, Type.DOUBLE_TYPE); + } else { + context.callDelegateSingle(m, whenInRangeMethod); + } + + m.goTo(end); + m.visitLabel(whenOutOfRangeLabel); + if (whenOutOfRangeMethod.equals(inputMethod)) { + m.load(inputValue, Type.DOUBLE_TYPE); + } else { + context.callDelegateSingle(m, whenOutOfRangeMethod); + } + + m.visitLabel(end); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String inputSingle = context.newSingleMethod(this.input); + String whenInRangeSingle = context.newSingleMethod(this.whenInRange); + String whenOutOfRangeSingle = context.newSingleMethod(this.whenOutOfRange); + String inputMulti = context.newMultiMethod(this.input); + context.callDelegateMulti(m, inputMulti); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + Label whenOutOfRangeLabel = new Label(); + Label end = new Label(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dconst(this.minInclusive); + m.cmpl(Type.DOUBLE_TYPE); + m.iflt(whenOutOfRangeLabel); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.dconst(this.maxExclusive); + m.cmpg(Type.DOUBLE_TYPE); + m.ifge(whenOutOfRangeLabel); + if (whenInRangeSingle.equals(inputSingle)) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, whenInRangeSingle, Context.SINGLE_DESC, false); + } + + m.goTo(end); + m.visitLabel(whenOutOfRangeLabel); + if (whenOutOfRangeSingle.equals(inputSingle)) { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, whenOutOfRangeSingle, Context.SINGLE_DESC, false); + } + + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RangeChoiceNode that = (RangeChoiceNode)o; + return Double.compare(this.minInclusive, that.minInclusive) == 0 && Double.compare(this.maxExclusive, that.maxExclusive) == 0 && Objects.equals(this.input, that.input) && Objects.equals(this.whenInRange, that.whenInRange) && Objects.equals(this.whenOutOfRange, that.whenOutOfRange); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.hashCode(); + result = 31 * result + Double.hashCode(this.minInclusive); + result = 31 * result + Double.hashCode(this.maxExclusive); + result = 31 * result + this.whenInRange.hashCode(); + result = 31 * result + this.whenOutOfRange.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RangeChoiceNode that = (RangeChoiceNode)o; + return Double.compare(this.minInclusive, that.minInclusive) == 0 && Double.compare(this.maxExclusive, that.maxExclusive) == 0 && this.input.relaxedEquals(that.input) && this.whenInRange.relaxedEquals(that.whenInRange) && this.whenOutOfRange.relaxedEquals(that.whenOutOfRange); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.relaxedHashCode(); + result = 31 * result + Double.hashCode(this.minInclusive); + result = 31 * result + Double.hashCode(this.maxExclusive); + result = 31 * result + this.whenInRange.relaxedHashCode(); + result = 31 * result + this.whenOutOfRange.relaxedHashCode(); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java new file mode 100644 index 0000000..4c6001f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/RootNode.java @@ -0,0 +1,82 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class RootNode implements AstNode { + private final AstNode next; + + public RootNode(AstNode next) { + this.next = (AstNode)Objects.requireNonNull(next); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.next.evalSingle(x, y, z, type); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.next.evalMulti(res, x, y, z, type); + } + + public AstNode[] getChildren() { + return new AstNode[]{this.next}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode next = this.next.transform(transformer); + return next == this.next ? transformer.transform(this) : transformer.transform(new RootNode(next)); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String nextMethod = context.newSingleMethod(this.next); + context.callDelegateSingle(m, nextMethod); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String nextMethod = context.newMultiMethod(this.next); + context.callDelegateMulti(m, nextMethod); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RootNode that = (RootNode)o; + return Objects.equals(this.next, that.next); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.next.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + RootNode that = (RootNode)o; + return this.next.relaxedEquals(that.next); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.next.relaxedHashCode(); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java new file mode 100644 index 0000000..0eb4c48 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/misc/YClampedGradientNode.java @@ -0,0 +1,100 @@ +package org.bxteam.divinemc.dfc.common.ast.misc; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import net.minecraft.util.Mth; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class YClampedGradientNode implements AstNode { + private final double fromY; + private final double toY; + private final double fromValue; + private final double toValue; + + public YClampedGradientNode(double fromY, double toY, double fromValue, double toValue) { + this.fromY = fromY; + this.toY = toY; + this.fromValue = fromValue; + this.toValue = toValue; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Mth.clampedMap((double)y, this.fromY, this.toY, this.fromValue, this.toValue); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = Mth.clampedMap((double)y[i], this.fromY, this.toY, this.fromValue, this.toValue); + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.fromY); + m.dconst(this.toY); + m.dconst(this.fromValue); + m.dconst(this.toValue); + m.invokestatic(Type.getInternalName(Mth.class), "clampedMap", "(DDDDD)D", false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.fromY); + m.dconst(this.toY); + m.dconst(this.fromValue); + m.dconst(this.toValue); + m.invokestatic(Type.getInternalName(Mth.class), "clampedMap", "(DDDDD)D", false); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + YClampedGradientNode that = (YClampedGradientNode)o; + return Double.compare(this.fromY, that.fromY) == 0 && Double.compare(this.toY, that.toY) == 0 && Double.compare(this.fromValue, that.fromValue) == 0 && Double.compare(this.toValue, that.toValue) == 0; + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + Double.hashCode(this.fromY); + result = 31 * result + Double.hashCode(this.toY); + result = 31 * result + Double.hashCode(this.fromValue); + result = 31 * result + Double.hashCode(this.toValue); + return result; + } + + public boolean relaxedEquals(AstNode o) { + return this.equals(o); + } + + public int relaxedHashCode() { + return this.hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java new file mode 100644 index 0000000..76b1899 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTNoiseNode.java @@ -0,0 +1,131 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTNoiseNode implements AstNode { + private final DensityFunction.NoiseHolder noise; + private final double xzScale; + private final double yScale; + + public DFTNoiseNode(DensityFunction.NoiseHolder noise, double xzScale, double yScale) { + this.noise = (DensityFunction.NoiseHolder)Objects.requireNonNull(noise); + this.xzScale = xzScale; + this.yScale = yScale; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.noise.getValue((double)x * this.xzScale, (double)y * this.yScale, (double)z * this.xzScale); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.noise.getValue((double)x[i] * this.xzScale, (double)y[i] * this.yScale, (double)z[i] * this.xzScale); + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTNoiseNode that = (DFTNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0 && Objects.equals(this.noise, that.noise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.noise.hashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTNoiseNode that = (DFTNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0; + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java new file mode 100644 index 0000000..499c09f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftANode.java @@ -0,0 +1,115 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTShiftANode implements AstNode { + private final DensityFunction.NoiseHolder offsetNoise; + + public DFTShiftANode(DensityFunction.NoiseHolder offsetNoise) { + this.offsetNoise = (DensityFunction.NoiseHolder)Objects.requireNonNull(offsetNoise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.offsetNoise.getValue((double)x * 0.25, 0.0, (double)z * 0.25) * 4.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.offsetNoise.getValue((double)x[i] * 0.25, 0.0, (double)z[i] * 0.25) * 4.0; + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTShiftANode that = (DFTShiftANode)o; + return Objects.equals(this.offsetNoise, that.offsetNoise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + Object o = this.getClass(); + result = 31 * result + o.hashCode(); + result = 31 * result + this.offsetNoise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else { + return o != null && this.getClass() == o.getClass(); + } + } + + public int relaxedHashCode() { + return this.getClass().hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java new file mode 100644 index 0000000..1611540 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftBNode.java @@ -0,0 +1,115 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTShiftBNode implements AstNode { + private final DensityFunction.NoiseHolder offsetNoise; + + public DFTShiftBNode(DensityFunction.NoiseHolder offsetNoise) { + this.offsetNoise = (DensityFunction.NoiseHolder)Objects.requireNonNull(offsetNoise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.offsetNoise.getValue((double)z * 0.25, (double)x * 0.25, 0.0) * 4.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.offsetNoise.getValue((double)z[i] * 0.25, (double)x[i] * 0.25, 0.0) * 4.0; + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.dconst(0.0); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTShiftBNode that = (DFTShiftBNode)o; + return Objects.equals(this.offsetNoise, that.offsetNoise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + Object o = this.getClass(); + result = 31 * result + o.hashCode(); + result = 31 * result + this.offsetNoise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else { + return o != null && this.getClass() == o.getClass(); + } + } + + public int relaxedHashCode() { + return this.getClass().hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java new file mode 100644 index 0000000..cff0089 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTShiftNode.java @@ -0,0 +1,123 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTShiftNode implements AstNode { + private final DensityFunction.NoiseHolder offsetNoise; + + public DFTShiftNode(DensityFunction.NoiseHolder offsetNoise) { + this.offsetNoise = (DensityFunction.NoiseHolder)Objects.requireNonNull(offsetNoise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return this.offsetNoise.getValue((double)x * 0.25, (double)y * 0.25, (double)z * 0.25) * 4.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.offsetNoise.getValue((double)x[i] * 0.25, (double)y[i] * 0.25, (double)z[i] * 0.25) * 4.0; + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.offsetNoise); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(0.25); + m.mul(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.dconst(4.0); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTShiftNode that = (DFTShiftNode)o; + return Objects.equals(this.offsetNoise, that.offsetNoise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + Object o = this.getClass(); + result = 31 * result + o.hashCode(); + result = 31 * result + this.offsetNoise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else { + return o != null && this.getClass() == o.getClass(); + } + } + + public int relaxedHashCode() { + return this.getClass().hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java new file mode 100644 index 0000000..9f9c368 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/DFTWeirdScaledSamplerNode.java @@ -0,0 +1,169 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.IDensityFunctionsCaveScaler; +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class DFTWeirdScaledSamplerNode implements AstNode { + private final AstNode input; + private final DensityFunction.NoiseHolder noise; + private final DensityFunctions.WeirdScaledSampler.RarityValueMapper mapper; + + public DFTWeirdScaledSamplerNode(AstNode input, DensityFunction.NoiseHolder noise, DensityFunctions.WeirdScaledSampler.RarityValueMapper mapper) { + this.input = Objects.requireNonNull(input); + this.noise = Objects.requireNonNull(noise); + this.mapper = Objects.requireNonNull(mapper); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.input.evalSingle(x, y, z, type); + double d = (this.mapper.mapper).get(v); + return d * Math.abs(this.noise.getValue((double)x / d, (double)y / d, (double)z / d)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.input.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + double d = (this.mapper.mapper).get(res[i]); + res[i] = d * Math.abs(this.noise.getValue((double)x[i] / d, (double)y[i] / d, (double)z[i] / d)); + } + + } + + public AstNode[] getChildren() { + return new AstNode[]{this.input}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode input = this.input.transform(transformer); + return input == this.input ? transformer.transform(this) : transformer.transform(new DFTWeirdScaledSamplerNode(input, this.noise, this.mapper)); + } + + public void doBytecodeGenSingle(BytecodeGen.@NotNull Context context, InstructionAdapter m, BytecodeGen.Context.@NotNull LocalVarConsumer localVarConsumer) { + String inputMethod = context.newSingleMethod(this.input); + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + int scale = localVarConsumer.createLocalVariable("scale", Type.DOUBLE_TYPE.getDescriptor()); + context.callDelegateSingle(m, inputMethod); + switch (this.mapper) { + case TYPE1 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleTunnels", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + case TYPE2 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleCaves", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + default -> throw new UnsupportedOperationException(String.format("Unknown mapper %s", this.mapper)); + } + + m.store(scale, Type.DOUBLE_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.invokestatic(Type.getInternalName(Math.class), "abs", "(D)D", false); + m.load(scale, Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String inputMethod = context.newMultiMethod(this.input); + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + context.callDelegateMulti(m, inputMethod); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + int scale = localVarConsumer.createLocalVariable("scale", Type.DOUBLE_TYPE.getDescriptor()); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + switch (this.mapper) { + case TYPE1 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleTunnels", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + case TYPE2 -> m.invokestatic(Type.getInternalName(IDensityFunctionsCaveScaler.class), "invokeScaleCaves", Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE), true); + default -> throw new UnsupportedOperationException(String.format("Unknown mapper %s", this.mapper)); + } + + m.store(scale, Type.DOUBLE_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.load(scale, Type.DOUBLE_TYPE); + m.div(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.invokestatic(Type.getInternalName(Math.class), "abs", "(D)D", false); + m.load(scale, Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTWeirdScaledSamplerNode that = (DFTWeirdScaledSamplerNode)o; + return Objects.equals(this.input, that.input) && Objects.equals(this.noise, that.noise) && this.mapper == that.mapper; + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.hashCode(); + result = 31 * result + this.noise.hashCode(); + result = 31 * result + this.mapper.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + DFTWeirdScaledSamplerNode that = (DFTWeirdScaledSamplerNode)o; + return this.input.relaxedEquals(that.input) && this.mapper == that.mapper; + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.input.relaxedHashCode(); + result = 31 * result + this.mapper.hashCode(); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java new file mode 100644 index 0000000..75c82a6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/noise/ShiftedNoiseNode.java @@ -0,0 +1,215 @@ +package org.bxteam.divinemc.dfc.common.ast.noise; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen.Context; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class ShiftedNoiseNode implements AstNode { + private final AstNode shiftX; + private final AstNode shiftY; + private final AstNode shiftZ; + private final double xzScale; + private final double yScale; + private final DensityFunction.NoiseHolder noise; + + public ShiftedNoiseNode(AstNode shiftX, AstNode shiftY, AstNode shiftZ, double xzScale, double yScale, DensityFunction.NoiseHolder noise) { + this.shiftX = (AstNode)Objects.requireNonNull(shiftX); + this.shiftY = (AstNode)Objects.requireNonNull(shiftY); + this.shiftZ = (AstNode)Objects.requireNonNull(shiftZ); + this.xzScale = xzScale; + this.yScale = yScale; + this.noise = (DensityFunction.NoiseHolder)Objects.requireNonNull(noise); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double d = (double)x * this.xzScale + this.shiftX.evalSingle(x, y, z, type); + double e = (double)y * this.yScale + this.shiftY.evalSingle(x, y, z, type); + double f = (double)z * this.xzScale + this.shiftZ.evalSingle(x, y, z, type); + return this.noise.getValue(d, e, f); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + double[] res1 = new double[res.length]; + double[] res2 = new double[res.length]; + this.shiftX.evalMulti(res, x, y, z, type); + this.shiftY.evalMulti(res1, x, y, z, type); + this.shiftZ.evalMulti(res2, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = this.noise.getValue((double)x[i] * this.xzScale + res[i], (double)y[i] * this.yScale + res1[i], (double)z[i] * this.xzScale + res2[i]); + } + + } + + public AstNode[] getChildren() { + return new AstNode[]{this.shiftX, this.shiftY, this.shiftZ}; + } + + public AstNode transform(AstTransformer transformer) { + AstNode shiftX = this.shiftX.transform(transformer); + AstNode shiftY = this.shiftY.transform(transformer); + AstNode shiftZ = this.shiftZ.transform(transformer); + return shiftX == this.shiftX && shiftY == this.shiftY && shiftZ == this.shiftZ ? transformer.transform(this) : transformer.transform(new ShiftedNoiseNode(shiftX, shiftY, shiftZ, this.xzScale, this.yScale, this.noise)); + } + + public void doBytecodeGenSingle(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + String shiftXMethod = context.newSingleMethod(this.shiftX); + String shiftYMethod = context.newSingleMethod(this.shiftY); + String shiftZMethod = context.newSingleMethod(this.shiftZ); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(1, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + context.callDelegateSingle(m, shiftXMethod); + m.add(Type.DOUBLE_TYPE); + m.load(2, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + context.callDelegateSingle(m, shiftYMethod); + m.add(Type.DOUBLE_TYPE); + m.load(3, Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + context.callDelegateSingle(m, shiftZMethod); + m.add(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(Context context, InstructionAdapter m, Context.LocalVarConsumer localVarConsumer) { + String noiseField = context.newField(DensityFunction.NoiseHolder.class, this.noise); + String shiftXMethod = context.newMultiMethod(this.shiftX); + String shiftYMethod = context.newMultiMethod(this.shiftY); + String shiftZMethod = context.newMultiMethod(this.shiftZ); + int res1 = localVarConsumer.createLocalVariable("res1", Type.getDescriptor(double[].class)); + int res2 = localVarConsumer.createLocalVariable("res2", Type.getDescriptor(double[].class)); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(0); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "getDoubleArray", Type.getMethodDescriptor(Type.getType(double[].class), new Type[]{Type.INT_TYPE, Type.BOOLEAN_TYPE}), false); + m.store(res1, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.iconst(0); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "getDoubleArray", Type.getMethodDescriptor(Type.getType(double[].class), new Type[]{Type.INT_TYPE, Type.BOOLEAN_TYPE}), false); + m.store(res2, InstructionAdapter.OBJECT_TYPE); + context.callDelegateMulti(m, shiftXMethod); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, shiftYMethod, Context.MULTI_DESC, false); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(res2, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, shiftZMethod, Context.MULTI_DESC, false); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, noiseField, Type.getDescriptor(DensityFunction.NoiseHolder.class)); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.yScale); + m.mul(Type.DOUBLE_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.cast(Type.INT_TYPE, Type.DOUBLE_TYPE); + m.dconst(this.xzScale); + m.mul(Type.DOUBLE_TYPE); + m.load(res2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.DOUBLE_TYPE); + m.add(Type.DOUBLE_TYPE); + m.invokevirtual(Type.getInternalName(DensityFunction.NoiseHolder.class), "getValue", "(DDD)D", false); + m.astore(Type.DOUBLE_TYPE); + }); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(res1, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "recycle", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class)}), false); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.load(res2, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(Type.getInternalName(ArrayCache.class), "recycle", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class)}), false); + m.areturn(Type.VOID_TYPE); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ShiftedNoiseNode that = (ShiftedNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0 && Objects.equals(this.shiftX, that.shiftX) && Objects.equals(this.shiftY, that.shiftY) && Objects.equals(this.shiftZ, that.shiftZ) && Objects.equals(this.noise, that.noise); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.shiftX.hashCode(); + result = 31 * result + this.shiftY.hashCode(); + result = 31 * result + this.shiftZ.hashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + result = 31 * result + this.noise.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + ShiftedNoiseNode that = (ShiftedNoiseNode)o; + return Double.compare(this.xzScale, that.xzScale) == 0 && Double.compare(this.yScale, that.yScale) == 0 && this.shiftX.relaxedEquals(that.shiftX) && this.shiftY.relaxedEquals(that.shiftY) && this.shiftZ.relaxedEquals(that.shiftZ); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.shiftX.relaxedHashCode(); + result = 31 * result + this.shiftY.relaxedHashCode(); + result = 31 * result + this.shiftZ.relaxedHashCode(); + result = 31 * result + Double.hashCode(this.xzScale); + result = 31 * result + Double.hashCode(this.yScale); + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java new file mode 100644 index 0000000..176b6a1 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineAstNode.java @@ -0,0 +1,453 @@ +package org.bxteam.divinemc.dfc.common.ast.spline; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ast.McToAst; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.bxteam.divinemc.dfc.common.vif.NoisePosVanillaInterface; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import net.minecraft.util.CubicSpline; +import net.minecraft.util.Mth; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.bxteam.divinemc.util.Assertions; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.AnalyzerAdapter; +import org.objectweb.asm.commons.InstructionAdapter; + +public class SplineAstNode implements AstNode { + public static final String SPLINE_METHOD_DESC; + private final CubicSpline spline; + + public SplineAstNode(CubicSpline spline) { + this.spline = spline; + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return (double)this.spline.apply(new DensityFunctions.Spline.Point(new NoisePosVanillaInterface(x, y, z, type))); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + for(int i = 0; i < res.length; ++i) { + res[i] = this.evalSingle(x[i], y[i], z[i], type); + } + + } + + public AstNode[] getChildren() { + return new AstNode[0]; + } + + public AstNode transform(AstTransformer transformer) { + return transformer.transform(this); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + ValuesMethodDef splineMethod = doBytecodeGenSpline(context, this.spline); + callSplineSingle(context, m, splineMethod); + m.cast(Type.FLOAT_TYPE, Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + private static ValuesMethodDef doBytecodeGenSpline(BytecodeGen.Context context, CubicSpline spline) { + String name = context.getCachedSplineMethod(spline); + if (name != null) { + return new ValuesMethodDef(false, name, 0.0F); + } else if (spline instanceof CubicSpline.Constant) { + CubicSpline.Constant spline1 = (CubicSpline.Constant)spline; + return new ValuesMethodDef(true, (String)null, spline1.value()); + } else { + name = context.nextMethodName("Spline"); + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 18, name, SPLINE_METHOD_DESC, context.classWriter.visitMethod(18, name, SPLINE_METHOD_DESC, (String)null, (String[])null))); + List>> extraLocals = new ArrayList(); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + BytecodeGen.Context.LocalVarConsumer localVarConsumer = (localName, localDesc) -> { + int ordinal = extraLocals.size() + 5; + extraLocals.add(IntObjectPair.of(ordinal, Pair.of(localName, localDesc))); + return ordinal; + }; + if (spline instanceof CubicSpline.Multipoint) { + CubicSpline.Multipoint impl = (CubicSpline.Multipoint)spline; + ValuesMethodDef[] valuesMethods = (ValuesMethodDef[])impl.values().stream().map((spline1x) -> { + return doBytecodeGenSpline(context, spline1x); + }).toArray((x$0) -> { + return new ValuesMethodDef[x$0]; + }); + String locations = context.newField(float[].class, impl.locations()); + String derivatives = context.newField(float[].class, impl.derivatives()); + int point = localVarConsumer.createLocalVariable("point", Type.FLOAT_TYPE.getDescriptor()); + int rangeForLocation = localVarConsumer.createLocalVariable("rangeForLocation", Type.INT_TYPE.getDescriptor()); + int lastConst = impl.locations().length - 1; + String locationFunction = context.newSingleMethod(McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)impl.coordinate()).function().value())); + context.callDelegateSingle(m, locationFunction); + m.cast(Type.DOUBLE_TYPE, Type.FLOAT_TYPE); + m.store(point, Type.FLOAT_TYPE); + if (valuesMethods.length == 1) { + m.load(point, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + callSplineSingle(context, m, valuesMethods[0]); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.iconst(0); + m.invokestatic(Type.getInternalName(SplineSupport.class), "sampleOutsideRange", Type.getMethodDescriptor(Type.FLOAT_TYPE, new Type[]{Type.FLOAT_TYPE, Type.getType(float[].class), Type.FLOAT_TYPE, Type.getType(float[].class), Type.INT_TYPE}), false); + m.areturn(Type.FLOAT_TYPE); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + m.load(point, Type.FLOAT_TYPE); + m.invokestatic(Type.getInternalName(SplineSupport.class), "findRangeForLocation", Type.getMethodDescriptor(Type.INT_TYPE, new Type[]{Type.getType(float[].class), Type.FLOAT_TYPE}), false); + m.store(rangeForLocation, Type.INT_TYPE); + Label label1 = new Label(); + Label label2 = new Label(); + m.load(rangeForLocation, Type.INT_TYPE); + m.ifge(label1); + m.load(point, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + callSplineSingle(context, m, valuesMethods[0]); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.iconst(0); + m.invokestatic(Type.getInternalName(SplineSupport.class), "sampleOutsideRange", Type.getMethodDescriptor(Type.FLOAT_TYPE, new Type[]{Type.FLOAT_TYPE, Type.getType(float[].class), Type.FLOAT_TYPE, Type.getType(float[].class), Type.INT_TYPE}), false); + m.areturn(Type.FLOAT_TYPE); + m.visitLabel(label1); + m.load(rangeForLocation, Type.INT_TYPE); + m.iconst(lastConst); + m.ificmpne(label2); + m.load(point, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + callSplineSingle(context, m, valuesMethods[lastConst]); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.iconst(lastConst); + m.invokestatic(Type.getInternalName(SplineSupport.class), "sampleOutsideRange", Type.getMethodDescriptor(Type.FLOAT_TYPE, new Type[]{Type.FLOAT_TYPE, Type.getType(float[].class), Type.FLOAT_TYPE, Type.getType(float[].class), Type.INT_TYPE}), false); + m.areturn(Type.FLOAT_TYPE); + m.visitLabel(label2); + int loc0 = localVarConsumer.createLocalVariable("loc0", Type.FLOAT_TYPE.getDescriptor()); + int loc1 = localVarConsumer.createLocalVariable("loc1", Type.FLOAT_TYPE.getDescriptor()); + int locDist = localVarConsumer.createLocalVariable("locDist", Type.FLOAT_TYPE.getDescriptor()); + int k = localVarConsumer.createLocalVariable("k", Type.FLOAT_TYPE.getDescriptor()); + int n = localVarConsumer.createLocalVariable("n", Type.FLOAT_TYPE.getDescriptor()); + int o = localVarConsumer.createLocalVariable("o", Type.FLOAT_TYPE.getDescriptor()); + int onDist = localVarConsumer.createLocalVariable("onDist", Type.FLOAT_TYPE.getDescriptor()); + int p = localVarConsumer.createLocalVariable("p", Type.FLOAT_TYPE.getDescriptor()); + int q = localVarConsumer.createLocalVariable("q", Type.FLOAT_TYPE.getDescriptor()); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.store(loc0, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, locations, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.iconst(1); + m.add(Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.store(loc1, Type.FLOAT_TYPE); + m.load(loc1, Type.FLOAT_TYPE); + m.load(loc0, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.store(locDist, Type.FLOAT_TYPE); + m.load(point, Type.FLOAT_TYPE); + m.load(loc0, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.load(locDist, Type.FLOAT_TYPE); + m.div(Type.FLOAT_TYPE); + m.store(k, Type.FLOAT_TYPE); + Label[] jumpLabels = new Label[valuesMethods.length - 1]; + boolean[] jumpGenerated = new boolean[valuesMethods.length - 1]; + + for(int i = 0; i < valuesMethods.length - 1; ++i) { + jumpLabels[i] = new Label(); + } + + Label defaultLabel = new Label(); + Label label3 = new Label(); + m.load(rangeForLocation, Type.INT_TYPE); + m.tableswitch(0, valuesMethods.length - 2, defaultLabel, jumpLabels); + + for(int i = 0; i < valuesMethods.length - 1; ++i) { + if (!jumpGenerated[i]) { + m.visitLabel(jumpLabels[i]); + jumpGenerated[i] = true; + + for(int j = i + 1; j < valuesMethods.length - 1; ++j) { + if (valuesMethods[i].equals(valuesMethods[j]) && valuesMethods[i + 1].equals(valuesMethods[j + 1])) { + m.visitLabel(jumpLabels[j]); + jumpGenerated[j] = true; + } + } + + callSplineSingle(context, m, valuesMethods[i]); + if (valuesMethods[i].equals(valuesMethods[i + 1])) { + m.dup(); + m.store(n, Type.FLOAT_TYPE); + m.store(o, Type.FLOAT_TYPE); + } else { + m.store(n, Type.FLOAT_TYPE); + callSplineSingle(context, m, valuesMethods[i + 1]); + m.store(o, Type.FLOAT_TYPE); + } + + m.goTo(label3); + } + } + + m.visitLabel(defaultLabel); + m.iconst(0); + m.aconst("boom"); + m.invokestatic(Type.getInternalName(Assertions.class), "assertTrue", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.BOOLEAN_TYPE, Type.getType(String.class)}), false); + m.fconst(Float.NaN); + m.areturn(Type.FLOAT_TYPE); + m.visitLabel(label3); + m.load(o, Type.FLOAT_TYPE); + m.load(n, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.store(onDist, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.load(locDist, Type.FLOAT_TYPE); + m.mul(Type.FLOAT_TYPE); + m.load(onDist, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.store(p, Type.FLOAT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, derivatives, Type.getDescriptor(float[].class)); + m.load(rangeForLocation, Type.INT_TYPE); + m.iconst(1); + m.add(Type.INT_TYPE); + m.aload(Type.FLOAT_TYPE); + m.neg(Type.FLOAT_TYPE); + m.load(locDist, Type.FLOAT_TYPE); + m.mul(Type.FLOAT_TYPE); + m.load(onDist, Type.FLOAT_TYPE); + m.add(Type.FLOAT_TYPE); + m.store(q, Type.FLOAT_TYPE); + m.load(k, Type.FLOAT_TYPE); + m.load(n, Type.FLOAT_TYPE); + m.load(o, Type.FLOAT_TYPE); + m.invokestatic(Type.getInternalName(Mth.class), "lerp", "(FFF)F", false); + m.load(k, Type.FLOAT_TYPE); + m.fconst(1.0F); + m.load(k, Type.FLOAT_TYPE); + m.sub(Type.FLOAT_TYPE); + m.mul(Type.FLOAT_TYPE); + m.load(k, Type.FLOAT_TYPE); + m.load(p, Type.FLOAT_TYPE); + m.load(q, Type.FLOAT_TYPE); + m.invokestatic(Type.getInternalName(Mth.class), "lerp", "(FFF)F", false); + m.mul(Type.FLOAT_TYPE); + m.add(Type.FLOAT_TYPE); + m.areturn(Type.FLOAT_TYPE); + } + } else { + if (!(spline instanceof CubicSpline.Constant)) { + throw new UnsupportedOperationException(String.format("Unsupported spline implementation: %s", spline.getClass().getName())); + } + + CubicSpline.Constant floatFunction = (CubicSpline.Constant)spline; + m.fconst(floatFunction.value()); + m.areturn(Type.FLOAT_TYPE); + } + + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("x", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 1); + m.visitLocalVariable("y", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 2); + m.visitLocalVariable("z", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 3); + m.visitLocalVariable("evalType", Type.getType(EvalType.class).getDescriptor(), (String)null, start, end, 4); + Iterator var35 = extraLocals.iterator(); + + while(var35.hasNext()) { + IntObjectPair> local = (IntObjectPair)var35.next(); + m.visitLocalVariable((String)((Pair)local.right()).left(), (String)((Pair)local.right()).right(), (String)null, start, end, local.leftInt()); + } + + m.visitMaxs(0, 0); + context.cacheSplineMethod(spline, name); + return new ValuesMethodDef(false, name, 0.0F); + } + } + + private static void callSplineSingle(BytecodeGen.Context context, InstructionAdapter m, ValuesMethodDef target) { + if (target.isConst()) { + m.fconst(target.constValue()); + } else { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, target.generatedMethod(), SPLINE_METHOD_DESC, false); + } + + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + context.delegateToSingle(m, localVarConsumer, this); + m.areturn(Type.VOID_TYPE); + } + + private static boolean deepEquals(CubicSpline a, CubicSpline b) { + if (a instanceof CubicSpline.Constant a1) { + if (b instanceof CubicSpline.Constant b1) { + return a1.value() == b1.value(); + } + } + + if (a instanceof CubicSpline.Multipoint a1) { + if (b instanceof CubicSpline.Multipoint b1) { + boolean equals1 = Arrays.equals(a1.derivatives(), b1.derivatives()) && Arrays.equals(a1.locations(), b1.locations()) && a1.values().size() == b1.values().size() && McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).equals(McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)b1.coordinate()).function().value())); + if (!equals1) { + return false; + } + + int size = a1.values().size(); + + for(int i = 0; i < size; ++i) { + if (!deepEquals((CubicSpline)a1.values().get(i), (CubicSpline)b1.values().get(i))) { + return false; + } + } + + return true; + } + } + + return false; + } + + private static boolean deepRelaxedEquals(CubicSpline a, CubicSpline b) { + if (a instanceof CubicSpline.Constant a1) { + if (b instanceof CubicSpline.Constant b1) { + return a1.value() == b1.value(); + } + } + + if (a instanceof CubicSpline.Multipoint a1) { + if (b instanceof CubicSpline.Multipoint b1) { + boolean equals1 = a1.values().size() == b1.values().size() && McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).relaxedEquals(McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)b1.coordinate()).function().value())); + if (!equals1) { + return false; + } + + int size = a1.values().size(); + + for(int i = 0; i < size; ++i) { + if (!deepRelaxedEquals((CubicSpline)a1.values().get(i), (CubicSpline)b1.values().get(i))) { + return false; + } + } + + return true; + } + } + + return false; + } + + private static int deepHashcode(CubicSpline a) { + if (a instanceof CubicSpline.Constant a1) { + return Float.hashCode(a1.value()); + } else if (!(a instanceof CubicSpline.Multipoint a1)) { + return a.hashCode(); + } else { + int result = 1; + result = 31 * result + Arrays.hashCode(a1.derivatives()); + result = 31 * result + Arrays.hashCode(a1.locations()); + + CubicSpline spline; + for(Iterator var4 = a1.values().iterator(); var4.hasNext(); result = 31 * result + deepHashcode(spline)) { + spline = (CubicSpline)var4.next(); + } + + result = 31 * result + McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).hashCode(); + return result; + } + } + + private static int deepRelaxedHashcode(CubicSpline a) { + if (a instanceof CubicSpline.Constant a1) { + return Float.hashCode(a1.value()); + } else if (!(a instanceof CubicSpline.Multipoint a1)) { + return a.hashCode(); + } else { + int result = 1; + + CubicSpline spline; + for(Iterator var4 = a1.values().iterator(); var4.hasNext(); result = 31 * result + deepRelaxedHashcode(spline)) { + spline = (CubicSpline)var4.next(); + } + + result = 31 * result + McToAst.toAst((DensityFunction)((DensityFunctions.Spline.Coordinate)a1.coordinate()).function().value()).relaxedHashCode(); + return result; + } + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SplineAstNode that = (SplineAstNode)o; + return deepEquals(this.spline, that.spline); + } else { + return false; + } + } + + public int hashCode() { + return deepHashcode(this.spline); + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + SplineAstNode that = (SplineAstNode)o; + return deepRelaxedEquals(this.spline, that.spline); + } else { + return false; + } + } + + public int relaxedHashCode() { + return deepRelaxedHashcode(this.spline); + } + + static { + SPLINE_METHOD_DESC = Type.getMethodDescriptor(Type.getType(Float.TYPE), new Type[]{Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(EvalType.class)}); + } + + private static record ValuesMethodDef(boolean isConst, String generatedMethod, float constValue) { + private ValuesMethodDef(boolean isConst, String generatedMethod, float constValue) { + this.isConst = isConst; + this.generatedMethod = generatedMethod; + this.constValue = constValue; + } + + public boolean isConst() { + return this.isConst; + } + + public String generatedMethod() { + return this.generatedMethod; + } + + public float constValue() { + return this.constValue; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java new file mode 100644 index 0000000..ee23110 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/spline/SplineSupport.java @@ -0,0 +1,29 @@ +package org.bxteam.divinemc.dfc.common.ast.spline; + +public class SplineSupport { + public SplineSupport() { + } + + public static int findRangeForLocation(float[] locations, float x) { + int min = 0; + int i = locations.length; + + while(i > 0) { + int j = i / 2; + int k = min + j; + if (x < locations[k]) { + i = j; + } else { + min = k + 1; + i -= j + 1; + } + } + + return min - 1; + } + + public static float sampleOutsideRange(float point, float[] locations, float value, float[] derivatives, int i) { + float f = derivatives[i]; + return f == 0.0F ? value : value + f * (point - locations[i]); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java new file mode 100644 index 0000000..e3a91a5 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbsNode.java @@ -0,0 +1,49 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class AbsNode extends AbstractUnaryNode { + public AbsNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new AbsNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + return Math.abs(this.operand.evalSingle(x, y, z, type)); + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = Math.abs(res[i]); + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.invokestatic(Type.getInternalName(Math.class), "abs", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE}), false); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.invokestatic(Type.getInternalName(Math.class), "abs", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE}), false); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java new file mode 100644 index 0000000..a48ca64 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/AbstractUnaryNode.java @@ -0,0 +1,72 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.AstTransformer; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import java.util.Objects; +import org.objectweb.asm.commons.InstructionAdapter; + +public abstract class AbstractUnaryNode implements AstNode { + protected final AstNode operand; + + public AbstractUnaryNode(AstNode operand) { + this.operand = (AstNode)Objects.requireNonNull(operand); + } + + public AstNode[] getChildren() { + return new AstNode[]{this.operand}; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractUnaryNode that = (AbstractUnaryNode)o; + return Objects.equals(this.operand, that.operand); + } else { + return false; + } + } + + public int hashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.operand.hashCode(); + return result; + } + + public boolean relaxedEquals(AstNode o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + AbstractUnaryNode that = (AbstractUnaryNode)o; + return this.operand.relaxedEquals(that.operand); + } else { + return false; + } + } + + public int relaxedHashCode() { + int result = 1; + result = 31 * result + this.getClass().hashCode(); + result = 31 * result + this.operand.relaxedHashCode(); + return result; + } + + protected abstract AstNode newInstance(AstNode var1); + + public AstNode transform(AstTransformer transformer) { + AstNode operand = this.operand.transform(transformer); + return this.operand == operand ? transformer.transform(this) : transformer.transform(this.newInstance(operand)); + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String operandMethod = context.newSingleMethod(this.operand); + context.callDelegateSingle(m, operandMethod); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + String operandMethod = context.newMultiMethod(this.operand); + context.callDelegateMulti(m, operandMethod); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java new file mode 100644 index 0000000..8bd4d3f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/CubeNode.java @@ -0,0 +1,56 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class CubeNode extends AbstractUnaryNode { + public CubeNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new CubeNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.operand.evalSingle(x, y, z, type); + return v * v * v; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] = res[i] * res[i] * res[i]; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java new file mode 100644 index 0000000..4be770d --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/NegMulNode.java @@ -0,0 +1,83 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class NegMulNode extends AbstractUnaryNode { + private final double negMul; + + public NegMulNode(AstNode operand, double negMul) { + super(operand); + this.negMul = negMul; + } + + protected AstNode newInstance(AstNode operand) { + return new NegMulNode(operand, this.negMul); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.operand.evalSingle(x, y, z, type); + return v > 0.0 ? v : v * this.negMul; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + double v = res[i]; + res[i] = v > 0.0 ? v : v * this.negMul; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.store(v, Type.DOUBLE_TYPE); + Label negMulLabel = new Label(); + Label end = new Label(); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifle(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.goTo(end); + m.visitLabel(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(this.negMul); + m.mul(Type.DOUBLE_TYPE); + m.visitLabel(end); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.store(v, Type.DOUBLE_TYPE); + Label negMulLabel = new Label(); + Label end = new Label(); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(0.0); + m.cmpl(Type.DOUBLE_TYPE); + m.ifle(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.goTo(end); + m.visitLabel(negMulLabel); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(this.negMul); + m.mul(Type.DOUBLE_TYPE); + m.visitLabel(end); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java new file mode 100644 index 0000000..284ff98 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SquareNode.java @@ -0,0 +1,52 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class SquareNode extends AbstractUnaryNode { + public SquareNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new SquareNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = this.operand.evalSingle(x, y, z, type); + return v * v; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + res[i] *= res[i]; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java new file mode 100644 index 0000000..ae7e8b1 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ast/unary/SqueezeNode.java @@ -0,0 +1,84 @@ +package org.bxteam.divinemc.dfc.common.ast.unary; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.gen.BytecodeGen; +import net.minecraft.util.Mth; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; + +public class SqueezeNode extends AbstractUnaryNode { + public SqueezeNode(AstNode operand) { + super(operand); + } + + protected AstNode newInstance(AstNode operand) { + return new SqueezeNode(operand); + } + + public double evalSingle(int x, int y, int z, EvalType type) { + double v = Mth.clamp(this.operand.evalSingle(x, y, z, type), -1.0, 1.0); + return v / 2.0 - v * v * v / 24.0; + } + + public void evalMulti(double[] res, int[] x, int[] y, int[] z, EvalType type) { + this.operand.evalMulti(res, x, y, z, type); + + for(int i = 0; i < res.length; ++i) { + double v = Mth.clamp(res[i], -1.0, 1.0); + res[i] = v / 2.0 - v * v * v / 24.0; + } + + } + + public void doBytecodeGenSingle(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenSingle(context, m, localVarConsumer); + m.dconst(1.0); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.dconst(-1.0); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.store(v, Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(2.0); + m.div(Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.dconst(24.0); + m.div(Type.DOUBLE_TYPE); + m.sub(Type.DOUBLE_TYPE); + m.areturn(Type.DOUBLE_TYPE); + } + + public void doBytecodeGenMulti(BytecodeGen.Context context, InstructionAdapter m, BytecodeGen.Context.LocalVarConsumer localVarConsumer) { + super.doBytecodeGenMulti(context, m, localVarConsumer); + context.doCountedLoop(m, localVarConsumer, (idx) -> { + int v = localVarConsumer.createLocalVariable("v", Type.DOUBLE_TYPE.getDescriptor()); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.dup2(); + m.aload(Type.DOUBLE_TYPE); + m.dconst(1.0); + m.invokestatic(Type.getInternalName(Math.class), "min", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.dconst(-1.0); + m.invokestatic(Type.getInternalName(Math.class), "max", Type.getMethodDescriptor(Type.DOUBLE_TYPE, new Type[]{Type.DOUBLE_TYPE, Type.DOUBLE_TYPE}), false); + m.store(v, Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dconst(2.0); + m.div(Type.DOUBLE_TYPE); + m.load(v, Type.DOUBLE_TYPE); + m.dup2(); + m.dup2(); + m.mul(Type.DOUBLE_TYPE); + m.mul(Type.DOUBLE_TYPE); + m.dconst(24.0); + m.div(Type.DOUBLE_TYPE); + m.sub(Type.DOUBLE_TYPE); + m.astore(Type.DOUBLE_TYPE); + }); + m.areturn(Type.VOID_TYPE); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java new file mode 100644 index 0000000..2afddfa --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IArrayCacheCapable.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +import org.bxteam.divinemc.dfc.common.util.ArrayCache; + +public interface IArrayCacheCapable { + ArrayCache c2me$getArrayCache(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java new file mode 100644 index 0000000..1454273 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IBlendingAwareVisitor.java @@ -0,0 +1,5 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +public interface IBlendingAwareVisitor { + boolean c2me$isBlendingEnabled(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java new file mode 100644 index 0000000..4fa5595 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/ICoordinatesFilling.java @@ -0,0 +1,5 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +public interface ICoordinatesFilling { + void c2me$fillCoordinates(int[] var1, int[] var2, int[] var3); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java new file mode 100644 index 0000000..9236883 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IEqualityOverriding.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +public interface IEqualityOverriding { + void c2me$overrideEquality(Object var1); + + Object c2me$getOverriddenEquality(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java new file mode 100644 index 0000000..8289c85 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/ducks/IFastCacheLike.java @@ -0,0 +1,20 @@ +package org.bxteam.divinemc.dfc.common.ducks; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import net.minecraft.world.level.levelgen.DensityFunction; + +public interface IFastCacheLike extends DensityFunction { + long CACHE_MISS_NAN_BITS = 9222769054270909007L; + + double c2me$getCached(int var1, int var2, int var3, EvalType var4); + + boolean c2me$getCached(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + + void c2me$cache(int var1, int var2, int var3, EvalType var4, double var5); + + void c2me$cache(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + + DensityFunction c2me$getDelegate(); + + DensityFunction c2me$withDelegate(DensityFunction var1); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java new file mode 100644 index 0000000..365ed1a --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/BytecodeGen.java @@ -0,0 +1,499 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import com.google.common.io.Files; +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ast.McToAst; +import org.bxteam.divinemc.dfc.common.ast.dfvisitor.StripBlending; +import org.bxteam.divinemc.dfc.common.ast.misc.ConstantNode; +import org.bxteam.divinemc.dfc.common.ast.misc.RootNode; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import org.bxteam.divinemc.dfc.common.vif.AstVanillaInterface; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMaps; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.stream.Collectors; +import net.minecraft.util.CubicSpline; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.DensityFunctions; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.AnalyzerAdapter; +import org.objectweb.asm.commons.InstructionAdapter; + +public class BytecodeGen { + private static final File exportDir = new File("./cache/c2me-dfc"); + private static final AtomicLong ordinal = new AtomicLong(); + public static final Hash.Strategy RELAXED_STRATEGY; + private static final Object2ReferenceMap> compilationCache; + + public BytecodeGen() { + } + + public static DensityFunction compile(DensityFunction densityFunction, Reference2ReferenceMap tempCache) { + DensityFunction cached = (DensityFunction)tempCache.get(densityFunction); + if (cached != null) { + return cached; + } else if (densityFunction instanceof AstVanillaInterface) { + AstVanillaInterface vif = (AstVanillaInterface)densityFunction; + AstNode ast = vif.getAstNode(); + return new CompiledDensityFunction(compile0(ast), vif.getBlendingFallback()); + } else { + AstNode ast = McToAst.toAst(densityFunction.mapAll(StripBlending.INSTANCE)); + if (ast instanceof ConstantNode) { + ConstantNode constantNode = (ConstantNode)ast; + return DensityFunctions.constant(constantNode.getValue()); + } else { + CompiledDensityFunction compiled = new CompiledDensityFunction(compile0(ast), densityFunction); + tempCache.put(densityFunction, compiled); + return compiled; + } + } + } + + public static synchronized CompiledEntry compile0(AstNode node) { + Class cached = (Class)compilationCache.get(node); + ClassWriter writer = new ClassWriter(3); + String name = cached != null ? String.format("DfcCompiled_discarded") : String.format("DfcCompiled_%d", ordinal.getAndIncrement()); + writer.visit(65, 17, name, (String)null, Type.getInternalName(Object.class), new String[]{Type.getInternalName(CompiledEntry.class)}); + RootNode rootNode = new RootNode(node); + Context genContext = new Context(writer, name); + genContext.newSingleMethod0((adapter, localVarConsumer) -> { + rootNode.doBytecodeGenSingle(genContext, adapter, localVarConsumer); + }, "evalSingle", true); + genContext.newMultiMethod0((adapter, localVarConsumer) -> { + rootNode.doBytecodeGenMulti(genContext, adapter, localVarConsumer); + }, "evalMulti", true); + List args = (List)genContext.args.entrySet().stream().sorted(Comparator.comparingInt((o) -> { + return ((Context.FieldRecord)o.getValue()).ordinal(); + })).map(Map.Entry::getKey).collect(Collectors.toCollection(ArrayList::new)); + if (cached != null) { + try { + return (CompiledEntry)cached.getConstructor(List.class).newInstance(args); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException var11) { + ReflectiveOperationException e = var11; + throw new RuntimeException(e); + } + } else { + genConstructor(genContext); + genGetArgs(genContext); + genNewInstance(genContext); + + Object var8; + for(ListIterator iterator = args.listIterator(); iterator.hasNext(); var8 = iterator.next()) { + } + + byte[] bytes = writer.toByteArray(); + dumpClass(genContext.className, bytes); + Class defined = defineClass(genContext.className, bytes); + compilationCache.put(node, defined); + + try { + return (CompiledEntry)defined.getConstructor(List.class).newInstance(args); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException var12) { + ReflectiveOperationException e = var12; + throw new RuntimeException(e); + } + } + } + + private static void genConstructor(Context context) { + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 1, "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(List.class)}), context.classWriter.visitMethod(1, "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(List.class)}), (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(Type.getInternalName(Object.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0]), false); + Iterator var4 = context.args.entrySet().stream().sorted(Comparator.comparingInt((o) -> { + return ((Context.FieldRecord)o.getValue()).ordinal(); + })).toList().iterator(); + + while(var4.hasNext()) { + Map.Entry entry = (Map.Entry)var4.next(); + String name = ((Context.FieldRecord)entry.getValue()).name(); + Class type = ((Context.FieldRecord)entry.getValue()).type(); + int ordinal = ((Context.FieldRecord)entry.getValue()).ordinal(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.iconst(ordinal); + m.invokeinterface(Type.getInternalName(List.class), "get", Type.getMethodDescriptor(InstructionAdapter.OBJECT_TYPE, new Type[]{Type.INT_TYPE})); + m.checkcast(Type.getType(type)); + m.putfield(context.className, name, Type.getDescriptor(type)); + } + + var4 = context.postProcessMethods.stream().sorted().toList().iterator(); + + while(var4.hasNext()) { + String postProcessingMethod = (String)var4.next(); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(context.className, postProcessingMethod, "()V", false); + } + + m.areturn(Type.VOID_TYPE); + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("list", Type.getDescriptor(List.class), (String)null, start, end, 1); + m.visitMaxs(0, 0); + } + + private static void genGetArgs(Context context) { + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 17, "getArgs", Type.getMethodDescriptor(Type.getType(List.class), new Type[0]), context.classWriter.visitMethod(17, "getArgs", Type.getMethodDescriptor(Type.getType(List.class), new Type[0]), (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.anew(Type.getType(ArrayList.class)); + m.dup(); + m.iconst(context.args.size()); + m.invokespecial(Type.getInternalName(ArrayList.class), "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.INT_TYPE}), false); + m.store(1, InstructionAdapter.OBJECT_TYPE); + Iterator var4 = context.args.entrySet().stream().sorted(Comparator.comparingInt((o) -> { + return ((Context.FieldRecord)o.getValue()).ordinal(); + })).toList().iterator(); + + while(var4.hasNext()) { + Map.Entry entry = (Map.Entry)var4.next(); + String name = ((Context.FieldRecord)entry.getValue()).name(); + Class type = ((Context.FieldRecord)entry.getValue()).type(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.getfield(context.className, name, Type.getDescriptor(type)); + m.invokeinterface(Type.getInternalName(List.class), "add", Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[]{InstructionAdapter.OBJECT_TYPE})); + m.pop(); + } + + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.areturn(InstructionAdapter.OBJECT_TYPE); + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("list", Type.getDescriptor(List.class), (String)null, start, end, 1); + m.visitMaxs(0, 0); + } + + private static void genNewInstance(Context context) { + InstructionAdapter m = new InstructionAdapter(new AnalyzerAdapter(context.className, 17, "newInstance", Type.getMethodDescriptor(Type.getType(CompiledEntry.class), new Type[]{Type.getType(List.class)}), context.classWriter.visitMethod(17, "newInstance", Type.getMethodDescriptor(Type.getType(CompiledEntry.class), new Type[]{Type.getType(List.class)}), (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.anew(Type.getType(context.classDesc)); + m.dup(); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.invokespecial(context.className, "", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(List.class)}), false); + m.areturn(InstructionAdapter.OBJECT_TYPE); + m.visitLabel(end); + m.visitLocalVariable("this", context.classDesc, (String)null, start, end, 0); + m.visitLocalVariable("list", Type.getDescriptor(List.class), (String)null, start, end, 1); + m.visitMaxs(0, 0); + } + + private static void dumpClass(String className, byte[] bytes) { + File outputFile = new File(exportDir, className + ".class"); + outputFile.getParentFile().mkdirs(); + + try { + Files.write(bytes, outputFile); + } catch (IOException var4) { + IOException e = var4; + e.printStackTrace(); + } + + } + + private static Class defineClass(final String className, final byte[] bytes) { + ClassLoader classLoader = new ClassLoader(BytecodeGen.class.getClassLoader()) { + public Class loadClass(String name) throws ClassNotFoundException { + return name.equals(className) ? super.defineClass(name, bytes, 0, bytes.length) : super.loadClass(name); + } + }; + + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException var4) { + ClassNotFoundException e = var4; + throw new RuntimeException(e); + } + } + + static { + try { + org.bxteam.divinemc.util.Files.deleteRecursively(exportDir); + } catch (IOException var1) { + IOException e = var1; + e.printStackTrace(); + } + + RELAXED_STRATEGY = new Hash.Strategy() { + public int hashCode(AstNode o) { + return o.relaxedHashCode(); + } + + public boolean equals(AstNode a, AstNode b) { + return a.relaxedEquals(b); + } + }; + compilationCache = Object2ReferenceMaps.synchronize(new Object2ReferenceOpenCustomHashMap<>(RELAXED_STRATEGY)); + } + + public static class Context { + public static final String SINGLE_DESC; + public static final String MULTI_DESC; + public final ClassWriter classWriter; + public final String className; + public final String classDesc; + private int methodIdx = 0; + private final Object2ReferenceOpenHashMap singleMethods = new Object2ReferenceOpenHashMap<>(); + private final Object2ReferenceOpenHashMap multiMethods = new Object2ReferenceOpenHashMap<>(); + private final Object2ReferenceOpenHashMap, String> splineMethods = new Object2ReferenceOpenHashMap<>(); + private final ObjectOpenHashSet postProcessMethods = new ObjectOpenHashSet<>(); + private final Reference2ObjectOpenHashMap args = new Reference2ObjectOpenHashMap<>(); + + public Context(ClassWriter classWriter, String className) { + this.classWriter = (ClassWriter)Objects.requireNonNull(classWriter); + this.className = (String)Objects.requireNonNull(className); + this.classDesc = String.format("L%s;", this.className); + } + + public String nextMethodName() { + return String.format("method_%d", this.methodIdx++); + } + + public String nextMethodName(String suffix) { + return String.format("method_%d_%s", this.methodIdx++, suffix); + } + + public String newSingleMethod(AstNode node) { + return this.singleMethods.computeIfAbsent(node, (AstNode node1) -> this.newSingleMethod((adapter, localVarConsumer) -> node1.doBytecodeGenSingle(this, adapter, localVarConsumer), nextMethodName(node.getClass().getSimpleName()))); + } + + public String newSingleMethod(BiConsumer generator) { + return this.newSingleMethod(generator, this.nextMethodName()); + } + + public String newSingleMethod(BiConsumer generator, String name) { + this.newSingleMethod0(generator, name, false); + return name; + } + + private void newSingleMethod0(BiConsumer generator, String name, boolean isPublic) { + InstructionAdapter adapter = new InstructionAdapter(new AnalyzerAdapter(this.className, (isPublic ? 1 : 2) | 16, name, SINGLE_DESC, this.classWriter.visitMethod((isPublic ? 1 : 2) | 16, name, SINGLE_DESC, (String)null, (String[])null))); + List>> extraLocals = new ArrayList<>(); + Label start = new Label(); + Label end = new Label(); + adapter.visitLabel(start); + generator.accept(adapter, (localName, localDesc) -> { + int ordinal = extraLocals.size() + 5; + extraLocals.add(IntObjectPair.of(ordinal, Pair.of(localName, localDesc))); + return ordinal; + }); + adapter.visitLabel(end); + adapter.visitLocalVariable("this", this.classDesc, (String)null, start, end, 0); + adapter.visitLocalVariable("x", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 1); + adapter.visitLocalVariable("y", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 2); + adapter.visitLocalVariable("z", Type.INT_TYPE.getDescriptor(), (String)null, start, end, 3); + adapter.visitLocalVariable("evalType", Type.getType(EvalType.class).getDescriptor(), (String)null, start, end, 4); + Iterator var8 = extraLocals.iterator(); + + while(var8.hasNext()) { + IntObjectPair> local = (IntObjectPair)var8.next(); + adapter.visitLocalVariable((String)((Pair)local.right()).left(), (String)((Pair)local.right()).right(), (String)null, start, end, local.leftInt()); + } + + adapter.visitMaxs(0, 0); + } + + public String newMultiMethod(AstNode node) { + return this.multiMethods.computeIfAbsent(node, (AstNode node1) -> this.newMultiMethod((adapter, localVarConsumer) -> node1.doBytecodeGenMulti(this, adapter, localVarConsumer), nextMethodName(node.getClass().getSimpleName()))); + } + + public String newMultiMethod(BiConsumer generator) { + return this.newMultiMethod(generator, this.nextMethodName()); + } + + public String newMultiMethod(BiConsumer generator, String name) { + this.newMultiMethod0(generator, name, false); + return name; + } + + private void newMultiMethod0(BiConsumer generator, String name, boolean isPublic) { + InstructionAdapter adapter = new InstructionAdapter(new AnalyzerAdapter(this.className, (isPublic ? 1 : 2) | 16, name, MULTI_DESC, this.classWriter.visitMethod((isPublic ? 1 : 2) | 16, name, MULTI_DESC, (String)null, (String[])null))); + List>> extraLocals = new ArrayList(); + Label start = new Label(); + Label end = new Label(); + adapter.visitLabel(start); + generator.accept(adapter, (localName, localDesc) -> { + int ordinal = extraLocals.size() + 7; + extraLocals.add(IntObjectPair.of(ordinal, Pair.of(localName, localDesc))); + return ordinal; + }); + adapter.visitLabel(end); + adapter.visitLocalVariable("this", this.classDesc, (String)null, start, end, 0); + adapter.visitLocalVariable("res", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 1); + adapter.visitLocalVariable("x", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 2); + adapter.visitLocalVariable("y", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 3); + adapter.visitLocalVariable("z", Type.getType(double[].class).getDescriptor(), (String)null, start, end, 4); + adapter.visitLocalVariable("evalType", Type.getType(EvalType.class).getDescriptor(), (String)null, start, end, 5); + adapter.visitLocalVariable("arrayCache", Type.getType(ArrayCache.class).getDescriptor(), (String)null, start, end, 6); + Iterator var8 = extraLocals.iterator(); + + while(var8.hasNext()) { + IntObjectPair> local = (IntObjectPair)var8.next(); + adapter.visitLocalVariable((String)((Pair)local.right()).left(), (String)((Pair)local.right()).right(), (String)null, start, end, local.leftInt()); + } + + adapter.visitMaxs(0, 0); + } + + public String getCachedSplineMethod(CubicSpline spline) { + return (String)this.splineMethods.get(spline); + } + + public void cacheSplineMethod(CubicSpline spline, String method) { + this.splineMethods.put(spline, method); + } + + public void callDelegateSingle(InstructionAdapter m, String target) { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, Type.INT_TYPE); + m.load(2, Type.INT_TYPE); + m.load(3, Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(this.className, target, SINGLE_DESC, false); + } + + public void callDelegateMulti(InstructionAdapter m, String target) { + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.load(6, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(this.className, target, MULTI_DESC, false); + } + + public String newField(Class type, T data) { + FieldRecord existing = (FieldRecord)this.args.get(data); + if (existing != null) { + return existing.name(); + } else { + int size = this.args.size(); + String name = String.format("field_%d", size); + this.classWriter.visitField(2, name, Type.getDescriptor(type), (String)null, (Object)null); + this.args.put(data, new FieldRecord(name, size, type)); + return name; + } + } + + public void doCountedLoop(InstructionAdapter m, LocalVarConsumer localVarConsumer, IntConsumer bodyGenerator) { + int loopIdx = localVarConsumer.createLocalVariable("loopIdx", Type.INT_TYPE.getDescriptor()); + m.iconst(0); + m.store(loopIdx, Type.INT_TYPE); + Label start = new Label(); + Label end = new Label(); + m.visitLabel(start); + m.load(loopIdx, Type.INT_TYPE); + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.arraylength(); + m.ificmpge(end); + bodyGenerator.accept(loopIdx); + m.iinc(loopIdx, 1); + m.goTo(start); + m.visitLabel(end); + } + + public void delegateToSingle(InstructionAdapter m, LocalVarConsumer localVarConsumer, AstNode current) { + String singleMethod = this.newSingleMethod(current); + this.doCountedLoop(m, localVarConsumer, (idx) -> { + m.load(1, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.load(0, InstructionAdapter.OBJECT_TYPE); + m.load(2, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(3, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(4, InstructionAdapter.OBJECT_TYPE); + m.load(idx, Type.INT_TYPE); + m.aload(Type.INT_TYPE); + m.load(5, InstructionAdapter.OBJECT_TYPE); + m.invokevirtual(this.className, singleMethod, SINGLE_DESC, false); + m.astore(Type.DOUBLE_TYPE); + }); + } + + public void genPostprocessingMethod(String name, Consumer generator) { + if (!this.postProcessMethods.contains(name)) { + InstructionAdapter adapter = new InstructionAdapter(new AnalyzerAdapter(this.className, 18, name, "()V", this.classWriter.visitMethod(18, name, "()V", (String)null, (String[])null))); + Label start = new Label(); + Label end = new Label(); + adapter.visitLabel(start); + generator.accept(adapter); + adapter.visitLabel(end); + adapter.visitMaxs(0, 0); + adapter.visitLocalVariable("this", this.classDesc, (String)null, start, end, 0); + this.postProcessMethods.add(name); + } + } + + static { + SINGLE_DESC = Type.getMethodDescriptor(Type.getType(Double.TYPE), new Type[]{Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(Integer.TYPE), Type.getType(EvalType.class)}); + MULTI_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(double[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(int[].class), Type.getType(EvalType.class), Type.getType(ArrayCache.class)}); + } + + public interface LocalVarConsumer { + int createLocalVariable(String var1, String var2); + } + + private static record FieldRecord(String name, int ordinal, Class type) { + private FieldRecord(String name, int ordinal, Class type) { + this.name = name; + this.ordinal = ordinal; + this.type = type; + } + + public String name() { + return this.name; + } + + public int ordinal() { + return this.ordinal; + } + + public Class type() { + return this.type; + } + } + } + + @FunctionalInterface + public interface EvalMultiInterface { + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5); + } + + @FunctionalInterface + public interface EvalSingleInterface { + double evalSingle(int var1, int var2, int var3, EvalType var4); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java new file mode 100644 index 0000000..15b6c16 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledDensityFunction.java @@ -0,0 +1,98 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import com.google.common.base.Suppliers; +import org.bxteam.divinemc.dfc.common.ducks.IBlendingAwareVisitor; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.function.Supplier; +import net.minecraft.world.level.levelgen.DensityFunction; + +public class CompiledDensityFunction extends SubCompiledDensityFunction { + private final CompiledEntry compiledEntry; + + public CompiledDensityFunction(CompiledEntry compiledEntry, DensityFunction blendingFallback) { + super(compiledEntry, compiledEntry, blendingFallback); + this.compiledEntry = (CompiledEntry)Objects.requireNonNull(compiledEntry); + } + + private CompiledDensityFunction(CompiledEntry compiledEntry, Supplier blendingFallback) { + super(compiledEntry, compiledEntry, blendingFallback); + this.compiledEntry = (CompiledEntry)Objects.requireNonNull(compiledEntry); + } + + public DensityFunction mapAll(Visitor visitor) { + if (visitor instanceof IBlendingAwareVisitor blendingAwareVisitor) { + if (blendingAwareVisitor.c2me$isBlendingEnabled()) { + DensityFunction fallback1 = this.getFallback(); + if (fallback1 == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + return fallback1.mapAll(visitor); + } + } + + boolean modified = false; + List args = this.compiledEntry.getArgs(); + ListIterator iterator = args.listIterator(); + + Object next; + while(iterator.hasNext()) { + next = iterator.next(); + if (next instanceof DensityFunction df) { + if (!(df instanceof IFastCacheLike)) { + DensityFunction applied = df.mapAll(visitor); + if (df != applied) { + iterator.set(applied); + modified = true; + } + } + } + + if (next instanceof NoiseHolder noise) { + NoiseHolder applied = visitor.visitNoise(noise); + if (noise != applied) { + iterator.set(applied); + modified = true; + } + } + } + + iterator = args.listIterator(); + + while(iterator.hasNext()) { + next = iterator.next(); + if (next instanceof IFastCacheLike cacheLike) { + DensityFunction applied = visitor.apply(cacheLike); + if (applied == cacheLike.c2me$getDelegate()) { + iterator.set((Object)null); + modified = true; + } else { + if (!(applied instanceof IFastCacheLike)) { + throw new UnsupportedOperationException("Unsupported transformation on Wrapping node"); + } + + IFastCacheLike newCacheLike = (IFastCacheLike)applied; + iterator.set(newCacheLike); + modified = true; + } + } + } + + Supplier fallback = this.blendingFallback != null ? Suppliers.memoize(() -> { + DensityFunction densityFunction = (DensityFunction)this.blendingFallback.get(); + return densityFunction != null ? densityFunction.mapAll(visitor) : null; + }) : null; + if (fallback != this.blendingFallback) { + modified = true; + } + + if (modified) { + return new CompiledDensityFunction(this.compiledEntry.newInstance(args), fallback); + } else { + return this; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java new file mode 100644 index 0000000..35b0867 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/CompiledEntry.java @@ -0,0 +1,15 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.List; + +public interface CompiledEntry extends ISingleMethod, IMultiMethod { + double evalSingle(int var1, int var2, int var3, EvalType var4); + + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5, ArrayCache var6); + + CompiledEntry newInstance(List var1); + + List getArgs(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java new file mode 100644 index 0000000..1b4ec4e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/DelegatingBlendingAwareVisitor.java @@ -0,0 +1,28 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ducks.IBlendingAwareVisitor; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; +import org.jetbrains.annotations.NotNull; + +public class DelegatingBlendingAwareVisitor implements IBlendingAwareVisitor, DensityFunction.Visitor { + private final DensityFunction.Visitor delegate; + private final boolean blendingEnabled; + + public DelegatingBlendingAwareVisitor(DensityFunction.Visitor delegate, boolean blendingEnabled) { + this.delegate = Objects.requireNonNull(delegate); + this.blendingEnabled = blendingEnabled; + } + + public @NotNull DensityFunction apply(@NotNull DensityFunction densityFunction) { + return this.delegate.apply(densityFunction); + } + + public DensityFunction.@NotNull NoiseHolder visitNoise(DensityFunction.@NotNull NoiseHolder noiseDensityFunction) { + return this.delegate.visitNoise(noiseDensityFunction); + } + + public boolean c2me$isBlendingEnabled() { + return this.blendingEnabled; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java new file mode 100644 index 0000000..21b2796 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/IMultiMethod.java @@ -0,0 +1,9 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; + +@FunctionalInterface +public interface IMultiMethod { + void evalMulti(double[] var1, int[] var2, int[] var3, int[] var4, EvalType var5, ArrayCache var6); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java new file mode 100644 index 0000000..f946553 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/ISingleMethod.java @@ -0,0 +1,8 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; + +@FunctionalInterface +public interface ISingleMethod { + double evalSingle(int var1, int var2, int var3, EvalType var4); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java new file mode 100644 index 0000000..aec0c62 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/gen/SubCompiledDensityFunction.java @@ -0,0 +1,142 @@ +package org.bxteam.divinemc.dfc.common.gen; + +import com.google.common.base.Suppliers; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable; +import org.bxteam.divinemc.dfc.common.ducks.IBlendingAwareVisitor; +import org.bxteam.divinemc.dfc.common.ducks.ICoordinatesFilling; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import org.bxteam.divinemc.dfc.common.vif.EachApplierVanillaInterface; +import java.util.Objects; +import java.util.function.Supplier; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.Blender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SubCompiledDensityFunction implements DensityFunction { + private static final Logger LOGGER = LoggerFactory.getLogger(SubCompiledDensityFunction.class); + private final ISingleMethod singleMethod; + private final IMultiMethod multiMethod; + protected final Supplier blendingFallback; + + public SubCompiledDensityFunction(ISingleMethod singleMethod, IMultiMethod multiMethod, DensityFunction blendingFallback) { + this(singleMethod, multiMethod, unwrap(blendingFallback)); + } + + protected SubCompiledDensityFunction(ISingleMethod singleMethod, IMultiMethod multiMethod, Supplier blendingFallback) { + this.singleMethod = (ISingleMethod)Objects.requireNonNull(singleMethod); + this.multiMethod = (IMultiMethod)Objects.requireNonNull(multiMethod); + this.blendingFallback = blendingFallback; + } + + private static Supplier unwrap(DensityFunction densityFunction) { + if (densityFunction instanceof SubCompiledDensityFunction scdf) { + return scdf.blendingFallback; + } else { + return densityFunction != null ? Suppliers.ofInstance(densityFunction) : null; + } + } + + public double compute(FunctionContext pos) { + if (pos.getBlender() != Blender.empty()) { + DensityFunction fallback = this.getFallback(); + if (fallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } else { + return fallback.compute(pos); + } + } else { + return this.singleMethod.evalSingle(pos.blockX(), pos.blockY(), pos.blockZ(), EvalType.from(pos)); + } + } + + public void fillArray(double[] densities, ContextProvider applier) { + if (applier instanceof NoiseChunk sampler) { + if (sampler.getBlender() != Blender.empty()) { + DensityFunction fallback = this.getFallback(); + if (fallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + fallback.fillArray(densities, applier); + return; + } + } + + if (applier instanceof EachApplierVanillaInterface vanillaInterface) { + this.multiMethod.evalMulti(densities, vanillaInterface.getX(), vanillaInterface.getY(), vanillaInterface.getZ(), EvalType.from(applier), vanillaInterface.c2me$getArrayCache()); + } else { + ArrayCache var10000; + if (applier instanceof IArrayCacheCapable cacheCapable) { + var10000 = cacheCapable.c2me$getArrayCache(); + } else { + var10000 = new ArrayCache(); + } + + ArrayCache cache = var10000; + int[] x = cache.getIntArray(densities.length, false); + int[] y = cache.getIntArray(densities.length, false); + int[] z = cache.getIntArray(densities.length, false); + if (applier instanceof ICoordinatesFilling coordinatesFilling) { + coordinatesFilling.c2me$fillCoordinates(x, y, z); + } else { + for(int i = 0; i < densities.length; ++i) { + FunctionContext pos = applier.forIndex(i); + x[i] = pos.blockX(); + y[i] = pos.blockY(); + z[i] = pos.blockZ(); + } + } + + this.multiMethod.evalMulti(densities, x, y, z, EvalType.from(applier), cache); + } + } + + public DensityFunction mapAll(Visitor visitor) { + if (this.getClass() != SubCompiledDensityFunction.class) { + throw new AbstractMethodError(); + } else { + if (visitor instanceof IBlendingAwareVisitor) { + IBlendingAwareVisitor blendingAwareVisitor = (IBlendingAwareVisitor)visitor; + if (blendingAwareVisitor.c2me$isBlendingEnabled()) { + DensityFunction fallback1 = this.getFallback(); + if (fallback1 == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + return fallback1.mapAll(visitor); + } + } + + boolean modified = false; + Supplier fallback = this.blendingFallback != null ? Suppliers.memoize(() -> { + DensityFunction densityFunction = (DensityFunction)this.blendingFallback.get(); + return densityFunction != null ? densityFunction.mapAll(visitor) : null; + }) : null; + if (fallback != this.blendingFallback) { + modified = true; + } + + return modified ? new SubCompiledDensityFunction(this.singleMethod, this.multiMethod, fallback) : this; + } + } + + public double minValue() { + return Double.MIN_VALUE; + } + + public double maxValue() { + return Double.MAX_VALUE; + } + + public KeyDispatchDataCodec codec() { + throw new UnsupportedOperationException(); + } + + protected DensityFunction getFallback() { + return this.blendingFallback != null ? (DensityFunction)this.blendingFallback.get() : null; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java new file mode 100644 index 0000000..a3813ca --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/util/ArrayCache.java @@ -0,0 +1,57 @@ +package org.bxteam.divinemc.dfc.common.util; + +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import java.util.Arrays; + +public class ArrayCache { + private final Int2ReferenceArrayMap> doubleArrayCache = new Int2ReferenceArrayMap<>(); + private final Int2ReferenceArrayMap> intArrayCache = new Int2ReferenceArrayMap<>(); + + public ArrayCache() { + } + + public double[] getDoubleArray(int size, boolean zero) { + ReferenceArrayList list = this.doubleArrayCache.computeIfAbsent(size, (k) -> { + return new ReferenceArrayList<>(); + }); + if (list.isEmpty()) { + return new double[size]; + } else { + double[] popped = list.pop(); + if (zero) { + Arrays.fill(popped, 0.0); + } + + return popped; + } + } + + public int[] getIntArray(int size, boolean zero) { + ReferenceArrayList list = this.intArrayCache.computeIfAbsent(size, (k) -> { + return new ReferenceArrayList<>(); + }); + if (list.isEmpty()) { + return new int[size]; + } else { + int[] popped = list.pop(); + if (zero) { + Arrays.fill(popped, 0); + } + + return popped; + } + } + + public void recycle(double[] array) { + this.doubleArrayCache.computeIfAbsent(array.length, (k) -> { + return new ReferenceArrayList<>(); + }).add(array); + } + + public void recycle(int[] array) { + this.intArrayCache.computeIfAbsent(array.length, (k) -> { + return new ReferenceArrayList<>(); + }).add(array); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java new file mode 100644 index 0000000..5b8c9ef --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/AstVanillaInterface.java @@ -0,0 +1,98 @@ +package org.bxteam.divinemc.dfc.common.vif; + +import org.bxteam.divinemc.dfc.common.ast.AstNode; +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ast.misc.CacheLikeNode; +import org.bxteam.divinemc.dfc.common.ast.misc.DelegateNode; +import org.bxteam.divinemc.dfc.common.ducks.IFastCacheLike; +import java.util.Objects; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.Blender; + +public class AstVanillaInterface implements DensityFunction { + private final AstNode astNode; + private final DensityFunction blendingFallback; + + public AstVanillaInterface(AstNode astNode, DensityFunction blendingFallback) { + this.astNode = (AstNode)Objects.requireNonNull(astNode); + this.blendingFallback = blendingFallback; + } + + public double compute(FunctionContext pos) { + if (pos.getBlender() != Blender.empty()) { + if (this.blendingFallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } else { + return this.blendingFallback.compute(pos); + } + } else { + return this.astNode.evalSingle(pos.blockX(), pos.blockY(), pos.blockZ(), EvalType.from(pos)); + } + } + + public void fillArray(double[] densities, ContextProvider applier) { + if (applier instanceof NoiseChunk sampler) { + if (sampler.getBlender() != Blender.empty()) { + if (this.blendingFallback == null) { + throw new IllegalStateException("blendingFallback is no more"); + } + + this.blendingFallback.fillArray(densities, applier); + return; + } + } + + if (applier instanceof EachApplierVanillaInterface vanillaInterface) { + this.astNode.evalMulti(densities, vanillaInterface.getX(), vanillaInterface.getY(), vanillaInterface.getZ(), EvalType.from(applier)); + } else { + int[] x = new int[densities.length]; + int[] y = new int[densities.length]; + int[] z = new int[densities.length]; + + for(int i = 0; i < densities.length; ++i) { + FunctionContext pos = applier.forIndex(i); + x[i] = pos.blockX(); + y[i] = pos.blockY(); + z[i] = pos.blockZ(); + } + + this.astNode.evalMulti(densities, x, y, z, EvalType.from(applier)); + } + } + + public DensityFunction mapAll(Visitor visitor) { + AstNode transformed = this.astNode.transform((astNode) -> { + if (astNode instanceof DelegateNode delegateNode) { + return new DelegateNode(delegateNode.getDelegate().mapAll(visitor)); + } else if (astNode instanceof CacheLikeNode cacheLikeNode) { + return new CacheLikeNode((IFastCacheLike)cacheLikeNode.getCacheLike().mapAll(visitor), cacheLikeNode.getDelegate()); + } else { + return astNode; + } + }); + DensityFunction blendingFallback1 = this.blendingFallback != null ? this.blendingFallback.mapAll(visitor) : null; + return transformed == this.astNode && blendingFallback1 == this.blendingFallback ? this : new AstVanillaInterface(transformed, blendingFallback1); + } + + public double minValue() { + return this.blendingFallback.minValue(); + } + + public double maxValue() { + return this.blendingFallback.maxValue(); + } + + public KeyDispatchDataCodec codec() { + throw new UnsupportedOperationException(); + } + + public AstNode getAstNode() { + return this.astNode; + } + + public DensityFunction getBlendingFallback() { + return this.blendingFallback; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java new file mode 100644 index 0000000..d557a82 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/EachApplierVanillaInterface.java @@ -0,0 +1,58 @@ +package org.bxteam.divinemc.dfc.common.vif; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import org.bxteam.divinemc.dfc.common.ducks.IArrayCacheCapable; +import org.bxteam.divinemc.dfc.common.util.ArrayCache; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; + +public class EachApplierVanillaInterface implements DensityFunction.ContextProvider, IArrayCacheCapable { + private final int[] x; + private final int[] y; + private final int[] z; + private final EvalType type; + private final ArrayCache cache; + + public EachApplierVanillaInterface(int[] x, int[] y, int[] z, EvalType type) { + this(x, y, z, type, new ArrayCache()); + } + + public EachApplierVanillaInterface(int[] x, int[] y, int[] z, EvalType type, ArrayCache cache) { + this.x = (int[])Objects.requireNonNull(x); + this.y = (int[])Objects.requireNonNull(y); + this.z = (int[])Objects.requireNonNull(z); + this.type = (EvalType)Objects.requireNonNull(type); + this.cache = (ArrayCache)Objects.requireNonNull(cache); + } + + public DensityFunction.FunctionContext forIndex(int index) { + return new NoisePosVanillaInterface(this.x[index], this.y[index], this.z[index], this.type); + } + + public void fillAllDirectly(double[] densities, DensityFunction densityFunction) { + for(int i = 0; i < this.x.length; ++i) { + densities[i] = densityFunction.compute(this.forIndex(i)); + } + + } + + public int[] getX() { + return this.x; + } + + public int[] getY() { + return this.y; + } + + public int[] getZ() { + return this.z; + } + + public EvalType getType() { + return this.type; + } + + public ArrayCache c2me$getArrayCache() { + return this.cache; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java new file mode 100644 index 0000000..88b83cc --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/dfc/common/vif/NoisePosVanillaInterface.java @@ -0,0 +1,35 @@ +package org.bxteam.divinemc.dfc.common.vif; + +import org.bxteam.divinemc.dfc.common.ast.EvalType; +import java.util.Objects; +import net.minecraft.world.level.levelgen.DensityFunction; + +public class NoisePosVanillaInterface implements DensityFunction.FunctionContext { + private final int x; + private final int y; + private final int z; + private final EvalType type; + + public NoisePosVanillaInterface(int x, int y, int z, EvalType type) { + this.x = x; + this.y = y; + this.z = z; + this.type = (EvalType)Objects.requireNonNull(type); + } + + public int blockX() { + return this.x; + } + + public int blockY() { + return this.y; + } + + public int blockZ() { + return this.z; + } + + public EvalType getType() { + return this.type; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java new file mode 100644 index 0000000..81b7b27 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPath.java @@ -0,0 +1,284 @@ +package org.bxteam.divinemc.entity.pathfinding; + +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; + +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/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java new file mode 100644 index 0000000..ad33ab3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/AsyncPathProcessor.java @@ -0,0 +1,48 @@ +package org.bxteam.divinemc.entity.pathfinding; + +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.bxteam.divinemc.DivineConfig.asyncPathfindingMaxThreads, + org.bxteam.divinemc.DivineConfig.asyncPathfindingKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setNameFormat("DivineMC 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/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java new file mode 100644 index 0000000..5700f5c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorCache.java @@ -0,0 +1,44 @@ +package org.bxteam.divinemc.entity.pathfinding; + +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; +import java.util.concurrent.ConcurrentLinkedQueue; + +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 ConcurrentLinkedQueue<>()); + } + + 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/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java new file mode 100644 index 0000000..1f0f1a3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorFeatures.java @@ -0,0 +1,23 @@ +package org.bxteam.divinemc.entity.pathfinding; + +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/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java new file mode 100644 index 0000000..0fa93c7 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorGenerator.java @@ -0,0 +1,8 @@ +package org.bxteam.divinemc.entity.pathfinding; + +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import org.jetbrains.annotations.NotNull; + +public interface NodeEvaluatorGenerator { + @NotNull NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java new file mode 100644 index 0000000..c9f3d8e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/NodeEvaluatorType.java @@ -0,0 +1,17 @@ +package org.bxteam.divinemc.entity.pathfinding; + +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/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java new file mode 100644 index 0000000..9211740 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/pathfinding/PathProcessState.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.entity.pathfinding; + +public enum PathProcessState { + WAITING, + PROCESSING, + COMPLETED +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java new file mode 100644 index 0000000..626cbc8 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java @@ -0,0 +1,141 @@ +package org.bxteam.divinemc.entity.tracking; + +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.bxteam.divinemc.DivineConfig.asyncEntityTrackerMaxThreads, + org.bxteam.divinemc.DivineConfig.asyncEntityTrackerKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() + .setThreadFactory( + r -> new MultithreadedTrackerThread() { + @Override + public void run() { + r.run(); + } + } + ) + .setNameFormat("DivineMC 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.bxteam.divinemc.DivineConfig.multithreadedCompatModeEnabled) { + 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 + @SuppressWarnings("DuplicatedCode") + 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/divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java new file mode 100644 index 0000000..29c2693 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/Bindings.java @@ -0,0 +1,105 @@ +package org.bxteam.divinemc.math; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; + +public class Bindings { + private static final Logger LOGGER = LoggerFactory.getLogger(Bindings.class); + + private static final MethodHandle MH_c2me_natives_noise_perlin_double = bind(BindingsTemplate.c2me_natives_noise_perlin_double, "c2me_natives_noise_perlin_double"); + private static final MethodHandle MH_c2me_natives_noise_perlin_double_ptr = bind(BindingsTemplate.c2me_natives_noise_perlin_double_ptr, "c2me_natives_noise_perlin_double"); + private static final MethodHandle MH_c2me_natives_noise_perlin_double_batch = bind(BindingsTemplate.c2me_natives_noise_perlin_double_batch, "c2me_natives_noise_perlin_double_batch"); + private static final MethodHandle MH_c2me_natives_noise_perlin_double_batch_partial_ptr = bind(BindingsTemplate.c2me_natives_noise_perlin_double_batch_ptr, "c2me_natives_noise_perlin_double_batch"); + private static final MethodHandle MH_c2me_natives_noise_interpolated = bind(BindingsTemplate.c2me_natives_noise_interpolated, "c2me_natives_noise_interpolated"); + private static final MethodHandle MH_c2me_natives_noise_interpolated_ptr = bind(BindingsTemplate.c2me_natives_noise_interpolated_ptr, "c2me_natives_noise_interpolated"); + private static final MethodHandle MH_c2me_natives_end_islands_sample = bind(BindingsTemplate.c2me_natives_end_islands_sample, "c2me_natives_end_islands_sample"); + private static final MethodHandle MH_c2me_natives_end_islands_sample_ptr = bind(BindingsTemplate.c2me_natives_end_islands_sample_ptr, "c2me_natives_end_islands_sample"); + private static final MethodHandle MH_c2me_natives_biome_access_sample = bind(BindingsTemplate.c2me_natives_biome_access_sample, "c2me_natives_biome_access_sample"); + + private static @Nullable MethodHandle bind(@NotNull MethodHandle template, String prefix) { + if (NativeLoader.currentMachineTarget == null) { + LOGGER.warn("Call to bindings was found! Please disable native acceleration in config, as your system may be incompatible"); + return null; + } + return template.bindTo(NativeLoader.lookup.find(prefix + NativeLoader.currentMachineTarget.getSuffix()).get()); + } + + public static double c2me_natives_noise_perlin_double(MemorySegment data, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_perlin_double.invokeExact(data, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static double c2me_natives_noise_perlin_double(long data_ptr, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_perlin_double_ptr.invokeExact(data_ptr, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static void c2me_natives_noise_perlin_double_batch(MemorySegment data, MemorySegment res, MemorySegment x, MemorySegment y, MemorySegment z, int length) { + try { + MH_c2me_natives_noise_perlin_double_batch.invokeExact(data, res, x, y, z, length); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static void c2me_natives_noise_perlin_double_batch(long data_ptr, MemorySegment res, MemorySegment x, MemorySegment y, MemorySegment z, int length) { + try { + MH_c2me_natives_noise_perlin_double_batch_partial_ptr.invokeExact(data_ptr, res, x, y, z, length); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static double c2me_natives_noise_interpolated(MemorySegment data, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_interpolated.invokeExact(data, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static double c2me_natives_noise_interpolated(long data_ptr, double x, double y, double z) { + try { + return (double) MH_c2me_natives_noise_interpolated_ptr.invokeExact(data_ptr, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static float c2me_natives_end_islands_sample(MemorySegment data, int x, int z) { + try { + return (float) MH_c2me_natives_end_islands_sample.invokeExact(data, x, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static float c2me_natives_end_islands_sample(long data_ptr, int x, int z) { + if ((x * x + z * z) < 0) { // workaround some compiler bugs + return Float.NaN; + } + try { + return (float) MH_c2me_natives_end_islands_sample_ptr.invokeExact(data_ptr, x, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public static int c2me_natives_biome_access_sample(long seed, int x, int y, int z) { + try { + return (int) MH_c2me_natives_biome_access_sample.invokeExact(seed, x, y, z); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java new file mode 100644 index 0000000..cbb97f0 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/BindingsTemplate.java @@ -0,0 +1,407 @@ +package org.bxteam.divinemc.math; + +import net.minecraft.world.level.levelgen.synth.BlendedNoise; +import net.minecraft.world.level.levelgen.synth.ImprovedNoise; +import net.minecraft.world.level.levelgen.synth.PerlinNoise; +import org.jetbrains.annotations.NotNull; +import org.bxteam.divinemc.util.MemoryUtil; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.Objects; +import java.util.stream.IntStream; + +public class BindingsTemplate { + // double c2me_natives_noise_perlin_sample (const uint8_t *permutations, double originX, double originY, double originZ, double x, double y, double z, double yScale, double yMax) + public static final MethodHandle c2me_natives_noise_perlin_sample = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(true) + ); + + // c2me_natives_noise_perlin_double, double, (const double_octave_sampler_data_t *data, double x, double y, double z) + public static final MethodHandle c2me_natives_noise_perlin_double = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + public static final MethodHandle c2me_natives_noise_perlin_double_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + + // c2me_natives_noise_perlin_double_batch, void, (const double_octave_sampler_data_t *const data, + // double *const res, const double *const x, + // const double *const y, const double *const z, + // const uint32_t length) + public static final MethodHandle c2me_natives_noise_perlin_double_batch = NativeLoader.linker.downcallHandle( + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(true) + ); + public static final MethodHandle c2me_natives_noise_perlin_double_batch_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.ofVoid( + ValueLayout.JAVA_LONG, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(true) + ); + + + public static final StructLayout double_octave_sampler_data = MemoryLayout.structLayout( + ValueLayout.JAVA_LONG.withName("length"), + ValueLayout.JAVA_DOUBLE.withName("amplitude"), + ValueLayout.ADDRESS.withName("need_shift"), + ValueLayout.ADDRESS.withName("lacunarity_powd"), + ValueLayout.ADDRESS.withName("persistence_powd"), + ValueLayout.ADDRESS.withName("sampler_permutations"), + ValueLayout.ADDRESS.withName("sampler_originX"), + ValueLayout.ADDRESS.withName("sampler_originY"), + ValueLayout.ADDRESS.withName("sampler_originZ"), + ValueLayout.ADDRESS.withName("amplitudes") + ).withByteAlignment(32).withName("double_double_octave_sampler_data"); + public static final VarHandle double_octave_sampler_data$length = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("length")); + public static final VarHandle double_octave_sampler_data$amplitude = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("amplitude")); + public static final VarHandle double_octave_sampler_data$need_shift = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("need_shift")); + public static final VarHandle double_octave_sampler_data$lacunarity_powd = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("lacunarity_powd")); + public static final VarHandle double_octave_sampler_data$persistence_powd = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("persistence_powd")); + public static final VarHandle double_octave_sampler_data$sampler_permutations = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle double_octave_sampler_data$sampler_originX = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle double_octave_sampler_data$sampler_originY = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle double_octave_sampler_data$sampler_originZ = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle double_octave_sampler_data$amplitudes = double_octave_sampler_data.varHandle(MemoryLayout.PathElement.groupElement("amplitudes")); + + public static MemorySegment double_octave_sampler_data$create(Arena arena, @NotNull PerlinNoise firstSampler, PerlinNoise secondSampler, double amplitude) { + long nonNullSamplerCount = 0; + for (ImprovedNoise sampler : (firstSampler.noiseLevels)) { + if (sampler != null) { + nonNullSamplerCount++; + } + } + for (ImprovedNoise sampler : (secondSampler.noiseLevels)) { + if (sampler != null) { + nonNullSamplerCount++; + } + } + final MemorySegment data = arena.allocate(double_octave_sampler_data.byteSize(), 64); + final MemorySegment need_shift = arena.allocate(nonNullSamplerCount, 64); + final MemorySegment lacunarity_powd = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment persistence_powd = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment sampler_permutations = arena.allocate(nonNullSamplerCount * 256 * 4, 64); + final MemorySegment sampler_originX = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment sampler_originY = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment sampler_originZ = arena.allocate(nonNullSamplerCount * 8, 64); + final MemorySegment amplitudes = arena.allocate(nonNullSamplerCount * 8, 64); + double_octave_sampler_data$length.set(data, 0L, nonNullSamplerCount); + double_octave_sampler_data$amplitude.set(data, 0L, amplitude); + double_octave_sampler_data$need_shift.set(data, 0L, need_shift); + double_octave_sampler_data$lacunarity_powd.set(data, 0L, lacunarity_powd); + double_octave_sampler_data$persistence_powd.set(data, 0L, persistence_powd); + double_octave_sampler_data$sampler_permutations.set(data, 0L, sampler_permutations); + double_octave_sampler_data$sampler_originX.set(data, 0L, sampler_originX); + double_octave_sampler_data$sampler_originY.set(data, 0L, sampler_originY); + double_octave_sampler_data$sampler_originZ.set(data, 0L, sampler_originZ); + double_octave_sampler_data$amplitudes.set(data, 0L, amplitudes); + long index = 0; + { + ImprovedNoise[] octaveSamplers = (firstSampler.noiseLevels); + for (int i = 0, octaveSamplersLength = octaveSamplers.length; i < octaveSamplersLength; i++) { + ImprovedNoise sampler = octaveSamplers[i]; + if (sampler != null) { + need_shift.set(ValueLayout.JAVA_BOOLEAN, index, false); + lacunarity_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (firstSampler.lowestFreqInputFactor) * Math.pow(2.0, i)); + persistence_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (firstSampler.lowestFreqValueFactor) * Math.pow(2.0, -i)); + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.zo); + amplitudes.set(ValueLayout.JAVA_DOUBLE, index * 8, (firstSampler.amplitudes).getDouble(i)); + index++; + } + } + } + { + ImprovedNoise[] octaveSamplers = (secondSampler.noiseLevels); + for (int i = 0, octaveSamplersLength = octaveSamplers.length; i < octaveSamplersLength; i++) { + ImprovedNoise sampler = octaveSamplers[i]; + if (sampler != null) { + need_shift.set(ValueLayout.JAVA_BOOLEAN, index, true); + lacunarity_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (secondSampler.lowestFreqInputFactor) * Math.pow(2.0, i)); + persistence_powd.set(ValueLayout.JAVA_DOUBLE, index * 8, (secondSampler.lowestFreqValueFactor) * Math.pow(2.0, -i)); + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8, sampler.zo); + amplitudes.set(ValueLayout.JAVA_DOUBLE, index * 8, (secondSampler.amplitudes).getDouble(i)); + index++; + } + } + } + + VarHandle.fullFence(); + + return data; + } + + // c2me_natives_noise_interpolated, double, (const interpolated_noise_sampler_t *const data, const double x, const double y, const double z) + public static final MethodHandle c2me_natives_noise_interpolated = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + public static final MethodHandle c2me_natives_noise_interpolated_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE, + ValueLayout.JAVA_DOUBLE + ), + Linker.Option.critical(false) + ); + + // typedef const struct interpolated_noise_sub_sampler { + // const aligned_uint8_ptr sampler_permutations; + // const aligned_double_ptr sampler_originX; + // const aligned_double_ptr sampler_originY; + // const aligned_double_ptr sampler_originZ; + // const aligned_double_ptr sampler_mulFactor; + // const uint32_t length; + // } interpolated_noise_sub_sampler_t; + public static final StructLayout interpolated_noise_sub_sampler = MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("sampler_permutations"), + ValueLayout.ADDRESS.withName("sampler_originX"), + ValueLayout.ADDRESS.withName("sampler_originY"), + ValueLayout.ADDRESS.withName("sampler_originZ"), + ValueLayout.ADDRESS.withName("sampler_mulFactor"), + ValueLayout.JAVA_INT.withName("length"), + MemoryLayout.paddingLayout(4) + ).withName("interpolated_noise_sub_sampler_t"); + + // typedef const struct interpolated_noise_sampler { + // const double scaledXzScale; + // const double scaledYScale; + // const double xzFactor; + // const double yFactor; + // const double smearScaleMultiplier; + // const double xzScale; + // const double yScale; + // + // const interpolated_noise_sub_sampler_t lower; + // const interpolated_noise_sub_sampler_t upper; + // const interpolated_noise_sub_sampler_t normal; + // } interpolated_noise_sampler_t; + public static final StructLayout interpolated_noise_sampler = MemoryLayout.structLayout( + ValueLayout.JAVA_DOUBLE.withName("scaledXzScale"), + ValueLayout.JAVA_DOUBLE.withName("scaledYScale"), + ValueLayout.JAVA_DOUBLE.withName("xzFactor"), + ValueLayout.JAVA_DOUBLE.withName("yFactor"), + ValueLayout.JAVA_DOUBLE.withName("smearScaleMultiplier"), + ValueLayout.JAVA_DOUBLE.withName("xzScale"), + ValueLayout.JAVA_DOUBLE.withName("yScale"), + + interpolated_noise_sub_sampler.withName("lower"), + interpolated_noise_sub_sampler.withName("upper"), + interpolated_noise_sub_sampler.withName("normal") + ).withByteAlignment(32).withName("interpolated_noise_sampler_t"); + + public static final VarHandle interpolated_noise_sampler$scaledXzScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("scaledXzScale")); + public static final VarHandle interpolated_noise_sampler$scaledYScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("scaledYScale")); + public static final VarHandle interpolated_noise_sampler$xzFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("xzFactor")); + public static final VarHandle interpolated_noise_sampler$yFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("yFactor")); + public static final VarHandle interpolated_noise_sampler$smearScaleMultiplier = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("smearScaleMultiplier")); + public static final VarHandle interpolated_noise_sampler$xzScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("xzScale")); + public static final VarHandle interpolated_noise_sampler$yScale = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("yScale")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_permutations = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_originX = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_originY = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_originZ = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle interpolated_noise_sampler$lower$sampler_mulFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("sampler_mulFactor")); + public static final VarHandle interpolated_noise_sampler$lower$length = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("lower"), MemoryLayout.PathElement.groupElement("length")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_permutations = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_originX = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_originY = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_originZ = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle interpolated_noise_sampler$upper$sampler_mulFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("sampler_mulFactor")); + public static final VarHandle interpolated_noise_sampler$upper$length = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("upper"), MemoryLayout.PathElement.groupElement("length")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_permutations = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_permutations")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_originX = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_originX")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_originY = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_originY")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_originZ = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_originZ")); + public static final VarHandle interpolated_noise_sampler$normal$sampler_mulFactor = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("sampler_mulFactor")); + public static final VarHandle interpolated_noise_sampler$normal$length = interpolated_noise_sampler.varHandle(MemoryLayout.PathElement.groupElement("normal"), MemoryLayout.PathElement.groupElement("length")); + + public static boolean interpolated_noise_sampler$isSpecializedBase3dNoiseFunction(BlendedNoise interpolated) { + return IntStream.range(0, 16).mapToObj((interpolated).minLimitNoise::getOctaveNoise).filter(Objects::nonNull).count() == 16 && + IntStream.range(0, 16).mapToObj((interpolated).maxLimitNoise::getOctaveNoise).filter(Objects::nonNull).count() == 16 && + IntStream.range(0, 8).mapToObj((interpolated).mainNoise::getOctaveNoise).filter(Objects::nonNull).count() == 8; + } + + public static MemorySegment interpolated_noise_sampler$create(@NotNull Arena arena, BlendedNoise interpolated) { + final MemorySegment data = arena.allocate(interpolated_noise_sampler.byteSize(), 64); + interpolated_noise_sampler$scaledXzScale.set(data, 0L, (interpolated).xzMultiplier); + interpolated_noise_sampler$scaledYScale.set(data, 0L, (interpolated).yMultiplier); + interpolated_noise_sampler$xzFactor.set(data, 0L, (interpolated).xzFactor); + interpolated_noise_sampler$yFactor.set(data, 0L, (interpolated).yFactor); + interpolated_noise_sampler$smearScaleMultiplier.set(data, 0L, (interpolated).smearScaleMultiplier); + interpolated_noise_sampler$xzScale.set(data, 0L, (interpolated).xzScale); + interpolated_noise_sampler$yScale.set(data, 0L, (interpolated).yScale); + +// if (true) { +// System.out.println(String.format("Interpolated total: %d", countNonNull)); +// System.out.println(String.format("lower: %d", IntStream.range(0, 16).mapToObj(((IInterpolatedNoiseSampler) interpolated).getLowerInterpolatedNoise()::getOctave).filter(Objects::nonNull).count())); +// System.out.println(String.format("upper: %d", IntStream.range(0, 16).mapToObj(((IInterpolatedNoiseSampler) interpolated).getUpperInterpolatedNoise()::getOctave).filter(Objects::nonNull).count())); +// System.out.println(String.format("normal: %d", IntStream.range(0, 8).mapToObj(((IInterpolatedNoiseSampler) interpolated).getInterpolationNoise()::getOctave).filter(Objects::nonNull).count())); +// } + + final MemorySegment sampler_permutations = arena.allocate(40 * 256L * 4L, 64); + final MemorySegment sampler_originX = arena.allocate(40 * 8L, 64); + final MemorySegment sampler_originY = arena.allocate(40 * 8L, 64); + final MemorySegment sampler_originZ = arena.allocate(40 * 8L, 64); + final MemorySegment sampler_mulFactor = arena.allocate(40 * 8L, 64); + + int index = 0; + + { + int startIndex = index; + + for (int i = 0; i < 8; i++) { + ImprovedNoise sampler = (interpolated.mainNoise).getOctaveNoise(i); + if (sampler != null) { + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.zo); + sampler_mulFactor.set(ValueLayout.JAVA_DOUBLE, index * 8L, Math.pow(2, -i)); + index ++; + } + } + + BindingsTemplate.interpolated_noise_sampler$normal$sampler_permutations.set(data, 0L, sampler_permutations.asSlice(startIndex * 256L * 4L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_originX.set(data, 0L, sampler_originX.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_originY.set(data, 0L, sampler_originY.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_originZ.set(data, 0L, sampler_originZ.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$sampler_mulFactor.set(data, 0L, sampler_mulFactor.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$normal$length.set(data, 0L, index - startIndex); + } + + { + int startIndex = index = 8; + + for (int i = 0; i < 16; i++) { + ImprovedNoise sampler = (interpolated.minLimitNoise).getOctaveNoise(i); + if (sampler != null) { + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.zo); + sampler_mulFactor.set(ValueLayout.JAVA_DOUBLE, index * 8L, Math.pow(2, -i)); + index ++; + } + } + + BindingsTemplate.interpolated_noise_sampler$lower$sampler_permutations.set(data, 0L, sampler_permutations.asSlice(startIndex * 256L * 4L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_originX.set(data, 0L, sampler_originX.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_originY.set(data, 0L, sampler_originY.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_originZ.set(data, 0L, sampler_originZ.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$sampler_mulFactor.set(data, 0L, sampler_mulFactor.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$lower$length.set(data, 0L, index - startIndex); + } + + { + int startIndex = index = 8 + 16; + + for (int i = 0; i < 16; i++) { + ImprovedNoise sampler = (interpolated.maxLimitNoise).getOctaveNoise(i); + if (sampler != null) { + MemorySegment.copy(MemorySegment.ofArray(MemoryUtil.byte2int(sampler.p)), 0, sampler_permutations, index * 256L * 4L, 256 * 4); + sampler_originX.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.xo); + sampler_originY.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.yo); + sampler_originZ.set(ValueLayout.JAVA_DOUBLE, index * 8L, sampler.zo); + sampler_mulFactor.set(ValueLayout.JAVA_DOUBLE, index * 8L, Math.pow(2, -i)); + index ++; + } + } + + BindingsTemplate.interpolated_noise_sampler$upper$sampler_permutations.set(data, 0L, sampler_permutations.asSlice(startIndex * 256L * 4L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_originX.set(data, 0L, sampler_originX.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_originY.set(data, 0L, sampler_originY.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_originZ.set(data, 0L, sampler_originZ.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$sampler_mulFactor.set(data, 0L, sampler_mulFactor.asSlice(startIndex * 8L)); + BindingsTemplate.interpolated_noise_sampler$upper$length.set(data, 0L, index - startIndex); + } + + VarHandle.fullFence(); + + return data; + } + + // c2me_natives_end_islands_sample, float, (const int32_t *const simplex_permutations, const int32_t x, const int32_t z) + public static final MethodHandle c2me_natives_end_islands_sample = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_FLOAT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(true) + ); + public static final MethodHandle c2me_natives_end_islands_sample_ptr = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_FLOAT, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(false) + ); + + // c2me_natives_biome_access_sample, uint32_t, (const int64_t theSeed, const int32_t x, const int32_t y, const int32_t z) + public static final MethodHandle c2me_natives_biome_access_sample = NativeLoader.linker.downcallHandle( + FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ), + Linker.Option.critical(false) + ); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java new file mode 100644 index 0000000..8c8824d --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/ISATarget.java @@ -0,0 +1,20 @@ +package org.bxteam.divinemc.math; + +import org.bxteam.divinemc.math.isa.ISA_aarch64; +import org.bxteam.divinemc.math.isa.ISA_x86_64; + +public interface ISATarget { + int ordinal(); + + String getSuffix(); + + boolean isNativelySupported(); + + static Class> getInstance() { + return switch (NativeLoader.NORMALIZED_ARCH) { + case "x86_64" -> ISA_x86_64.class; + case "aarch_64" -> ISA_aarch64.class; + default -> null; + }; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java new file mode 100644 index 0000000..5c680d6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/NativeLoader.java @@ -0,0 +1,179 @@ +package org.bxteam.divinemc.math; + +import io.netty.util.internal.SystemPropertyUtil; +import org.bxteam.divinemc.DivineConfig; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.lang.foreign.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Locale; + +public class NativeLoader { + private static final Logger LOGGER = LoggerFactory.getLogger(NativeLoader.class); + + public static final String NORMALIZED_ARCH = normalizeArch(SystemPropertyUtil.get("os.arch", "")); + public static final String NORMALIZED_OS = normalizeOs(SystemPropertyUtil.get("os.name", "")); + + private static final Arena arena = Arena.ofAuto(); + public static final SymbolLookup lookup; + public static final Linker linker = Linker.nativeLinker(); + public static final ISATarget currentMachineTarget; + + static { + String libName = String.format("%s-%s-%s", NORMALIZED_OS, NORMALIZED_ARCH, System.mapLibraryName("c2me-opts-natives-math")); + lookup = load0(libName); + if (lookup == null) { + currentMachineTarget = null; + DivineConfig.nativeAccelerationEnabled = false; + LOGGER.warn("Disabling native math optimization due to unsupported platform."); + } else { + try { + LOGGER.info("Attempting to call native library. If your game crashes right after this point, native acceleration may not be available for your system."); + int level = (int) linker.downcallHandle( + lookup.find("c2me_natives_get_system_isa").get(), + FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.JAVA_BOOLEAN + ) + ).invokeExact(DivineConfig.allowAVX512); + ISATarget target; + if (DivineConfig.isaTargetLevelOverride != -1) { + target = (ISATarget) ISATarget.getInstance().getEnumConstants()[DivineConfig.isaTargetLevelOverride]; + } else { + target = (ISATarget) ISATarget.getInstance().getEnumConstants()[level]; + while (!target.isNativelySupported()) target = (ISATarget) ISATarget.getInstance().getEnumConstants()[target.ordinal() - 1]; + } + currentMachineTarget = target; + LOGGER.info("Detected maximum supported ISA target: {}", currentMachineTarget); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + + @Contract(pure = true) + public static @NotNull String getAvailabilityString() { + if (lookup != null) { + return String.format("Available, with ISA target %s", currentMachineTarget); + } else { + return "Unavailable"; + } + } + + private static @Nullable SymbolLookup load0(String libName) { + // load from resources + try (final InputStream in = NativeLoader.class.getClassLoader().getResourceAsStream(libName)) { + if (in == null) { + LOGGER.warn("Cannot find native library {}, possibly unsupported platform for native acceleration", libName); + return null; + } + final Path tempFile; + if (Boolean.getBoolean("vectorizedgen.preserveNative")) { + tempFile = Path.of(".", libName); + } else { + tempFile = Files.createTempFile(null, libName); + tempFile.toFile().deleteOnExit(); + } + Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING); + return SymbolLookup.libraryLookup(tempFile, arena); + } catch (Throwable e) { + LOGGER.warn("Failed to load native library", e); + return null; + } + } + + private static @NotNull String normalize(@NotNull String value) { + return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); + } + + private static @NotNull String normalizeArch(String value) { + value = normalize(value); + if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) { + return "x86_64"; + } + if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) { + return "x86_32"; + } + if (value.matches("^(ia64|itanium64)$")) { + return "itanium_64"; + } + if (value.matches("^(sparc|sparc32)$")) { + return "sparc_32"; + } + if (value.matches("^(sparcv9|sparc64)$")) { + return "sparc_64"; + } + if (value.matches("^(arm|arm32)$")) { + return "arm_32"; + } + if ("aarch64".equals(value)) { + return "aarch_64"; + } + if (value.matches("^(ppc|ppc32)$")) { + return "ppc_32"; + } + if ("ppc64".equals(value)) { + return "ppc_64"; + } + if ("ppc64le".equals(value)) { + return "ppcle_64"; + } + if ("s390".equals(value)) { + return "s390_32"; + } + if ("s390x".equals(value)) { + return "s390_64"; + } + if ("loongarch64".equals(value)) { + return "loongarch_64"; + } + + return "unknown"; + } + + private static @NotNull String normalizeOs(String value) { + value = normalize(value); + if (value.startsWith("aix")) { + return "aix"; + } + if (value.startsWith("hpux")) { + return "hpux"; + } + if (value.startsWith("os400")) { + // Avoid the names such as os4000 + if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) { + return "os400"; + } + } + if (value.startsWith("linux")) { + return "linux"; + } + if (value.startsWith("macosx") || value.startsWith("osx") || value.startsWith("darwin")) { + return "osx"; + } + if (value.startsWith("freebsd")) { + return "freebsd"; + } + if (value.startsWith("openbsd")) { + return "openbsd"; + } + if (value.startsWith("netbsd")) { + return "netbsd"; + } + if (value.startsWith("solaris") || value.startsWith("sunos")) { + return "sunos"; + } + if (value.startsWith("windows")) { + return "windows"; + } + + return "unknown"; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java new file mode 100644 index 0000000..7c17bfd --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_aarch64.java @@ -0,0 +1,25 @@ +package org.bxteam.divinemc.math.isa; + +import org.bxteam.divinemc.math.ISATarget; + +public enum ISA_aarch64 implements ISATarget { + GENERIC("_generic", true); + + private final String suffix; + private final boolean nativelySupported; + + ISA_aarch64(String suffix, boolean nativelySupported) { + this.suffix = suffix; + this.nativelySupported = nativelySupported; + } + + @Override + public String getSuffix() { + return this.suffix; + } + + @Override + public boolean isNativelySupported() { + return this.nativelySupported; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java new file mode 100644 index 0000000..817b317 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/math/isa/ISA_x86_64.java @@ -0,0 +1,34 @@ +package org.bxteam.divinemc.math.isa; + +import org.bxteam.divinemc.math.ISATarget; + +public enum ISA_x86_64 implements ISATarget { + SSE2("_sse2", true), // 0 + SSE4_1("_sse2", false), // 1, not implemented + SSE4_2("_sse4_2", true), // 2 + AVX("_avx", true), // 3 + AVX2("_avx2", true), // 4 + AVX2ADL("_avx2adl", true), // 5 + AVX512KNL("_avx2", false), // 6, not implemented + AVX512SKX("_avx512skx", true), // 7 + AVX512ICL("_avx512icl", true), // 8 + AVX512SPR("_avx512spr", true); // 9 + + private final String suffix; + private final boolean nativelySupported; + + ISA_x86_64(String suffix, boolean nativelySupported) { + this.suffix = suffix; + this.nativelySupported = nativelySupported; + } + + @Override + public String getSuffix() { + return this.suffix; + } + + @Override + public boolean isNativelySupported() { + return this.nativelySupported; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java new file mode 100644 index 0000000..7f5fc08 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/ServerLevelTickExecutorThreadFactory.java @@ -0,0 +1,27 @@ +package org.bxteam.divinemc.server; + +import ca.spottedleaf.moonrise.common.util.TickThread; +import java.util.concurrent.ThreadFactory; + +public class ServerLevelTickExecutorThreadFactory implements ThreadFactory { + private final String worldName; + + public ServerLevelTickExecutorThreadFactory(String worldName) { + this.worldName = worldName; + } + + @Override + public Thread newThread(Runnable runnable) { + TickThread.ServerLevelTickThread tickThread = new TickThread.ServerLevelTickThread(runnable, "serverlevel-tick-worker [" + worldName + "]"); + + if (tickThread.isDaemon()) { + tickThread.setDaemon(false); + } + + if (tickThread.getPriority() != 5) { + tickThread.setPriority(5); + } + + return tickThread; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java new file mode 100644 index 0000000..4692fb3 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemAlgorithms.java @@ -0,0 +1,116 @@ +package org.bxteam.divinemc.server.chunk; + +import ca.spottedleaf.moonrise.common.PlatformHooks; +import io.netty.util.internal.PlatformDependent; +import net.objecthunter.exp4j.ExpressionBuilder; +import net.objecthunter.exp4j.function.Function; +import org.jetbrains.annotations.NotNull; +import oshi.util.tuples.Pair; +import java.util.function.BiFunction; + +public enum ChunkSystemAlgorithms { + MOONRISE((configWorkerThreads, configIoThreads) -> { + int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; + if (defaultWorkerThreads <= 4) { + defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; + } else { + defaultWorkerThreads = defaultWorkerThreads / 2; + } + defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); + + int workerThreads = configWorkerThreads; + + if (workerThreads <= 0) { + workerThreads = defaultWorkerThreads; + } + final int ioThreads = Math.max(1, configIoThreads); + return new Pair<>(workerThreads, ioThreads); + }), + C2ME_AGGRESSIVE((configWorkerThreads, configIoThreads) -> { + String expression = """ + + max( + 1, + min( + if( is_windows, + (cpus / 1.6), + (cpus / 1.3) + ) - if(is_client, 1, 0), + ( ( mem_gb - (if(is_client, 1.0, 0.5)) ) / 0.6 ) + ) + ) + \040"""; + int eval = configWorkerThreads <= 0 ? tryEvaluateExpression(expression) : configWorkerThreads; + return new Pair<>(eval, Math.max(1, configIoThreads)); + }), + C2ME((configWorkerThreads, configIoThreads) -> { + String expression = """ + + max( + 1, + min( + if( is_windows, + (cpus / 1.6 - 2), + (cpus / 1.2 - 2) + ) - if(is_client, 2, 0), + if( is_j9vm, + ( ( mem_gb - (if(is_client, 0.6, 0.2)) ) / 0.4 ), + ( ( mem_gb - (if(is_client, 1.2, 0.6)) ) / 0.6 ) + ) + ) + ) + \040"""; + int eval = configWorkerThreads <= 0 ? tryEvaluateExpression(expression) : configWorkerThreads; + return new Pair<>(eval, Math.max(1, configIoThreads)); + }); + + private final BiFunction> eval; + + ChunkSystemAlgorithms(BiFunction> eval) { + this.eval = eval; + } + + private static int tryEvaluateExpression(String expression) { + return (int) Math.max(1, + new ExpressionBuilder(expression) + .variables("is_windows", "is_j9vm", "is_client", "cpus", "mem_gb") + .function(new Function("max", 2) { + @Override + public double apply(double... args) { + return Math.max(args[0], args[1]); + } + }) + .function(new Function("min", 2) { + @Override + public double apply(double... args) { + return Math.min(args[0], args[1]); + } + }) + .function(new Function("if", 3) { + @Override + public double apply(double... args) { + return args[0] != 0 ? args[1] : args[2]; + } + }) + .build() + .setVariable("is_windows", PlatformDependent.isWindows() ? 1 : 0) + .setVariable("is_j9vm", PlatformDependent.isJ9Jvm() ? 1 : 0) + .setVariable("is_client", 0) + .setVariable("cpus", Runtime.getRuntime().availableProcessors()) + .setVariable("mem_gb", Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0 / 1024.0) + .evaluate() + ); + } + + public int evalWorkers(final int configWorkerThreads, final int configIoThreads) { + return eval.apply(configWorkerThreads, configIoThreads).getA(); + } + + public int evalIO(final int configWorkerThreads, final int configIoThreads) { + return eval.apply(configWorkerThreads, configIoThreads).getB(); + } + + public @NotNull String asDebugString() { + return this + "(" + evalWorkers(-1, -1) + ")"; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java new file mode 100644 index 0000000..4ea68e0 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java @@ -0,0 +1,421 @@ +package org.bxteam.divinemc.util; + +import java.util.Collection; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; + +/** + * Assertion utility class that assists in validating arguments. + * + *

Useful for identifying programmer errors early and clearly at runtime. + * + *

For example, if the contract of a public method states it does not + * allow {@code null} arguments, {@code Assert} can be used to validate that + * contract. Doing this clearly indicates a contract violation when it + * occurs and protects the class's invariants. + * + *

Typically used to validate method arguments rather than configuration + * properties, to check for cases that are usually programmer errors rather + * than configuration errors. In contrast to configuration initialization + * code, there is usually no point in falling back to defaults in such methods. + * + *

This class is similar to JUnit's assertion library. If an argument value is + * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). + * For example: + * + *

+ * Assert.notNull(clazz, "The class must not be null");
+ * Assert.isTrue(i > 0, "The value must be greater than zero");
+ * + *

Mainly for internal use within the framework; for a more comprehensive suite + * of assertion utilities consider {@code org.apache.commons.lang3.Validate} from + * Apache Commons Lang, + * Google Guava's + * Preconditions, + * or similar third-party libraries. + * + * @author Keith Donald + * @author Juergen Hoeller + * @author Sam Brannen + * @author Colin Sampaleanu + * @author Rob Harrop + * @author Sebastien Deleuze + */ +public abstract class Assert { + /** + * Assert a boolean expression, throwing an {@code IllegalStateException} + * if the expression evaluates to {@code false}. + *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} + * on an assertion failure. + *

Assert.state(id == null, "The id property must not already be initialized");
+ * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalStateException if {@code expression} is {@code false} + */ + @Contract("false, _ -> fail") + public static void state(boolean expression, String message) { + if (!expression) { + throw new IllegalStateException(message); + } + } + + /** + * Assert a boolean expression, throwing an {@code IllegalStateException} + * if the expression evaluates to {@code false}. + *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} + * on an assertion failure. + *

+     * Assert.state(entity.getId() == null,
+     *     () -> "ID for entity " + entity.getName() + " must not already be initialized");
+     * 
+ * @param expression a boolean expression + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalStateException if {@code expression} is {@code false} + * @since 5.0 + */ + @Contract("false, _ -> fail") + public static void state(boolean expression, Supplier messageSupplier) { + if (!expression) { + throw new IllegalStateException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert a boolean expression, throwing an {@code IllegalArgumentException} + * if the expression evaluates to {@code false}. + *
Assert.isTrue(i > 0, "The value must be greater than zero");
+ * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if {@code expression} is {@code false} + */ + @Contract("false, _ -> fail") + public static void isTrue(boolean expression, String message) { + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert a boolean expression, throwing an {@code IllegalArgumentException} + * if the expression evaluates to {@code false}. + *
+     * Assert.isTrue(i > 0, () -> "The value '" + i + "' must be greater than zero");
+     * 
+ * @param expression a boolean expression + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if {@code expression} is {@code false} + * @since 5.0 + */ + @Contract("false, _ -> fail") + public static void isTrue(boolean expression, Supplier messageSupplier) { + if (!expression) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that an object is {@code null}. + *
Assert.isNull(value, "The value must be null");
+ * @param object the object to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object is not {@code null} + */ + @Contract("!null, _ -> fail") + public static void isNull(@Nullable Object object, String message) { + if (object != null) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that an object is {@code null}. + *
+     * Assert.isNull(value, () -> "The value '" + value + "' must be null");
+     * 
+ * @param object the object to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the object is not {@code null} + * @since 5.0 + */ + @Contract("!null, _ -> fail") + public static void isNull(@Nullable Object object, Supplier messageSupplier) { + if (object != null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that an object is not {@code null}. + *
Assert.notNull(clazz, "The class must not be null");
+ * @param object the object to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object is {@code null} + */ + @Contract("null, _ -> fail") + public static void notNull(@Nullable Object object, String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that an object is not {@code null}. + *
+     * Assert.notNull(entity.getId(),
+     *     () -> "ID for entity " + entity.getName() + " must not be null");
+     * 
+ * @param object the object to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the object is {@code null} + * @since 5.0 + */ + @Contract("null, _ -> fail") + public static void notNull(@Nullable Object object, Supplier messageSupplier) { + if (object == null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that an array contains no {@code null} elements. + *

Note: Does not complain if the array is empty! + *

Assert.noNullElements(array, "The array must contain non-null elements");
+ * @param array the array to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object array contains a {@code null} element + */ + public static void noNullElements(Object @Nullable [] array, String message) { + if (array != null) { + for (Object element : array) { + if (element == null) { + throw new IllegalArgumentException(message); + } + } + } + } + + /** + * Assert that an array contains no {@code null} elements. + *

Note: Does not complain if the array is empty! + *

+     * Assert.noNullElements(array, () -> "The " + arrayType + " array must contain non-null elements");
+     * 
+ * @param array the array to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the object array contains a {@code null} element + * @since 5.0 + */ + public static void noNullElements(Object @Nullable [] array, Supplier messageSupplier) { + if (array != null) { + for (Object element : array) { + if (element == null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + } + } + + /** + * Assert that a collection contains no {@code null} elements. + *

Note: Does not complain if the collection is empty! + *

Assert.noNullElements(collection, "Collection must contain non-null elements");
+ * @param collection the collection to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the collection contains a {@code null} element + * @since 5.2 + */ + public static void noNullElements(@Nullable Collection collection, String message) { + if (collection != null) { + for (Object element : collection) { + if (element == null) { + throw new IllegalArgumentException(message); + } + } + } + } + + /** + * Assert that a collection contains no {@code null} elements. + *

Note: Does not complain if the collection is empty! + *

+     * Assert.noNullElements(collection, () -> "Collection " + collectionName + " must contain non-null elements");
+     * 
+ * @param collection the collection to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails + * @throws IllegalArgumentException if the collection contains a {@code null} element + * @since 5.2 + */ + public static void noNullElements(@Nullable Collection collection, Supplier messageSupplier) { + if (collection != null) { + for (Object element : collection) { + if (element == null) { + throw new IllegalArgumentException(nullSafeGet(messageSupplier)); + } + } + } + } + + /** + * Assert that the provided object is an instance of the provided class. + *
Assert.instanceOf(Foo.class, foo, "Foo expected");
+ * @param type the type to check against + * @param obj the object to check + * @param message a message which will be prepended to provide further context. + * If it is empty or ends in ":" or ";" or "," or ".", a full exception message + * will be appended. If it ends in a space, the name of the offending object's + * type will be appended. In any other case, a ":" with a space and the name + * of the offending object's type will be appended. + * @throws IllegalArgumentException if the object is not an instance of type + */ + @Contract("_, null, _ -> fail") + public static void isInstanceOf(Class type, @Nullable Object obj, String message) { + notNull(type, "Type to check against must not be null"); + if (!type.isInstance(obj)) { + instanceCheckFailed(type, obj, message); + } + } + + /** + * Assert that the provided object is an instance of the provided class. + *
+     * Assert.instanceOf(Foo.class, foo, () -> "Processing " + Foo.class.getSimpleName() + ":");
+     * 
+ * @param type the type to check against + * @param obj the object to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails. See {@link #isInstanceOf(Class, Object, String)} for details. + * @throws IllegalArgumentException if the object is not an instance of type + * @since 5.0 + */ + @Contract("_, null, _ -> fail") + public static void isInstanceOf(Class type, @Nullable Object obj, Supplier messageSupplier) { + notNull(type, "Type to check against must not be null"); + if (!type.isInstance(obj)) { + instanceCheckFailed(type, obj, nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that the provided object is an instance of the provided class. + *
Assert.instanceOf(Foo.class, foo);
+ * @param type the type to check against + * @param obj the object to check + * @throws IllegalArgumentException if the object is not an instance of type + */ + @Contract("_, null -> fail") + public static void isInstanceOf(Class type, @Nullable Object obj) { + isInstanceOf(type, obj, ""); + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
Assert.isAssignable(Number.class, myClass, "Number expected");
+ * @param superType the supertype to check against + * @param subType the subtype to check + * @param message a message which will be prepended to provide further context. + * If it is empty or ends in ":" or ";" or "," or ".", a full exception message + * will be appended. If it ends in a space, the name of the offending subtype + * will be appended. In any other case, a ":" with a space and the name of the + * offending subtype will be appended. + * @throws IllegalArgumentException if the classes are not assignable + */ + @Contract("_, null, _ -> fail") + public static void isAssignable(Class superType, @Nullable Class subType, String message) { + notNull(superType, "Supertype to check against must not be null"); + if (subType == null || !superType.isAssignableFrom(subType)) { + assignableCheckFailed(superType, subType, message); + } + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
+     * Assert.isAssignable(Number.class, myClass, () -> "Processing " + myAttributeName + ":");
+     * 
+ * @param superType the supertype to check against + * @param subType the subtype to check + * @param messageSupplier a supplier for the exception message to use if the + * assertion fails. See {@link #isAssignable(Class, Class, String)} for details. + * @throws IllegalArgumentException if the classes are not assignable + * @since 5.0 + */ + @Contract("_, null, _ -> fail") + public static void isAssignable(Class superType, @Nullable Class subType, Supplier messageSupplier) { + notNull(superType, "Supertype to check against must not be null"); + if (subType == null || !superType.isAssignableFrom(subType)) { + assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier)); + } + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
Assert.isAssignable(Number.class, myClass);
+ * @param superType the supertype to check + * @param subType the subtype to check + * @throws IllegalArgumentException if the classes are not assignable + */ + @Contract("_, null -> fail") + public static void isAssignable(Class superType, @Nullable Class subType) { + isAssignable(superType, subType, ""); + } + + + private static void instanceCheckFailed(Class type, @Nullable Object obj, @Nullable String msg) { + String className = (obj != null ? obj.getClass().getName() : "null"); + String result = ""; + boolean defaultMessage = true; + if (StringUtil.hasLength(msg)) { + if (endsWithSeparator(msg)) { + result = msg + " "; + } + else { + result = messageWithTypeName(msg, className); + defaultMessage = false; + } + } + if (defaultMessage) { + result = result + ("Object of class [" + className + "] must be an instance of " + type); + } + throw new IllegalArgumentException(result); + } + + private static void assignableCheckFailed(Class superType, @Nullable Class subType, @Nullable String msg) { + String result = ""; + boolean defaultMessage = true; + if (StringUtil.hasLength(msg)) { + if (endsWithSeparator(msg)) { + result = msg + " "; + } + else { + result = messageWithTypeName(msg, subType); + defaultMessage = false; + } + } + if (defaultMessage) { + result = result + (subType + " is not assignable to " + superType); + } + throw new IllegalArgumentException(result); + } + + private static boolean endsWithSeparator(@NotNull String msg) { + return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith(".")); + } + + @Contract(pure = true) + private static @NotNull String messageWithTypeName(@NotNull String msg, @Nullable Object typeName) { + return msg + (msg.endsWith(" ") ? "" : ": ") + typeName; + } + + private static @Nullable String nullSafeGet(@Nullable Supplier messageSupplier) { + return (messageSupplier != null ? messageSupplier.get() : null); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java new file mode 100644 index 0000000..98975c5 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java @@ -0,0 +1,27 @@ +package org.bxteam.divinemc.util; + +public final class Assertions { + public static void assertTrue(boolean value, String message) { + if (!value) { + final AssertionError error = new AssertionError(message); + error.printStackTrace(); + throw error; + } + } + + public static void assertTrue(boolean state, String format, Object... args) { + if (!state) { + final AssertionError error = new AssertionError(String.format(format, args)); + error.printStackTrace(); + throw error; + } + } + + public static void assertTrue(boolean value) { + if (!value) { + final AssertionError error = new AssertionError(); + error.printStackTrace(); + throw error; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java new file mode 100644 index 0000000..328f27d --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/BlockEntityTickersList.java @@ -0,0 +1,98 @@ +package org.bxteam.divinemc.util; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.world.level.block.entity.TickingBlockEntity; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A list for ServerLevel's blockEntityTickers + *

+ * This list is behaving identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what + * indexes should be deleted from the list + *

+ * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove, + * since we don't need to resize the array every single remove. + */ +public final class BlockEntityTickersList extends ObjectArrayList { + private final IntOpenHashSet toRemove = new IntOpenHashSet(); + private int startSearchFromIndex = -1; + + /** Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. */ + public BlockEntityTickersList() { + super(); + } + + /** + * Creates a new array list and fills it with a given collection. + * + * @param c a collection that will be used to fill the array list. + */ + public BlockEntityTickersList(final Collection c) { + super(c); + } + + /** + * Marks an entry as removed + * + * @param index the index of the item on the list to be marked as removed + */ + public void markAsRemoved(final int index) { + // The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it + if (this.startSearchFromIndex == -1) + this.startSearchFromIndex = index; + this.toRemove.add(index); + } + + /** + * Removes elements that have been marked as removed. + */ + public void removeMarkedEntries() { + if (this.startSearchFromIndex == -1) // No entries in the list, skip + return; + + removeAllByIndex(startSearchFromIndex, toRemove); + toRemove.clear(); + this.startSearchFromIndex = -1; // Reset the start search index + } + + /** + * Removes elements by their index. + */ + private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set because we want to avoid autoboxing when using contains + final int requiredMatches = c.size(); + if (requiredMatches == 0) + return; // exit early, we don't need to do anything + + final Object[] a = this.a; + int j = startSearchFromIndex; + int matches = 0; + for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions + if (!c.contains(i)) { + // TODO: It can be possible to optimize this loop by tracking the start/finish and then using arraycopy to "skip" the elements, + // this would optimize cases where the index to be removed are far apart, HOWEVER it does have a big performance impact if you are doing + // "arraycopy" for each element + a[j++] = a[i]; + } else { + matches++; + } + + if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else + // We need to update the final size here, because we know that we already found everything! + // Because we know that the size must be currentSize - requiredMatches (because we have matched everything), let's update the value + // However, we need to copy the rest of the stuff over + if (i != (size - 1)) { // If it isn't the last index... + // i + 1 because we want to copy the *next* element over + // and the size - i - 1 is because we want to get the current size, minus the current index (which is i), and then - 1 because we want to copy -1 ahead (remember, we are adding +1 to copy the *next* element) + System.arraycopy(a, i + 1, a, j, size - i - 1); + } + j = size - requiredMatches; + break; + } + } + Arrays.fill(a, j, size, null); + size = j; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java new file mode 100644 index 0000000..7d02eb2 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java @@ -0,0 +1,35 @@ +package org.bxteam.divinemc.util; + +import java.io.File; +import java.io.IOException; + +public final class Files { + public static void deleteRecursively(File dir) throws IOException { + if (dir == null || !dir.isDirectory()) { + return; + } + + try { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("Error enumerating directory during recursive delete operation: " + dir.getAbsolutePath()); + } + + for (File child : files) { + if (child.isDirectory()) { + Files.deleteRecursively(child); + } else if (child.isFile()) { + if (!child.delete()) { + throw new IOException("Error deleting file during recursive delete operation: " + child.getAbsolutePath()); + } + } + } + + if (!dir.delete()) { + throw new IOException("Error deleting directory during recursive delete operation: " + dir.getAbsolutePath()); + } + } catch (SecurityException ex) { + throw new IOException("Security error during recursive delete operation", ex); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java new file mode 100644 index 0000000..1c84493 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java @@ -0,0 +1,12 @@ +package org.bxteam.divinemc.util; + +public final class MemoryUtil { + public static int[] byte2int(byte[] data) { + if (data == null) return null; + int[] ints = new int[data.length]; + for (int i = 0; i < data.length; i++) { + ints[i] = data[i] & 0xff; + } + return ints; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java new file mode 100644 index 0000000..358a393 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java @@ -0,0 +1,108 @@ +package org.bxteam.divinemc.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import java.util.Arrays; + +public class ObjectUtil { + @Contract("null, null -> true; null, _ -> false; _, null -> false") + public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (o1.equals(o2)) { + return true; + } + if (o1.getClass().isArray() && o2.getClass().isArray()) { + return arrayEquals(o1, o2); + } + return false; + } + + /** + * Compare the given arrays with {@code Arrays.equals}, performing an equality + * check based on the array elements rather than the array reference. + * @param o1 first array to compare + * @param o2 second array to compare + * @return whether the given objects are equal + * @see #nullSafeEquals(Object, Object) + * @see java.util.Arrays#equals + */ + private static boolean arrayEquals(Object o1, Object o2) { + if (o1 instanceof Object[] objects1 && o2 instanceof Object[] objects2) { + return Arrays.equals(objects1, objects2); + } + if (o1 instanceof boolean[] booleans1 && o2 instanceof boolean[] booleans2) { + return Arrays.equals(booleans1, booleans2); + } + if (o1 instanceof byte[] bytes1 && o2 instanceof byte[] bytes2) { + return Arrays.equals(bytes1, bytes2); + } + if (o1 instanceof char[] chars1 && o2 instanceof char[] chars2) { + return Arrays.equals(chars1, chars2); + } + if (o1 instanceof double[] doubles1 && o2 instanceof double[] doubles2) { + return Arrays.equals(doubles1, doubles2); + } + if (o1 instanceof float[] floats1 && o2 instanceof float[] floats2) { + return Arrays.equals(floats1, floats2); + } + if (o1 instanceof int[] ints1 && o2 instanceof int[] ints2) { + return Arrays.equals(ints1, ints2); + } + if (o1 instanceof long[] longs1 && o2 instanceof long[] longs2) { + return Arrays.equals(longs1, longs2); + } + if (o1 instanceof short[] shorts1 && o2 instanceof short[] shorts2) { + return Arrays.equals(shorts1, shorts2); + } + return false; + } + + /** + * Return a hash code for the given object; typically the value of + * {@code Object#hashCode()}}. If the object is an array, + * this method will delegate to any of the {@code Arrays.hashCode} + * methods. If the object is {@code null}, this method returns 0. + * @see Object#hashCode() + * @see Arrays + */ + public static int nullSafeHashCode(@Nullable Object obj) { + if (obj == null) { + return 0; + } + if (obj.getClass().isArray()) { + if (obj instanceof Object[] objects) { + return Arrays.hashCode(objects); + } + if (obj instanceof boolean[] booleans) { + return Arrays.hashCode(booleans); + } + if (obj instanceof byte[] bytes) { + return Arrays.hashCode(bytes); + } + if (obj instanceof char[] chars) { + return Arrays.hashCode(chars); + } + if (obj instanceof double[] doubles) { + return Arrays.hashCode(doubles); + } + if (obj instanceof float[] floats) { + return Arrays.hashCode(floats); + } + if (obj instanceof int[] ints) { + return Arrays.hashCode(ints); + } + if (obj instanceof long[] longs) { + return Arrays.hashCode(longs); + } + if (obj instanceof short[] shorts) { + return Arrays.hashCode(shorts); + } + } + return obj.hashCode(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java new file mode 100644 index 0000000..5f47188 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/RandomUtil.java @@ -0,0 +1,40 @@ +package org.bxteam.divinemc.util; + +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; +import net.minecraft.world.level.levelgen.SingleThreadedRandomSource; +import net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus; +import net.minecraft.world.level.levelgen.XoroshiroRandomSource; +import org.jetbrains.annotations.NotNull; + +public final class RandomUtil { + public static @NotNull RandomSource getRandom(PositionalRandomFactory deriver) { + if (deriver instanceof XoroshiroRandomSource.XoroshiroPositionalRandomFactory) { + return new XoroshiroRandomSource(0L, 0L); + } + if (deriver instanceof LegacyRandomSource.LegacyPositionalRandomFactory) { + return new SingleThreadedRandomSource(0L); + } + throw new IllegalArgumentException(); + } + + private static final ThreadLocal xoroshiro = ThreadLocal.withInitial(() -> new XoroshiroRandomSource(0L, 0L)); + private static final ThreadLocal simple = ThreadLocal.withInitial(() -> new SingleThreadedRandomSource(0L)); + + public static void derive(PositionalRandomFactory deriver, RandomSource random, int x, int y, int z) { + if (deriver instanceof final XoroshiroRandomSource.XoroshiroPositionalRandomFactory deriver1) { + final Xoroshiro128PlusPlus implementation = ((XoroshiroRandomSource) random).randomNumberGenerator; + implementation.seedLo = (Mth.getSeed(x, y, z) ^ deriver1.seedLo()); + implementation.seedHi = (deriver1.seedHi()); + return; + } + if (deriver instanceof LegacyRandomSource.LegacyPositionalRandomFactory(long seed)) { + final SingleThreadedRandomSource random1 = (SingleThreadedRandomSource) random; + random1.setSeed(Mth.getSeed(x, y, z) ^ seed); + return; + } + throw new IllegalArgumentException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java new file mode 100644 index 0000000..1d1ba14 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java @@ -0,0 +1,9 @@ +package org.bxteam.divinemc.util; + +import org.jetbrains.annotations.Nullable; + +public final class StringUtil { + public static boolean hasLength(@Nullable String str) { + return (str != null && !str.isEmpty()); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java new file mode 100644 index 0000000..d06a9b1 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/LongJumpChoiceList.java @@ -0,0 +1,217 @@ +package org.bxteam.divinemc.util.collections; + +import it.unimi.dsi.fastutil.bytes.ByteBytePair; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.ai.behavior.LongJumpToRandomPos; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +public class LongJumpChoiceList extends AbstractList { + /** + * A cache of choice lists for different ranges. The elements must not be mutated, but copied instead. + * In vanilla minecraft there should be two elements, one for frog jumps and one for goat jumps. + */ + private static final ConcurrentHashMap CHOICE_LISTS = new ConcurrentHashMap<>(); + /** + * The choice list for frog jumps. Skipping the hash map access. Must not be mutated, but copied instead. + */ + private static final LongJumpChoiceList FROG_JUMP = new LongJumpChoiceList((byte) 4, (byte) 2); + /** + * The choice list for goat jumps. Skipping the hash map access. Must not be mutated, but copied instead. + */ + private static final LongJumpChoiceList GOAT_JUMP = new LongJumpChoiceList((byte) 5, (byte) 5); + + + private final BlockPos origin; + private final IntArrayList[] packedOffsetsByDistanceSq; + private final int[] weightByDistanceSq; + private int totalWeight; + + /** + * Constructs a new LongJumpChoiceList with the given horizontal and vertical range. + * We avoid creating too many objects here, e.g. LongJumpTask.Target is not created yet. + * @param horizontalRange the horizontal range + * @param verticalRange the vertical range + */ + public LongJumpChoiceList(byte horizontalRange, byte verticalRange) { + if (horizontalRange < 0 || verticalRange < 0) { + throw new IllegalArgumentException("The ranges must be within 0..127!"); + } + + this.origin = BlockPos.ZERO; + int maxSqDistance = horizontalRange*horizontalRange * 2 + verticalRange*verticalRange; + this.packedOffsetsByDistanceSq = new IntArrayList[maxSqDistance]; + this.weightByDistanceSq = new int[maxSqDistance]; + + for (int x = -horizontalRange; x <= horizontalRange; x++) { + for (int y = -verticalRange; y <= verticalRange; y++) { + for (int z = -horizontalRange; z <= horizontalRange; z++) { + int squaredDistance = x * x + y * y + z * z; + int index = squaredDistance - 1; + if (index >= 0) { //exclude origin (distance 0) + int packedOffset = this.packOffset(x, y, z); + IntArrayList offsets = this.packedOffsetsByDistanceSq[index]; + if (offsets == null) { + this.packedOffsetsByDistanceSq[index] = offsets = new IntArrayList(); + } + offsets.add(packedOffset); + this.weightByDistanceSq[index] += squaredDistance; + this.totalWeight += squaredDistance; + } + } + } + } + } + + public LongJumpChoiceList(BlockPos origin, IntArrayList[] packedOffsetsByDistanceSq, int[] weightByDistanceSq, int totalWeight) { + this.origin = origin; + this.packedOffsetsByDistanceSq = packedOffsetsByDistanceSq; + this.weightByDistanceSq = weightByDistanceSq; + this.totalWeight = totalWeight; + } + + private int packOffset(int x, int y, int z) { + return (x + 128) | ((y + 128) << 8) | ((z + 128) << 16); + } + + private int unpackX(int packedOffset) { + return (packedOffset & 0xFF) - 128; + } + + private int unpackY(int packedOffset) { + return ((packedOffset >>> 8) & 0xFF) - 128; + } + + private int unpackZ(int packedOffset) { + return ((packedOffset >>> 16) & 0xFF) - 128; + } + + /** + * Returns a LongJumpChoiceList for the given center position and ranges. + * Quickly creates the list by copying an existing, memoized list. + * @param centerPos the center position + * @param horizontalRange the horizontal range + * @param verticalRange the vertical range + * @return a LongJumpChoiceList for the given parameters + */ + public static LongJumpChoiceList forCenter(BlockPos centerPos, byte horizontalRange, byte verticalRange) { + if (horizontalRange < 0 || verticalRange < 0) { + throw new IllegalArgumentException("The ranges must be within 0..127!"); + } + + LongJumpChoiceList jumpDestinationsList; + short range = (short) ((horizontalRange << 8) | verticalRange); + if (range == ((4 << 8) | 2)) { + //Frog jump + jumpDestinationsList = LongJumpChoiceList.FROG_JUMP; + } else if (range == ((5 << 8) | 5)) { + //Goat jump + jumpDestinationsList = LongJumpChoiceList.GOAT_JUMP; + } else { + jumpDestinationsList = LongJumpChoiceList.CHOICE_LISTS.computeIfAbsent( + ByteBytePair.of(horizontalRange, verticalRange), + key -> new LongJumpChoiceList(key.leftByte(), key.rightByte()) + ); + } + + return jumpDestinationsList.offsetCopy(centerPos); + } + + private LongJumpChoiceList offsetCopy(BlockPos offset) { + IntArrayList[] packedOffsetsByDistanceSq = new IntArrayList[this.packedOffsetsByDistanceSq.length]; + for (int i = 0; i < packedOffsetsByDistanceSq.length; i++) { + IntArrayList packedOffsets = this.packedOffsetsByDistanceSq[i]; + if (packedOffsets != null) { + packedOffsetsByDistanceSq[i] = packedOffsets.clone(); + } + } + + return new LongJumpChoiceList( + this.origin.offset(offset), + packedOffsetsByDistanceSq, + Arrays.copyOf(this.weightByDistanceSq, this.weightByDistanceSq.length), this.totalWeight); + } + + /** + * Removes and returns a random target from the list, weighted by squared distance. + * @param random the random number generator + * @return a random target + */ + public LongJumpToRandomPos.PossibleJump removeRandomWeightedByDistanceSq(RandomSource random) { + int targetWeight = random.nextInt(this.totalWeight); + for (int index = 0; targetWeight >= 0 && index < this.weightByDistanceSq.length; index++) { + targetWeight -= this.weightByDistanceSq[index]; + if (targetWeight < 0) { + int distanceSq = index + 1; + IntArrayList elementsOfDistance = this.packedOffsetsByDistanceSq[index]; + int elementIndex = random.nextInt(elementsOfDistance.size()); + + //fast remove by swapping to end and removing, order does not matter + elementsOfDistance.set(elementIndex, elementsOfDistance.set(elementsOfDistance.size() - 1, elementsOfDistance.getInt(elementIndex))); + int packedOffset = elementsOfDistance.removeInt(elementsOfDistance.size() - 1); + this.weightByDistanceSq[index] -= distanceSq; + this.totalWeight -= distanceSq; + + return new LongJumpToRandomPos.PossibleJump(this.origin.offset(this.unpackX(packedOffset), this.unpackY(packedOffset), this.unpackZ(packedOffset)), distanceSq); + } + } + return null; + } + + @Override + public LongJumpToRandomPos.PossibleJump get(int index) { + int elementIndex = index; + IntArrayList[] offsetsByDistanceSq = this.packedOffsetsByDistanceSq; + for (int distanceSq = 0; distanceSq < offsetsByDistanceSq.length; distanceSq++) { + IntArrayList packedOffsets = offsetsByDistanceSq[distanceSq]; + if (packedOffsets != null) { + if (elementIndex < packedOffsets.size()) { + int packedOffset = packedOffsets.getInt(elementIndex); + return new LongJumpToRandomPos.PossibleJump(this.origin.offset(this.unpackX(packedOffset), this.unpackY(packedOffset), this.unpackZ(packedOffset)), distanceSq); + } + elementIndex -= packedOffsets.size(); + } + } + throw new IndexOutOfBoundsException(); + } + + @Override + public boolean isEmpty() { + return this.totalWeight == 0; + } + + @Override + public int size() { + int size = 0; + for (IntArrayList packedOffsets : this.packedOffsetsByDistanceSq) { + if (packedOffsets != null) { + size += packedOffsets.size(); + } + } + return size; + } + + @Override + public LongJumpToRandomPos.PossibleJump remove(int index) { + int elementIndex = index; + IntArrayList[] offsetsByDistanceSq = this.packedOffsetsByDistanceSq; + for (int distanceSq = 0; distanceSq < offsetsByDistanceSq.length; distanceSq++) { + IntArrayList packedOffsets = offsetsByDistanceSq[distanceSq]; + if (packedOffsets != null) { + if (elementIndex < packedOffsets.size()) { + int packedOffset = packedOffsets.getInt(elementIndex); + packedOffsets.set(elementIndex, packedOffsets.set(packedOffsets.size() - 1, packedOffsets.getInt(elementIndex))); + packedOffsets.removeInt(packedOffsets.size() - 1); + this.weightByDistanceSq[distanceSq] -= distanceSq; + this.totalWeight -= distanceSq; + return new LongJumpToRandomPos.PossibleJump(this.origin.offset(this.unpackX(packedOffset), this.unpackY(packedOffset), this.unpackZ(packedOffset)), distanceSq); + } + elementIndex -= packedOffsets.size(); + } + } + throw new IndexOutOfBoundsException(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java new file mode 100644 index 0000000..e60f8d6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/collections/MaskedList.java @@ -0,0 +1,150 @@ +package org.bxteam.divinemc.util.collections; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.AbstractList; +import java.util.BitSet; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; + +public class MaskedList extends AbstractList { + private final ObjectArrayList allElements; + private final BitSet visibleMask; + private final Object2IntOpenHashMap element2Index; + private final boolean defaultVisibility; + private int numCleared; + + public MaskedList(ObjectArrayList allElements, boolean defaultVisibility) { + this.allElements = new ObjectArrayList<>(); + this.visibleMask = new BitSet(); + this.defaultVisibility = defaultVisibility; + this.element2Index = new Object2IntOpenHashMap<>(); + this.element2Index.defaultReturnValue(-1); + + this.addAll(allElements); + } + + public MaskedList() { + this(new ObjectArrayList<>(), true); + } + + public int totalSize() { + return this.allElements.size(); + } + + public void addOrSet(E element, boolean visible) { + int index = this.element2Index.getInt(element); + if (index != -1) { + this.visibleMask.set(index, visible); + } else { + this.add(element); + this.setVisible(element, visible); + } + } + + public void setVisible(E element, final boolean visible) { + int index = this.element2Index.getInt(element); + if (index != -1) { + this.visibleMask.set(index, visible); + } + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + int nextIndex = 0; + int cachedNext = -1; + + @Override + public boolean hasNext() { + return (this.cachedNext = MaskedList.this.visibleMask.nextSetBit(this.nextIndex)) != -1; + } + + @Override + public E next() { + int index = this.cachedNext; + this.cachedNext = -1; + this.nextIndex = index + 1; + return MaskedList.this.allElements.get(index); + } + }; + } + + @Override + public Spliterator spliterator() { + return new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) { + int nextIndex = 0; + + @Override + public boolean tryAdvance(Consumer action) { + int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex); + if (index == -1) { + return false; + } + this.nextIndex = index + 1; + action.accept(MaskedList.this.allElements.get(index)); + return true; + } + }; + } + + @Override + public boolean add(E e) { + int oldIndex = this.element2Index.put(e, this.allElements.size()); + if (oldIndex != -1) { + throw new IllegalStateException("MaskedList must not contain duplicates! Trying to add " + e + " but it is already present at index " + oldIndex + ". Current size: " + this.allElements.size()); + } + this.visibleMask.set(this.allElements.size(), this.defaultVisibility); + return this.allElements.add(e); + } + + @Override + public boolean remove(Object o) { + int index = this.element2Index.removeInt(o); + if (index == -1) { + return false; + } + this.visibleMask.clear(index); + this.allElements.set(index, null); + this.numCleared++; + + + if (this.numCleared * 2 > this.allElements.size()) { + ObjectArrayList clonedElements = this.allElements.clone(); + BitSet clonedVisibleMask = (BitSet) this.visibleMask.clone(); + this.allElements.clear(); + this.visibleMask.clear(); + this.element2Index.clear(); + for (int i = 0; i < clonedElements.size(); i++) { + E element = clonedElements.get(i); + int newIndex = this.allElements.size(); + this.allElements.add(element); + this.visibleMask.set(newIndex, clonedVisibleMask.get(i)); + this.element2Index.put(element, newIndex); + } + this.numCleared = 0; + } + return true; + } + + @Override + public E get(int index) { + if (index < 0 || index >= this.size()) { + throw new IndexOutOfBoundsException(index); + } + + int i = 0; + while (index >= 0) { + index--; + i = this.visibleMask.nextSetBit(i + 1); + } + return this.allElements.get(i); + } + + @Override + public int size() { + return this.visibleMask.cardinality(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java new file mode 100644 index 0000000..d8636f4 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/entity/SensorHelper.java @@ -0,0 +1,53 @@ +package org.bxteam.divinemc.util.entity; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.Brain; +import net.minecraft.world.entity.ai.sensing.Sensor; +import net.minecraft.world.entity.ai.sensing.SensorType; + +public class SensorHelper { + public static void disableSensor(LivingEntity brainedEntity, SensorType sensorType) { + if (brainedEntity.level().isClientSide()) { + return; + } + Brain brain = brainedEntity.getBrain(); + Sensor sensor = (brain).sensors.get(sensorType); + if (sensor != null) { + long lastSenseTime = sensor.timeToTick; + int senseInterval = sensor.scanRate; + + long maxMultipleOfSenseInterval = Long.MAX_VALUE - (Long.MAX_VALUE % senseInterval); + maxMultipleOfSenseInterval -= senseInterval; + maxMultipleOfSenseInterval += lastSenseTime; + + sensor.timeToTick = (maxMultipleOfSenseInterval); + } + } + + public static > void enableSensor(T brainedEntity, SensorType sensorType) { + enableSensor(brainedEntity, sensorType, false); + } + + public static > void enableSensor(T brainedEntity, SensorType sensorType, boolean extraTick) { + if (brainedEntity.level().isClientSide()) { + return; + } + + Brain brain = brainedEntity.getBrain(); + U sensor = (U) (brain).sensors.get(sensorType); + if (sensor != null) { + long lastSenseTime = sensor.timeToTick; + int senseInterval = sensor.scanRate; + + if (lastSenseTime > senseInterval) { + lastSenseTime = lastSenseTime % senseInterval; + if (extraTick) { + (sensor).timeToTick = (0L); + sensor.tick((ServerLevel) brainedEntity.level(), brainedEntity); + } + } + sensor.timeToTick = (lastSenseTime); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java new file mode 100644 index 0000000..4271658 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java @@ -0,0 +1,1054 @@ +package org.bxteam.divinemc.util.map; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; +import org.bxteam.divinemc.util.Assert; +import org.bxteam.divinemc.util.ObjectUtil; + +/** + * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or + * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}. + * + *

This class can be used as an alternative to + * {@code Collections.synchronizedMap(new WeakHashMap>())} in order to + * support better performance when accessed concurrently. This implementation follows the + * same design constraints as {@link ConcurrentHashMap} with the exception that + * {@code null} values and {@code null} keys are supported. + * + *

NOTE: The use of references means that there is no guarantee that items + * placed into the map will be subsequently available. The garbage collector may discard + * references at any time, so it may appear that an unknown thread is silently removing + * entries. + * + *

If not explicitly specified, this implementation will use + * {@linkplain SoftReference soft entry references}. + * + * @param the key type + * @param the value type + * @author Phillip Webb + * @author Juergen Hoeller + * @author Brian Clozel + */ +public class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT; + private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16; + private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30; + + /** + * Array of segments indexed using the high order bits from the hash. + */ + private final Segment[] segments; + + /** + * When the average number of references per table exceeds this value resize will be attempted. + */ + private final float loadFactor; + + /** + * The reference type: SOFT or WEAK. + */ + private final ReferenceType referenceType; + + /** + * The shift value used to calculate the size of the segments array and an index from the hash. + */ + private final int shift; + + /** + * Late binding entry set. + */ + private volatile @Nullable Set> entrySet; + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + */ + public ConcurrentReferenceHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + */ + public ConcurrentReferenceHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per table + * exceeds this value resize will be attempted + */ + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + */ + public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param referenceType the reference type used for entries (soft or weak) + */ + public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + */ + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { + this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + * @param referenceType the reference type used for entries (soft or weak) + */ + @SuppressWarnings("unchecked") + public ConcurrentReferenceHashMap( + int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) { + + Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); + Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); + Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); + Assert.notNull(referenceType, "Reference type must not be null"); + this.loadFactor = loadFactor; + this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); + int size = 1 << this.shift; + this.referenceType = referenceType; + int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); + int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE); + Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size); + int resizeThreshold = (int) (initialSize * getLoadFactor()); + for (int i = 0; i < segments.length; i++) { + segments[i] = new Segment(initialSize, resizeThreshold); + } + this.segments = segments; + } + + /** + * Calculate a shift value that can be used to create a power-of-two value between + * the specified maximum and minimum values. + * + * @param minimumValue the minimum value + * @param maximumValue the maximum value + * @return the calculated shift (use {@code 1 << shift} to obtain a value) + */ + protected static int calculateShift(int minimumValue, int maximumValue) { + int shift = 0; + int value = 1; + while (value < minimumValue && value < maximumValue) { + value <<= 1; + shift++; + } + return shift; + } + + protected final float getLoadFactor() { + return this.loadFactor; + } + + protected final int getSegmentsSize() { + return this.segments.length; + } + + protected final Segment getSegment(int index) { + return this.segments[index]; + } + + /** + * Factory method that returns the {@link ReferenceManager}. + * This method will be called once for each {@link Segment}. + * + * @return a new reference manager + */ + protected ReferenceManager createReferenceManager() { + return new ReferenceManager(); + } + + /** + * Get the hash for a given object, apply an additional hash function to reduce + * collisions. This implementation uses the same Wang/Jenkins algorithm as + * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing. + * + * @param o the object to hash (may be null) + * @return the resulting hash code + */ + protected int getHash(@Nullable Object o) { + int hash = (o != null ? o.hashCode() : 0); + hash += (hash << 15) ^ 0xffffcd7d; + hash ^= (hash >>> 10); + hash += (hash << 3); + hash ^= (hash >>> 6); + hash += (hash << 2) + (hash << 14); + hash ^= (hash >>> 16); + return hash; + } + + @Override + public @Nullable V get(@Nullable Object key) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + Entry entry = (ref != null ? ref.get() : null); + return (entry != null ? entry.getValue() : null); + } + + @Override + public @Nullable V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + Entry entry = (ref != null ? ref.get() : null); + return (entry != null ? entry.getValue() : defaultValue); + } + + @Override + public boolean containsKey(@Nullable Object key) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + Entry entry = (ref != null ? ref.get() : null); + return (entry != null && ObjectUtil.nullSafeEquals(entry.getKey(), key)); + } + + /** + * Return a {@link Reference} to the {@link Entry} for the specified {@code key}, + * or {@code null} if not found. + * + * @param key the key (can be {@code null}) + * @param restructure types of restructure allowed during this call + * @return the reference, or {@code null} if not found + */ + protected final @Nullable Reference getReference(@Nullable Object key, Restructure restructure) { + int hash = getHash(key); + return getSegmentForHash(hash).getReference(key, hash, restructure); + } + + @Override + public @Nullable V put(@Nullable K key, @Nullable V value) { + return put(key, value, true); + } + + @Override + public @Nullable V putIfAbsent(@Nullable K key, @Nullable V value) { + return put(key, value, false); + } + + private @Nullable V put(final @Nullable K key, final @Nullable V value, final boolean overwriteExisting) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { + @Override + protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { + if (entry != null) { + V oldValue = entry.getValue(); + if (overwriteExisting) { + entry.setValue(value); + } + return oldValue; + } + Assert.state(entries != null, "No entries segment"); + entries.add(value); + return null; + } + }); + } + + @Override + public @Nullable V remove(@Nullable Object key) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null) { + if (ref != null) { + ref.release(); + } + return entry.value; + } + return null; + } + }); + } + + @Override + public boolean remove(@Nullable Object key, final @Nullable Object value) { + Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), value)) { + if (ref != null) { + ref.release(); + } + return true; + } + return false; + } + }); + return (Boolean.TRUE.equals(result)); + } + + @Override + public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) { + Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), oldValue)) { + entry.setValue(newValue); + return true; + } + return false; + } + }); + return (Boolean.TRUE.equals(result)); + } + + @Override + public @Nullable V replace(@Nullable K key, final @Nullable V value) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry) { + if (entry != null) { + V oldValue = entry.getValue(); + entry.setValue(value); + return oldValue; + } + return null; + } + }); + } + + @Override + public void clear() { + for (Segment segment : this.segments) { + segment.clear(); + } + } + + /** + * Remove any entries that have been garbage collected and are no longer referenced. + * Under normal circumstances garbage collected entries are automatically purged as + * items are added or removed from the Map. This method can be used to force a purge, + * and is useful when the Map is read frequently but updated less often. + */ + public void purgeUnreferencedEntries() { + for (Segment segment : this.segments) { + segment.restructureIfNecessary(false); + } + } + + @Override + public int size() { + int size = 0; + for (Segment segment : this.segments) { + size += segment.getCount(); + } + return size; + } + + @Override + public boolean isEmpty() { + for (Segment segment : this.segments) { + if (segment.getCount() > 0) { + return false; + } + } + return true; + } + + @Override + public Set> entrySet() { + Set> entrySet = this.entrySet; + if (entrySet == null) { + entrySet = new EntrySet(); + this.entrySet = entrySet; + } + return entrySet; + } + + private @Nullable T doTask(@Nullable Object key, Task task) { + int hash = getHash(key); + return getSegmentForHash(hash).doTask(hash, key, task); + } + + private Segment getSegmentForHash(int hash) { + return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)]; + } + + + /** + * Various reference types supported by this map. + */ + public enum ReferenceType { + + /** + * Use {@link SoftReference SoftReferences}. + */ + SOFT, + + /** + * Use {@link WeakReference WeakReferences}. + */ + WEAK + } + + + /** + * Various options supported by a {@code Task}. + */ + private enum TaskOption { + + RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE + } + + + /** + * The types of restructuring that can be performed. + */ + protected enum Restructure { + + WHEN_NECESSARY, NEVER + } + + + /** + * A reference to an {@link Entry} contained in the map. Implementations are usually + * wrappers around specific Java reference implementations (for example, {@link SoftReference}). + * + * @param the key type + * @param the value type + */ + protected interface Reference { + + /** + * Return the referenced entry, or {@code null} if the entry is no longer available. + */ + @Nullable + Entry get(); + + /** + * Return the hash for the reference. + */ + int getHash(); + + /** + * Return the next reference in the chain, or {@code null} if none. + */ + @Nullable + Reference getNext(); + + /** + * Release this entry and ensure that it will be returned from + * {@code ReferenceManager#pollForPurge()}. + */ + void release(); + } + + + /** + * Allows a task access to {@link ConcurrentReferenceHashMap.Segment} entries. + */ + private interface Entries { + + /** + * Add a new entry with the specified value. + * + * @param value the value to add + */ + void add(@Nullable V value); + } + + /** + * A single map entry. + * + * @param the key type + * @param the value type + */ + protected static final class Entry implements Map.Entry { + + private final @Nullable K key; + + private volatile @Nullable V value; + + public Entry(@Nullable K key, @Nullable V value) { + this.key = key; + this.value = value; + } + + @Override + public @Nullable K getKey() { + return this.key; + } + + @Override + public @Nullable V getValue() { + return this.value; + } + + @Override + public @Nullable V setValue(@Nullable V value) { + V previous = this.value; + this.value = value; + return previous; + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof Map.Entry that && + ObjectUtil.nullSafeEquals(getKey(), that.getKey()) && + ObjectUtil.nullSafeEquals(getValue(), that.getValue()))); + } + + @Override + public int hashCode() { + return (ObjectUtil.nullSafeHashCode(this.key) ^ ObjectUtil.nullSafeHashCode(this.value)); + } + + @Contract(pure = true) + @Override + public @NotNull String toString() { + return (this.key + "=" + this.value); + } + } + + /** + * Internal {@link Reference} implementation for {@link SoftReference SoftReferences}. + */ + private static final class SoftEntryReference extends SoftReference> implements Reference { + + private final int hash; + + private final @Nullable Reference nextReference; + + public SoftEntryReference(Entry entry, int hash, @Nullable Reference next, + ReferenceQueue> queue) { + + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public @Nullable Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + } + } + + /** + * Internal {@link Reference} implementation for {@link WeakReference WeakReferences}. + */ + private static final class WeakEntryReference extends WeakReference> implements Reference { + + private final int hash; + + private final @Nullable Reference nextReference; + + public WeakEntryReference(Entry entry, int hash, @Nullable Reference next, + ReferenceQueue> queue) { + + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public @Nullable Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + } + } + + /** + * A single segment used to divide the map to allow better concurrent performance. + */ + @SuppressWarnings("serial") + protected final class Segment extends ReentrantLock { + + private final ReferenceManager referenceManager; + + private final int initialSize; + /** + * The total number of references contained in this segment. This includes chained + * references and references that have been garbage collected but not purged. + */ + private final AtomicInteger count = new AtomicInteger(); + /** + * Array of references indexed using the low order bits from the hash. + * This property should only be set along with {@code resizeThreshold}. + */ + private volatile @Nullable Reference[] references; + /** + * The threshold when resizing of the references should occur. When {@code count} + * exceeds this value references will be resized. + */ + private int resizeThreshold; + + public Segment(int initialSize, int resizeThreshold) { + this.referenceManager = createReferenceManager(); + this.initialSize = initialSize; + this.references = createReferenceArray(initialSize); + this.resizeThreshold = resizeThreshold; + } + + public @Nullable Reference getReference(@Nullable Object key, int hash, Restructure restructure) { + if (restructure == Restructure.WHEN_NECESSARY) { + restructureIfNecessary(false); + } + if (this.count.get() == 0) { + return null; + } + // Use a local copy to protect against other threads writing + @Nullable Reference[] references = this.references; + int index = getIndex(hash, references); + Reference head = references[index]; + return findInChain(head, key, hash); + } + + /** + * Apply an update operation to this segment. + * The segment will be locked during the update. + * + * @param hash the hash of the key + * @param key the key + * @param task the update operation + * @return the result of the operation + */ + public @Nullable T doTask(final int hash, final @Nullable Object key, final @NotNull Task task) { + boolean resize = task.hasOption(TaskOption.RESIZE); + if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { + restructureIfNecessary(resize); + } + if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count.get() == 0) { + return task.execute(null, null, null); + } + lock(); + try { + final int index = getIndex(hash, this.references); + final Reference head = this.references[index]; + Reference ref = findInChain(head, key, hash); + Entry entry = (ref != null ? ref.get() : null); + Entries entries = value -> { + @SuppressWarnings("unchecked") + Entry newEntry = new Entry<>((K) key, value); + Reference newReference = Segment.this.referenceManager.createReference(newEntry, hash, head); + Segment.this.references[index] = newReference; + Segment.this.count.incrementAndGet(); + }; + return task.execute(ref, entry, entries); + } finally { + unlock(); + if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { + restructureIfNecessary(resize); + } + } + } + + /** + * Clear all items from this segment. + */ + public void clear() { + if (this.count.get() == 0) { + return; + } + lock(); + try { + this.references = createReferenceArray(this.initialSize); + this.resizeThreshold = (int) (this.references.length * getLoadFactor()); + this.count.set(0); + } finally { + unlock(); + } + } + + /** + * Restructure the underlying data structure when it becomes necessary. This + * method can increase the size of the references table as well as purge any + * references that have been garbage collected. + * + * @param allowResize if resizing is permitted + */ + void restructureIfNecessary(boolean allowResize) { + int currCount = this.count.get(); + boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold); + Reference ref = this.referenceManager.pollForPurge(); + if (ref != null || (needsResize)) { + restructure(allowResize, ref); + } + } + + private void restructure(boolean allowResize, @Nullable Reference ref) { + boolean needsResize; + lock(); + try { + int expectedCount = this.count.get(); + Set> toPurge = Collections.emptySet(); + if (ref != null) { + toPurge = new HashSet<>(); + while (ref != null) { + toPurge.add(ref); + ref = this.referenceManager.pollForPurge(); + } + } + expectedCount -= toPurge.size(); + + // Estimate new count, taking into account count inside lock and items that + // will be purged. + needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold); + boolean resizing = false; + int restructureSize = this.references.length; + if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { + restructureSize <<= 1; + resizing = true; + } + + int newCount = 0; + // Restructure the resized reference array + if (resizing) { + Reference[] restructured = createReferenceArray(restructureSize); + for (Reference reference : this.references) { + ref = reference; + while (ref != null) { + if (!toPurge.contains(ref)) { + Entry entry = ref.get(); + // Also filter out null references that are now null + // they should be polled from the queue in a later restructure call. + if (entry != null) { + int index = getIndex(ref.getHash(), restructured); + restructured[index] = this.referenceManager.createReference( + entry, ref.getHash(), restructured[index]); + newCount++; + } + } + ref = ref.getNext(); + } + } + // Replace volatile members + this.references = restructured; + this.resizeThreshold = (int) (this.references.length * getLoadFactor()); + } + // Restructure the existing reference array "in place" + else { + for (int i = 0; i < this.references.length; i++) { + Reference purgedRef = null; + ref = this.references[i]; + while (ref != null) { + if (!toPurge.contains(ref)) { + Entry entry = ref.get(); + // Also filter out null references that are now null + // they should be polled from the queue in a later restructure call. + if (entry != null) { + purgedRef = this.referenceManager.createReference( + entry, ref.getHash(), purgedRef); + } + newCount++; + } + ref = ref.getNext(); + } + this.references[i] = purgedRef; + } + } + this.count.set(Math.max(newCount, 0)); + } finally { + unlock(); + } + } + + private @Nullable Reference findInChain(@Nullable Reference ref, @Nullable Object key, int hash) { + Reference currRef = ref; + while (currRef != null) { + if (currRef.getHash() == hash) { + Entry entry = currRef.get(); + if (entry != null) { + K entryKey = entry.getKey(); + if (ObjectUtil.nullSafeEquals(entryKey, key)) { + return currRef; + } + } + } + currRef = currRef.getNext(); + } + return null; + } + + @Contract(value = "_ -> new", pure = true) + @SuppressWarnings({"unchecked"}) + private Reference @NotNull [] createReferenceArray(int size) { + return new Reference[size]; + } + + private int getIndex(int hash, @Nullable Reference[] references) { + return (hash & (references.length - 1)); + } + + /** + * Return the size of the current references array. + */ + public int getSize() { + return this.references.length; + } + + /** + * Return the total number of references in this segment. + */ + public int getCount() { + return this.count.get(); + } + } + + /** + * A task that can be {@link Segment#doTask run} against a {@link Segment}. + */ + private abstract class Task { + + private final EnumSet options; + + public Task(TaskOption... options) { + this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options)); + } + + public boolean hasOption(TaskOption option) { + return this.options.contains(option); + } + + /** + * Execute the task. + * + * @param ref the found reference (or {@code null}) + * @param entry the found entry (or {@code null}) + * @param entries access to the underlying entries + * @return the result of the task + * @see #execute(Reference, Entry) + */ + protected @Nullable T execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { + return execute(ref, entry); + } + + /** + * Convenience method that can be used for tasks that do not need access to {@link Entries}. + * + * @param ref the found reference (or {@code null}) + * @param entry the found entry (or {@code null}) + * @return the result of the task + * @see #execute(Reference, Entry, Entries) + */ + protected @Nullable T execute(@Nullable Reference ref, @Nullable Entry entry) { + return null; + } + } + + /** + * Internal entry-set implementation. + */ + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(@Nullable Object o) { + if (o instanceof Map.Entry entry) { + Reference ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); + Entry otherEntry = (ref != null ? ref.get() : null); + if (otherEntry != null) { + return ObjectUtil.nullSafeEquals(entry.getValue(), otherEntry.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Map.Entry entry) { + return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public int size() { + return ConcurrentReferenceHashMap.this.size(); + } + + @Override + public void clear() { + ConcurrentReferenceHashMap.this.clear(); + } + } + + /** + * Internal entry iterator implementation. + */ + private class EntryIterator implements Iterator> { + + private int segmentIndex; + + private int referenceIndex; + + private @Nullable Reference @Nullable [] references; + + private @Nullable Reference reference; + + private @Nullable Entry next; + + private @Nullable Entry last; + + public EntryIterator() { + moveToNextSegment(); + } + + @Override + public boolean hasNext() { + getNextIfNecessary(); + return (this.next != null); + } + + @Override + public Entry next() { + getNextIfNecessary(); + if (this.next == null) { + throw new NoSuchElementException(); + } + this.last = this.next; + this.next = null; + return this.last; + } + + private void getNextIfNecessary() { + while (this.next == null) { + moveToNextReference(); + if (this.reference == null) { + return; + } + this.next = this.reference.get(); + } + } + + private void moveToNextReference() { + if (this.reference != null) { + this.reference = this.reference.getNext(); + } + while (this.reference == null && this.references != null) { + if (this.referenceIndex >= this.references.length) { + moveToNextSegment(); + this.referenceIndex = 0; + } else { + this.reference = this.references[this.referenceIndex]; + this.referenceIndex++; + } + } + } + + private void moveToNextSegment() { + this.reference = null; + this.references = null; + if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) { + this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references; + this.segmentIndex++; + } + } + + @Override + public void remove() { + Assert.state(this.last != null, "No element to remove"); + ConcurrentReferenceHashMap.this.remove(this.last.getKey()); + this.last = null; + } + } + + /** + * Strategy class used to manage {@link Reference References}. + * This class can be overridden if alternative reference types need to be supported. + */ + protected class ReferenceManager { + + private final ReferenceQueue> queue = new ReferenceQueue<>(); + + /** + * Factory method used to create a new {@link Reference}. + * + * @param entry the entry contained in the reference + * @param hash the hash + * @param next the next reference in the chain, or {@code null} if none + * @return a new {@link Reference} + */ + public Reference createReference(Entry entry, int hash, @Nullable Reference next) { + if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { + return new WeakEntryReference<>(entry, hash, next, this.queue); + } + return new SoftEntryReference<>(entry, hash, next, this.queue); + } + + /** + * Return any reference that has been garbage collected and can be purged from the + * underlying structure or {@code null} if no references need purging. This + * method must be thread safe and ideally should not block when returning + * {@code null}. References should be returned once and only once. + * + * @return a reference to purge or {@code null} + */ + @SuppressWarnings("unchecked") + public @Nullable Reference pollForPurge() { + return (Reference) this.queue.poll(); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java new file mode 100644 index 0000000..183ed6e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/BoxOctree.java @@ -0,0 +1,165 @@ +package org.bxteam.divinemc.util.structure; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.phys.AABB; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + +public class BoxOctree { + private static final int subdivideThreshold = 10; + private static final int maximumDepth = 3; + private final AABB boundary; + private final Vec3i size; + private final int depth; + private final List innerBoxes = new ArrayList<>(); + private final List childrenOctants = new ArrayList<>(); + + public BoxOctree(AABB axisAlignedBB) { + this(axisAlignedBB, 0); + } + + private BoxOctree(@NotNull AABB axisAlignedBB, int parentDepth) { + boundary = axisAlignedBB.move(0, 0, 0); // deep copy + size = new Vec3i(roundAwayFromZero(boundary.getXsize()), roundAwayFromZero(boundary.getYsize()), roundAwayFromZero(boundary.getZsize())); + depth = parentDepth + 1; + } + + private int roundAwayFromZero(double value) { + return (value >= 0) ? (int)Math.ceil(value) : (int)Math.floor(value); + } + + private void subdivide() { + if (!childrenOctants.isEmpty()) { + throw new UnsupportedOperationException("Tried to subdivide when there are already children octants."); + } + + int halfXSize = size.getX()/2; + int halfYSize = size.getY()/2; + int halfZSize = size.getZ()/2; + // Lower Left Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY, boundary.minZ, + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.minZ + halfZSize), + depth)); + // Lower Left Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY, boundary.minZ + halfZSize, + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.maxZ), + depth)); + // Lower Right Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY, boundary.minZ, + boundary.maxX, boundary.minY + halfYSize, boundary.minZ + halfZSize), + depth)); + // Lower Right Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY, boundary.minZ + halfZSize, + boundary.maxX, boundary.minY + halfYSize, boundary.maxZ), + depth)); + // Upper Left Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY + halfYSize, boundary.minZ, + boundary.minX + halfXSize, boundary.maxY, boundary.minZ + halfZSize), + depth)); + // Upper Left Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX, boundary.minY + halfYSize, boundary.minZ + halfZSize, + boundary.minX + halfXSize, boundary.maxY, boundary.maxZ), + depth)); + // Upper Right Back Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.minZ, + boundary.maxX, boundary.maxY, boundary.minZ + halfZSize), + depth)); + // Upper Right Front Corner + childrenOctants.add(new BoxOctree(new AABB( + boundary.minX + halfXSize, boundary.minY + halfYSize, boundary.minZ + halfZSize, + boundary.maxX, boundary.maxY, boundary.maxZ), + depth)); + + for (AABB parentInnerBox : innerBoxes) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryIntersects(parentInnerBox)) { + octree.addBox(parentInnerBox); + } + } + } + + innerBoxes.clear(); + } + + public void addBox(AABB axisAlignedBB) { + if (depth < maximumDepth && innerBoxes.size() > subdivideThreshold) { + subdivide(); + } if (!childrenOctants.isEmpty()) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryIntersects(axisAlignedBB)) { + octree.addBox(axisAlignedBB); + } + } + } else { + // Prevent re-adding the same box if it already exists + for (AABB parentInnerBox : innerBoxes) { + if (parentInnerBox.equals(axisAlignedBB)) { + return; + } + } + innerBoxes.add(axisAlignedBB); + } + } + + public boolean boundaryEntirelyContains(@NotNull AABB axisAlignedBB) { + return boundary.contains(axisAlignedBB.minX, axisAlignedBB.minY, axisAlignedBB.minZ) && + boundary.contains(axisAlignedBB.maxX, axisAlignedBB.maxY, axisAlignedBB.maxZ); + } + + public boolean boundaryIntersects(AABB axisAlignedBB) { + return boundary.intersects(axisAlignedBB); + } + + public boolean withinBoundsButNotIntersectingChildren(AABB axisAlignedBB) { + return this.boundaryEntirelyContains(axisAlignedBB) && !this.intersectsAnyBox(axisAlignedBB); + } + + public boolean intersectsAnyBox(AABB axisAlignedBB) { + if (!childrenOctants.isEmpty()) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryIntersects(axisAlignedBB) && octree.intersectsAnyBox(axisAlignedBB)) { + return true; + } + } + } + else { + for (AABB innerBox : innerBoxes) { + if (innerBox.intersects(axisAlignedBB)) { + return true; + } + } + } + return false; + } + + public boolean boundaryContains(@NotNull BlockPos position) { + return boundary.contains(position.getX(), position.getY(), position.getZ()); + } + + public boolean withinAnyBox(BlockPos position) { + if (!childrenOctants.isEmpty()) { + for (BoxOctree octree : childrenOctants) { + if (octree.boundaryContains(position) && octree.withinAnyBox(position)) { + return true; + } + } + } + else { + for (AABB innerBox : innerBoxes) { + if (innerBox.contains(position.getX(), position.getY(), position.getZ())) { + return true; + } + } + } + return false; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java new file mode 100644 index 0000000..3298792 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/GeneralUtils.java @@ -0,0 +1,90 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntComparators; +import net.minecraft.Util; +import net.minecraft.core.FrontAndTop; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NumericTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.JigsawBlock; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + +public final class GeneralUtils { + private GeneralUtils() {} + + // More optimized with checking if the jigsaw blocks can connect + public static boolean canJigsawsAttach(StructureTemplate.@NotNull JigsawBlockInfo jigsaw1, StructureTemplate.@NotNull JigsawBlockInfo jigsaw2) { + FrontAndTop prop1 = jigsaw1.info().state().getValue(JigsawBlock.ORIENTATION); + FrontAndTop prop2 = jigsaw2.info().state().getValue(JigsawBlock.ORIENTATION); + + return prop1.front() == prop2.front().getOpposite() && + (prop1.top() == prop2.top() || isRollableJoint(jigsaw1, prop1)) && + getStringMicroOptimised(jigsaw1.info().nbt(), "target").equals(getStringMicroOptimised(jigsaw2.info().nbt(), "name")); + } + + private static boolean isRollableJoint(StructureTemplate.@NotNull JigsawBlockInfo jigsaw1, FrontAndTop prop1) { + String joint = getStringMicroOptimised(jigsaw1.info().nbt(), "joint"); + if(!joint.equals("rollable") && !joint.equals("aligned")) { + return !prop1.front().getAxis().isHorizontal(); + } + else { + return joint.equals("rollable"); + } + } + + public static void shuffleAndPrioritize(@NotNull List list, RandomSource random) { + Int2ObjectArrayMap> buckets = new Int2ObjectArrayMap<>(); + + // Add entries to the bucket + for (StructureTemplate.JigsawBlockInfo structureBlockInfo : list) { + int key = 0; + if (structureBlockInfo.info().nbt() != null) { + key = getIntMicroOptimised(structureBlockInfo.info().nbt(), "selection_priority"); + } + + buckets.computeIfAbsent(key, k -> new ArrayList<>()).add(structureBlockInfo); + } + + // Shuffle the entries in the bucket + for (List bucketList : buckets.values()) { + Util.shuffle(bucketList, random); + } + + if (buckets.size() == 1) { + list.clear(); + copyAll(buckets.int2ObjectEntrySet().fastIterator().next().getValue(), list); + } + else if (buckets.size() > 1) { + // Priorities found. Concat them into a single new master list in reverse order to match vanilla behavior + list.clear(); + + IntArrayList keys = new IntArrayList(buckets.keySet()); + keys.sort(IntComparators.OPPOSITE_COMPARATOR); + + for (int i = 0; i < keys.size(); i++) { + copyAll(buckets.get(keys.getInt(i)), list); + } + } + } + + public static int getIntMicroOptimised(@NotNull CompoundTag tag, String key) { + return tag.get(key) instanceof NumericTag numericTag ? numericTag.getAsInt() : 0; + } + + public static @NotNull String getStringMicroOptimised(@NotNull CompoundTag tag, String key) { + return tag.get(key) instanceof StringTag stringTag ? stringTag.getAsString() : ""; + } + + public static void copyAll(@NotNull List src, List dest) { + // Do not listen to IDE. This is faster than addAll + for (int i = 0; i < src.size(); i++) { + dest.add(src.get(i)); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java new file mode 100644 index 0000000..4f6021c --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoList.java @@ -0,0 +1,274 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.function.Predicate; + +public class PalettedStructureBlockInfoList implements List { + private static final long[] EMPTY_DATA = new long[0]; + private static final CompoundTag[] NULL_TAGS = new CompoundTag[]{null}; + private static final String UNSUPPORTED_OPERATION_ERROR_MESSAGE = "Structure Layout Optimizer: No mod should be modifying a StructureTemplate's Palette itself. Please reach out to Structure Layout Optimizer dev for this crash to investigate this mod compat issue."; + + protected final long[] data; + protected final BlockState[] states; + protected final CompoundTag[] nbts; + protected final int xBits, yBits, zBits; + protected final int stateBits, nbtBits; + protected final int bitsPerEntry; + protected final int size; + private WeakReference> cachedStructureBlockInfoList = new WeakReference<>(null); + + public PalettedStructureBlockInfoList(List infos) { + this(infos, null); + } + + public PalettedStructureBlockInfoList(List infos, Predicate predicate) { + List entries = new ArrayList<>(); + List states = new ArrayList<>(); + List tags = new ArrayList<>(); + + int maxX = 0; + int maxY = 0; + int maxZ = 0; + + for (StructureTemplate.StructureBlockInfo info : infos) { + if (predicate != null && !predicate.test(info)) { + continue; + } + + int state = states.indexOf(info.state()); + if (state == -1) { + state = states.size(); + states.add(info.state()); + } + + int tag = indexOf(tags, info.nbt()); + if (tag == -1) { + tag = tags.size(); + tags.add(info.nbt()); + } + + int x = info.pos().getX(); + int y = info.pos().getY(); + int z = info.pos().getZ(); + + if (x < 0 || y < 0 || z < 0) { + throw new RuntimeException("StructureLayoutOptimizer: Invalid StructureBlockInfo position: " + info.pos()); + } + if (x > maxX) { + maxX = x; + } + if (y > maxY) { + maxY = y; + } + if (z > maxZ) { + maxZ = z; + } + + entries.add(new Entry(x, y, z, state, tag)); + } + + this.xBits = bits(maxX); + this.yBits = bits(maxY); + this.zBits = bits(maxZ); + this.stateBits = bits(states.size() - 1); + this.nbtBits = bits(tags.size() - 1); + this.bitsPerEntry = this.xBits + this.yBits + this.zBits + this.stateBits + this.nbtBits; + + if (this.bitsPerEntry > 64) { + throw new RuntimeException("StructureLayoutOptimizer: Too many bits per entry: " + this.bitsPerEntry); + } + + this.size = entries.size(); + if (this.bitsPerEntry != 0) { + int entriesPerLong = 64 / this.bitsPerEntry; + this.data = new long[(this.size + entriesPerLong - 1) / entriesPerLong]; + for (int i = 0; i < this.size; i++) { + this.data[i / entriesPerLong] |= entries.get(i).compress(this.xBits, this.yBits, this.zBits, this.stateBits) << ((i % entriesPerLong) * this.bitsPerEntry); + } + } else { + this.data = EMPTY_DATA; + } + this.states = states.toArray(new BlockState[0]); + this.nbts = tags.size() == 1 && tags.get(0) == null ? NULL_TAGS : tags.toArray(new CompoundTag[0]); + } + + private static int bits(int i) { + int bits = 0; + while (i >= 1 << bits) { + bits++; + } + return bits; + } + + private static int indexOf(@NotNull List list, T o) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i) == o) { + return i; + } + } + return -1; + } + + private @NotNull List convertBackToStructureBlockInfoListAndCache() { + synchronized(data) { + List structureBlockInfos = cachedStructureBlockInfoList.get(); + if (structureBlockInfos != null) { + return structureBlockInfos; + } + + structureBlockInfos = new ObjectArrayList<>(new PalettedStructureBlockInfoListIterator(this)); + cachedStructureBlockInfoList = new WeakReference<>(structureBlockInfos); + return structureBlockInfos; + } + } + + @Override + public int size() { + return this.size; + } + + @Override + public boolean isEmpty() { + return this.size == 0; + } + + @NotNull + @Override + public Iterator iterator() { + return convertBackToStructureBlockInfoListAndCache().iterator(); + } + + @NotNull + @Override + public ListIterator listIterator() { + return convertBackToStructureBlockInfoListAndCache().listIterator(); + } + + @NotNull + @Override + public ListIterator listIterator(int index) { + return convertBackToStructureBlockInfoListAndCache().listIterator(index); + } + + + @Override + public boolean contains(Object o) { + return convertBackToStructureBlockInfoListAndCache().contains(o); + } + + @Override + public boolean containsAll(@NotNull Collection c) { + return new HashSet<>(convertBackToStructureBlockInfoListAndCache()).containsAll(c); + } + + @NotNull + @Override + public Object @NotNull [] toArray() { + return convertBackToStructureBlockInfoListAndCache().toArray(); + } + + @NotNull + @Override + public T @NotNull [] toArray(@NotNull T @NotNull [] a) { + return convertBackToStructureBlockInfoListAndCache().toArray(a); + } + + @Override + public StructureTemplate.StructureBlockInfo get(int index) { + return convertBackToStructureBlockInfoListAndCache().get(index); + } + + @Override + public int indexOf(Object o) { + return convertBackToStructureBlockInfoListAndCache().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return convertBackToStructureBlockInfoListAndCache().lastIndexOf(o); + } + + @NotNull + @Override + public List subList(int fromIndex, int toIndex) { + return convertBackToStructureBlockInfoListAndCache().subList(fromIndex, toIndex); + } + + @Override + public StructureTemplate.StructureBlockInfo set(int index, StructureTemplate.StructureBlockInfo element) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public void add(int index, StructureTemplate.StructureBlockInfo element) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean add(StructureTemplate.StructureBlockInfo info) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public StructureTemplate.StructureBlockInfo remove(int index) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean addAll(@NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean addAll(int index, @NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_ERROR_MESSAGE); + } + + private static class Entry { + private final int x, y, z; + private final int state, nbt; + + private Entry(int x, int y, int z, int state, int nbt) { + this.x = x; + this.y = y; + this.z = z; + this.state = state; + this.nbt = nbt; + } + + private long compress(int xBits, int yBits, int zBits, int stateBits) { + return this.x + ((this.y + ((this.z + ((this.state + ((long) this.nbt << stateBits)) << zBits)) << yBits)) << xBits); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java new file mode 100644 index 0000000..00db7b9 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/PalettedStructureBlockInfoListIterator.java @@ -0,0 +1,65 @@ +package org.bxteam.divinemc.util.structure; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.util.Iterator; + +public class PalettedStructureBlockInfoListIterator implements Iterator { + private final PalettedStructureBlockInfoList infos; + + private final int xOffset, yOffset, zOffset, stateOffset; + private final int xMask, yMask, zMask, stateMask, tagMask; + + private int index = 0; + + public PalettedStructureBlockInfoListIterator(@NotNull PalettedStructureBlockInfoList infos) { + this.infos = infos; + this.xOffset = infos.xBits; + this.yOffset = this.xOffset + infos.yBits; + this.zOffset = this.yOffset + infos.zBits; + this.stateOffset = this.zOffset + infos.stateBits; + this.xMask = (1 << infos.xBits) - 1; + this.yMask = (1 << infos.yBits) - 1; + this.zMask = (1 << infos.zBits) - 1; + this.stateMask = (1 << infos.stateBits) - 1; + this.tagMask = (1 << infos.nbtBits) - 1; + } + + @Override + public boolean hasNext() { + return this.index < this.size(); + } + + @Override + public StructureTemplate.StructureBlockInfo next() { + int index = this.toIndex(this.index++); + if (index >= this.infos.size) { + throw new IndexOutOfBoundsException(); + } + + int bitsPerEntry = this.infos.bitsPerEntry; + if (bitsPerEntry == 0) { + return new StructureTemplate.StructureBlockInfo(new BlockPos(0, 0, 0), this.infos.states[0], this.infos.nbts[0]); + } + + int entriesPerLong = 64 / bitsPerEntry; + long entry = this.infos.data[index / entriesPerLong] >>> ((index % entriesPerLong) * bitsPerEntry); + + int x = (int) (entry & this.xMask); + int y = (int) (entry >> this.xOffset & this.yMask); + int z = (int) (entry >> this.yOffset & this.zMask); + int state = (int) (entry >> this.zOffset & this.stateMask); + int tag = (int) (entry >> this.stateOffset & this.tagMask); + + return new StructureTemplate.StructureBlockInfo(new BlockPos(x, y, z), this.infos.states[state], this.infos.nbts[tag]); + } + + protected int toIndex(int index) { + return index; + } + + protected int size() { + return this.infos.size; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java new file mode 100644 index 0000000..f1aa0da --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/StructureTemplateOptimizer.java @@ -0,0 +1,105 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.objects.Object2BooleanMaps; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class StructureTemplateOptimizer { + private static final Map FINALIZE_PROCESSING_PROCESSORS = Object2BooleanMaps.synchronize(new Object2BooleanOpenHashMap<>()); + + public static @NotNull List getStructureBlockInfosInBounds(StructureTemplate.@NotNull Palette palette, BlockPos offset, @NotNull StructurePlaceSettings structurePlaceSettings) { + BoundingBox boundingBox = structurePlaceSettings.getBoundingBox(); + List originalPositions = palette.blocks(); + if (boundingBox == null) { + return originalPositions; + } + + // Capped processor needs full nbt block lists + for (StructureProcessor processor : structurePlaceSettings.getProcessors()) { + if (FINALIZE_PROCESSING_PROCESSORS.computeIfAbsent(processor, StructureTemplateOptimizer::isFinalizeProcessor)) { + return palette.blocks(); + } + } + + Mirror mirror = structurePlaceSettings.getMirror(); + Rotation rotation = structurePlaceSettings.getRotation(); + BlockPos pivot = structurePlaceSettings.getRotationPivot(); + + List listOfInBoundsRelativePositions = new ArrayList<>(); + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + + for (StructureTemplate.StructureBlockInfo blockInfo : originalPositions) { + mutableBlockPos.set(blockInfo.pos()); + transform(mutableBlockPos, mirror, rotation, pivot); + mutableBlockPos.move(offset); + + if (boundingBox.isInside(mutableBlockPos)) { + listOfInBoundsRelativePositions.add(blockInfo); + } + } + + // DO NOT REMOVE. This is required because the Template will return false for an entirely empty list and then remove the structure piece + // out of the structure start, preventing it from placing blocks into any other side chunks that the piece was supposed to place blocks in. + if (listOfInBoundsRelativePositions.isEmpty() && !originalPositions.isEmpty()) { + listOfInBoundsRelativePositions.add(originalPositions.get(0)); + } + + return listOfInBoundsRelativePositions; + } + + private static @NotNull Boolean isFinalizeProcessor(@NotNull StructureProcessor structureProcessor) { + try { + var method = structureProcessor.getClass().getMethod( + "finalizeProcessing", ServerLevelAccessor.class, BlockPos.class, BlockPos.class, List.class, List.class, StructurePlaceSettings.class); + + return method.getDeclaringClass() != StructureProcessor.class; + } + catch (NoSuchMethodException e) { + throw new RuntimeException("StructureProcessor does not have finalizeProcessing method", e); + } + } + + private static void transform(BlockPos.@NotNull MutableBlockPos mutableBlockPos, @NotNull Mirror mirror, Rotation rotation, BlockPos pivot) { + int i = mutableBlockPos.getX(); + int j = mutableBlockPos.getY(); + int k = mutableBlockPos.getZ(); + boolean flag = true; + switch (mirror) { + case LEFT_RIGHT: + k = -k; + break; + case FRONT_BACK: + i = -i; + break; + default: + flag = false; + } + + int l = pivot.getX(); + int i1 = pivot.getZ(); + switch (rotation) { + case COUNTERCLOCKWISE_90: + mutableBlockPos.set(l - i1 + k, j, l + i1 - i); + return; + case CLOCKWISE_90: + mutableBlockPos.set(l + i1 - k, j, i1 - l + i); + return; + case CLOCKWISE_180: + mutableBlockPos.set(l + l - i, j, i1 + i1 - k); + return; + default: + if (flag) mutableBlockPos.set(i, j, k); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java new file mode 100644 index 0000000..d12e100 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanArrayList.java @@ -0,0 +1,11 @@ +package org.bxteam.divinemc.util.structure; + +import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class TrojanArrayList extends ArrayList { + public final Set elementsAlreadyParsed = new HashSet<>(); +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java new file mode 100644 index 0000000..b6ff5cf --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/TrojanVoxelShape.java @@ -0,0 +1,21 @@ +package org.bxteam.divinemc.util.structure; + +import it.unimi.dsi.fastutil.doubles.DoubleList; +import net.minecraft.core.Direction; +import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.NotNull; + +public class TrojanVoxelShape extends VoxelShape { + public final BoxOctree boxOctree; + + public TrojanVoxelShape(BoxOctree boxOctree) { + super(BitSetDiscreteVoxelShape.withFilledBounds(0 ,0, 0, 0, 0, 0, 0, 0, 0)); + this.boxOctree = boxOctree; + } + + @Override + public DoubleList getCoords(Direction.@NotNull Axis axis) { + return null; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java new file mode 100644 index 0000000..0167a1e --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/structure/package-info.java @@ -0,0 +1,5 @@ +/** + * This package was ported from the Structure Layout Optimizer mod by TelepathicGrunt.
+ * Original source code - https://github.com/TelepathicGrunt/StructureLayoutOptimizer (MIT License) + */ +package org.bxteam.divinemc.util.structure; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java new file mode 100644 index 0000000..5b1a4b9 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSCalculator.java @@ -0,0 +1,83 @@ +package org.bxteam.divinemc.util.tps; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class TPSCalculator { + public Long lastTick; + public Long currentTick; + private double allMissedTicks = 0; + private final List tpsHistory = new CopyOnWriteArrayList<>(); + private static final int historyLimit = 40; + + public static final int MAX_TPS = 20; + public static final int FULL_TICK = 50; + + public TPSCalculator() {} + + public void doTick() { + if (currentTick != null) { + lastTick = currentTick; + } + + currentTick = System.currentTimeMillis(); + addToHistory(getTPS()); + clearMissedTicks(); + missedTick(); + } + + private void addToHistory(double tps) { + if (tpsHistory.size() >= historyLimit) { + tpsHistory.remove(0); + } + + tpsHistory.add(tps); + } + + public long getMSPT() { + return currentTick - lastTick; + } + + public double getAverageTPS() { + return tpsHistory.stream() + .mapToDouble(Double::doubleValue) + .average() + .orElse(0.1); + } + + public 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 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 double getMostAccurateTPS() { + return getTPS() > getAverageTPS() ? getAverageTPS() : getTPS(); + } + + public double getAllMissedTicks() { + return allMissedTicks; + } + + public int applicableMissedTicks() { + return (int) Math.floor(allMissedTicks); + } + + public void clearMissedTicks() { + allMissedTicks -= applicableMissedTicks(); + } + + public void resetMissedTicks() { + allMissedTicks = 0; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java new file mode 100644 index 0000000..3380e67 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/tps/TPSUtil.java @@ -0,0 +1,35 @@ +package org.bxteam.divinemc.util.tps; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; + +public class TPSUtil { + public static final int MAX_TPS = 20; + public static final int FULL_TICK = 50; + + public static float tt20(float ticks, boolean limitZero, @Nullable ServerLevel level) { + float newTicks = (float) rawTT20(ticks, level); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static int tt20(int ticks, boolean limitZero, @Nullable ServerLevel level) { + int newTicks = (int) Math.ceil(rawTT20(ticks, level)); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double tt20(double ticks, boolean limitZero, @Nullable ServerLevel level) { + double newTicks = (double) rawTT20(ticks, level); + + if (limitZero) return newTicks > 0 ? newTicks : 1; + else return newTicks; + } + + public static double rawTT20(double ticks, @Nullable ServerLevel level) { + return ticks == 0 ? 0 : ticks * (level == null ? MinecraftServer.getServer().tpsCalculator.getMostAccurateTPS() : level.tpsCalculator.getMostAccurateTPS()) / MAX_TPS; + } +} diff --git a/divinemc-server/src/main/java/su/plo/matter/Globals.java b/divinemc-server/src/main/java/su/plo/matter/Globals.java new file mode 100644 index 0000000..6a8134b --- /dev/null +++ b/divinemc-server/src/main/java/su/plo/matter/Globals.java @@ -0,0 +1,95 @@ +package su.plo.matter; + +import com.google.common.collect.Iterables; +import net.minecraft.server.level.ServerLevel; +import org.bxteam.divinemc.DivineConfig; + +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 (!DivineConfig.enableSecureSeed) 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/divinemc-server/src/main/java/su/plo/matter/Hashing.java b/divinemc-server/src/main/java/su/plo/matter/Hashing.java new file mode 100644 index 0000000..edf696f --- /dev/null +++ b/divinemc-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/divinemc-server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java b/divinemc-server/src/main/java/su/plo/matter/WorldgenCryptoRandom.java new file mode 100644 index 0000000..fab359a --- /dev/null +++ b/divinemc-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(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/divinemc-server/src/main/resources/linux-x86_64-libc2me-opts-natives-math.so b/divinemc-server/src/main/resources/linux-x86_64-libc2me-opts-natives-math.so new file mode 100644 index 0000000000000000000000000000000000000000..6a3dafd4f4e96b7cf1f0d00a85c9a82638c8bf27 GIT binary patch literal 943176 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≧_ag;Ml%I$9DgU(m2$qE!)$L1X#jld+I~xRm&}t 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

9fQ{pi4q@?Di#Oj^82wIyG>p=_( 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

bvw=IBcj&=s|xHaCkM5syCx^o0gSpTBXKBQ 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 J4~|j){{l!!Lcjn3 literal 0 HcmV?d00001 diff --git a/divinemc-server/src/main/resources/windows-x86_64-c2me-opts-natives-math.dll b/divinemc-server/src/main/resources/windows-x86_64-c2me-opts-natives-math.dll new file mode 100644 index 0000000000000000000000000000000000000000..65ffb81e17d41ff7cbc456a8eabe4117e23d98c7 GIT binary patch literal 1266176 zcmd?SeSDPHb?-X^5=cgP1~MXcBNE|=c4TA>4z%Fl9*vP~QHy)=i7@lr3@%K7#w{4X z$gVjCTSS&mCh8<$M`_$15~(S-Nn6gvJ;6+yIp_#ggPJUsFn!Y`11`iee#Bj4j$GWD5dODmUpJa5dS@HEQ&5$}B7!~Tjrp5m#|=bl)(?1^PFl@6{R zXyy6STaId(6^*f54Z^L~i;u)kuJoB0)!^3>Lm zVC(aE{)zYVyodc2tK9GZfl0al3%sh}6%AsL_prZW$_yFaF_0pZ_lk{M23HTa>uyYfO%~=v(~G3SY&}6~2YdD}0Y7FNS=N+rZ1b?@OaS zXZj~Npmts?^;vs(gqv1`e8I$-lWxWlkccFx6#j&^cbs{f^ozct1VxJ)NiNzQU*M(H zvFE=?2E}&z-b&QYdG*0~#%Mw%wRx|G;u&L1Fz3}!b%!sM?8q#OXI?6yWnW-`<^stT zzQ|Rzo8evIvv$x>>k3~k|KV2OTl-3C13#D>4~!s4D4utl@2$4lydPC}`I23kD}4UQ z$Sk2o{pN(~i>&v&0y@;c0BU(7FZ!(Gboq#gwv$Kh&IjXbM|+EXZw=PY`L}qUZ#cw| zcXw#8sg$ILXeC55A+;0o`AcXB2#0wz@AdMu7y?Hs`zP@c>R>ziKb@`VBTD zgb%8%qksq==aH*Na1UUccX|c<9mQ=PfgP$@B!WyR8mX)^N+|JW2~-ma3^3icOX4}c z&|vb@jjQn6DxjfZ9McdB)Xane1JRHt;>{AUV=DWe(}Z6{74+uD5#C*cjSi1gclwf@ znI+!jV3Ll~&*}~zT&H?4NBDt1XVTC}1q{v=BQ&hmVa9STE%+wP8L`6G+$~nRAUd2g z8Gh*9Lgv~LgseWm^do-9NIgOP2nB;JJaStA9MQwt$s zrV6a@3nVBb77i0|xF6_^jChiY60`^b_=^%rUl0Z23!+M(7g~j<(J(>O4Pm2tf@w_@ zOzWngqpix;r=-2U{>P6u`My-_2`vge=IAt6gZ;dqifq)?N~6J+31nf!D6k4Og)*B4 zxzJ|$(q?{N;fw+Z^sh!e5%{^Qu+V1h00R5(7n~um!gv3e#%wx;ST8!c!Z-C3^wnG{ z{H@V-j13B_#MhXT#_EI;740;$u#v}O5~Rm>h+dg*|ITQS>W2W=4mj(5S~%fbbVcwl zkr3YFTQsOhACV$eD%we_Cx{FFeun$lv2j}x$%rFM^&vyRejY21M5RDQ3x(V zRh1RfCdwyo2K}Q$!TsAU`fiEBF zXVfP|9P#_L!UZE}(CU<+b^%Ls(S&j%a61Wx-=^~T_k`0)>PuM3pCdtKa>_j^&_$#4 ztQ@5VBBP`VClnc=A6)c}?^LwXY40O7av6b+82A$4@ChlEqLF%{HR%N%lCh0qLB#22+81%cWKJs$dF0DtKfqMlFb5Bmky zL{%gcpf*n+@dPSQTHr}*JZag!D*eH8MuGjJ{}rP^?CWR}t#}AH3dBAie#Iz|RM(m@ zJ=ym4NV9!((MfyF)D#NjUdnynC=k1?v2%odesL@19@$r%O8(j7t<=qJW5JT@dScAJ zZ2MPMr`;7ktk|_|`+VfEeJOlA)wOiYdB7Ig_KQU)_&;0{FZNP%?9ZraltGSE#GM3d zXRK#kGeM!idPne44~26^dnD~h?fQq}8DDV1a}@skr?o)oFfA}6_HaCB!e_NW+tBi3 z&KnQM^8`ankzsOvXs{VMzfzm`rlTbujL-PIn>6Rm>i2!g_cQ0lXH0ODX1wXD4ezzk zU?b4>Jy@HwvD#<|`?}S`{}l!xGnqTu1C6c{9=l!P{}mo2Z;*KIHpTye_-^ZC`@D6H z|3Uk!*0EIR%i*K;mz}^d`zNIK1)A)mf#Y^xpek9g~m;Yc>Dl8W`OQ(?lvGs3B)J^Vw3~I3y5w&v>6cI zArRdLgf|VM+g{q-=B0Ll9tbthN-rR2ccfEE73ks{RvS$ME_*_RUN%_!?2hn0D0zhc zHmH_Jg}xe2Lh1x0P1s$$k4BE#*T*LyMw{Im-ff?a93<{@EvK_bo52$@8hun|6qG+`r@zfq)1Jf6oEvAy}JBf+V$7qhl^-Ly`9+(_ND31IY6Z-pwWPKk_>B^+W6= z(lUp1c^`~djPSBR_IgvvHBatJH9dXTZf~mV>AQA~P4~p#ql86`M~dAu@4S>Xdi65 zVDEYUik-YLnChIrVb0IM^Pay{k*;;K2fV51ns8>hy3{z&)01ipd9K>Y=oP!==_@1b zmbRG(P02NZD;^ZiFa7{zE?H}P{LnA(vDbdqD!J-UHLVF;0_Qb>izD^y&Cs(aQ)MoW zvcG1Px0>?3qwS8BN9?03VarITZG|U(8{l@E73i^#HoxbEA0s_kka$1;hmpUDFDSpm z%DajnULrqy!u}Qf-SBm5#u2-rxzC%L6tYT=WRZFW0R+%vAUrcgI2HO__(S`3DH8uZzf$JwsG*-Ij?8|U|1VfqQ=yUJ0lONJdmWMc3GZhu zX}+UmcBt{w_Fto}UI&Sv@a~EngQRVSjK7MUFyY31TL{7CB4g z6iWA`-4^LWRivPF0@-L}tP@oD|19Xpy(7v@;u7sYe%!wL=bFNeyhl3*v;5@+x0!cI z0TW}GJq0qjvS60vvt9C;l6*!x`m?6)LP8%Px1t~%ZRqxp+Izv%xXVbW3ItoD9T&X* zebF~Bcpl#iu-uqmT&@|Iv@C0HwBh}%a3cDXe$SYl64yOQ+nA#g*CvUqreDpl8SB|E z`Vm#_T3J#D{P7aMHx#e%8%ga*HCcCcBB|D0?IZN;G?IGHPiW0P+IGr8&i>?~ozc#9 z*&6~4(KlbtDoP@(ChdQ%%AeKgjdrZdda^azwaRnXJ|g|W)f2x!{pjB;&;NaTusAr- zX&-MpW$(Sv9lf}&a6?f;v|-Q_IU4PF-xHqv3m-)pEM(p*(m654rWPG4lq;^TC_e?PM92lrJMuSO#|Pv6a(Unq4HFRG&g`Ue2=K z(KEa!+OZvw6cotgCT%y;kYycWrJQINRgsDfkRnIUY#9$#_$2}25VEjIKSLH;) zBX)0Or&$Q~v1I5o`ET33D1;6a!q8G+KM5=aT*+RsVrqJQzEB(d(85rKv&lb$wYMz( zj|Ekz0k#W;+ZT|az_wyHuw|MX*qrYMHu>E^oyQHtdoV()R!zGCfN3@Fm$xN&m)92l zQ9PqtHs!MF_MWoBR9UQjeoeRUmFajP%4*m6%W5CJqr7%retB&u$QA`vBokn8@`W62 zPYX+{%x210xO`f9?Hc|comF0I6|i&$f zSj{($0km*oepzh$Q-sT6buaKLkHxFZYx@TC+VWHLs`<8g)%}o{ulfg>9#84?ebW=M zres&vV4^%evuv7UF1qK>$2x(T&l5O%32JO$KlXTvZB^!1NGhxTApgwYdlg9KL!aPI zwsF?mu_I-*aWzsFpRu0(U*NiSe}l3}s0KaXai%g|%NOiX0=u>y2He+&G6KqYmrWq{ z+|!3v_+G-2^3<_~1dd|mDyhx*ZYZ8J%LMbjOAAl7LDGMOV3&OUt`)ukcH%ED_xs*j z81TI{-#=s(E2+)-K`0)WZI(wrD2=b3d@%=VVVGqg~8*VqZYO+%ZKn{1-NBij2r z51Paz%~p5v!x|;h5AZN%L=2^b4l#23=|cx^^G{eCTI)UKVxhGZV;8QJx$MNhqG#n- zvh-T^6+d=4KaDpy;pfR z)duux!~k02duzu-L;o7?4pjPFm3h$5&u<&Fd)N21aqH;qSnfsfgZV{8awW(nWpA0svooE3-2+!@Y% z{x+pprzmw=WK^TMGKX46sl=EE-c>{f#^6q8);SPVcaXPP0~G+9SL zANo|@hgs9=RdV?>|B8ov6LRiTdii~RtWy}Ls$FwY?x31^SD<}K0PHnLQ~`)x%eQXMR(OA&A5nNHqht>ws_0@(+1XZYNTZ4uw~m&G~BF^ z4TH<1jYSmBNYGx2lwL*P|8Or6-hq7}gA9a`fgsu-7t2WmYrM4|0=Jgc)<_$Gj}?%a z%i337Ym@@y85cNHTk~_&R4CTCzR5!|WW3g>g88!#0u8NE>u8NyM{Cp?tua5=<$Ja1 z^Bzyk)SiCj!Kyi*_Z0iS4_^c?R^o^VKKGPpQm{{h5ZJ3{-r@PN_Y|13Uwr-%g|NX` znTN8Z#=_<;DW4xZeCC<{QA2t|mQX7*p;E9%bMdzaR`{O8ee+ZTHhHG!OFY_nte#)B z!q>*Q{JM173g7u8WDClY9(Y^BC?*O*FKg!E(PFRg%wO|%HBR%H5$X1PK@8BjSB5gq ze&QnH3y@(wFb^~|hJ8R@)S_|gfWXq0^JD$=IGl0S)TOn=nSUIr9P9!2NP1BDWEFPyor zv{-zd2Q#i-Rm=cgM&?$-%>$WbK%*D56g|g3B`#pDe)jT+OTL%tG%ql)`$|^$4v_n7 z5A-l=G_eqKZm>()0Di2Ku5WG? zvJ{~rx#Zyccnlv%;U*8*x>!=HqR~%c-3C!+p6{g(=-{3Y6?$hyneQdT$R8@S1AMIO zR3M2-9mpuk;Mm{G6u~Tb7uE!AX`Vg>awjSWUc9KswIP=M{cFQy=O$*(yO9$}xaD`8 z8I?{ovu*C+P)2v(5byV>!F&ESg9bs;*i!=v{+1W4Fy3zXBRIbYj9n>& zI4G{*LX}#$i^pU1f=tqzm2^=_Jp#R(P;k4U6|s*+1@%00lA4y*BVmAo!PCU#CjE5p zXQhz_PU5ECfAL1nu-<2-Q$OAN3s;Br{-#cAE{dEMqFW3Pka=FQZ5nrV!l11gF7r=E z4oCae7{8is=rb6vypb~uE5+f;`lT7n2k!4ALTAwA)y!Y{N&SE4qf<{hqlPY!QSLp(*hO0yj)(j4#Q7(1n zf26bNsiWbjpPMpOtI~N?^}AJ7#yIsO9R_JMWD_Efi=WWDBh)zZ(l~(rloS>|hu~Kj z-?>Jh@gsRO3zsh;(a17O>k1~Qg?L~dGo^^Ye<9yO<|5dS)vFexk4x+_+gWNdO`!C{ zJwp=FcI5^E&?CcSC3zz!(CU_NKJyRh)K5<)1p*-3wSpk%^;)V zn7Xh@217cU%y4>8SR(+GOlnWOPAGyjKBWItvE8QNHZ&h0o;auBLe$=o$v&wVu zr%>~mbC=UXfbvm_sx_Nx?+#@Z)@bFx^r5aq&Iw~jFyOY26*Gd7W-8>w=nF)5#*TC% zXl*ijgOyKYhr@De|4-A6P@tV*k+ulJ!7dJu49gaC2igtkLA2@2z3DuOiBg1mlR-V; zW-)yi^|KZ!+R5%|0exVSF4`oW4l3429n@$;8bhFM56l%2%XuW{P*k}P# z^bW9os%pNs=-swi1X=w5K{u%AXh0Ne?OnUI#Vn z5bgcO&}H!5eB2Cg8ec*)(YovhPl`xMMl@dO1@%Br~AujGm#MuZxT zD@5*!#X#En=glA@uz6Jv^E1qs`<95VN^IfP*cNjcF3Dh1v>s}ZQw7} zVU0{(E5L;AIUNgdDS`tY9Jgq2wM)whiWPDoi}kkI%s~b1stjGUmo)n!mVny-6dFEM zv67(Ke3jfS;z&;8CDXj1FIjppUOo=opnwFonvSXfRQMj9QGs~I9c8g&lqm6bm_jqE zL$!0h2P8iT&x;-L9vMu)r@;Fr#VMuJHG&6o#XTg6{Ab~#Ur=6cl*hIwOfctr>N|Q) zTi(Jgf;Y73EHmu&LviJFS{InnjmBHVlinQnKV;!>k|?)(U^zz#HRV4I5nG z5ge?|*f_T~=M9JP!=EtbbV(Rin@Ix36G0564a1|usm7Y6(3u&vorfuwF5%WA0Uwc2?Q&&Z85Eb{7#3SZJ z-yoyZM%pY&KU6#C7l@o8LTnc^<{R$R2LZ$}rQYi*4N$*{y-RG6g9SgN;=iO15rr7t z5JPKvI7Rru{_F?qg`_MQB~#2ei`5wnW4>xs;bkahL}1wzzs4}$3X&2p?ICmjl7+51 zxKADIM-fGn`V6)(> z*1^1vOdZY>;}w~OUyQxo3xR<$gdl`@o6&Y)ByJ160$|e%Veo|N`fupCFRYy|~(A-() zf_QT+3tq>rD5bgfZep&Hi6hHWd4+1`DJWw!pCcIp2`67Ocxa9{_DO*@qv^Ok;L8DU z;_$HS?-EQTk|N9Qr43{3R+l=2C~{@&gIF7|wa^d4+V}KbLRx9Zmi+Ef`aM^iq_B?r z8UZkMkr&B~D>tH@p!Sn;UA1ys-C8=sX=xi3W*v$J!!WJZv%cUN5}bc37;8u`tzs&t zMi2R=OL*+05dY-UhS}C)6*uNh+0@NGACm|*WPfnBVcP8(TQa=>vU-7?1;{33I#uTm zTF|InJ%Z<`bI-MlX5nlpCETqhwY>}Gj!MTSuee9!%YtbP6tT3Pz5I__9ZAr{;seMw&ApbE6AHwR7n zv~=?>0BsA+3>-cq1SDUiV7Q{1cDq5xP=TQV4q!ed0MCLFy=x~Q3joEBs`v>-k1FE; z(a1HWF{=g5`ivcj0Gt*8RG_iPw^M>)rUEJ9d-n7~G}(%%d|j3Cq?pFYU0qrXB+RMQ zklK~qaq(8vV!eZ~H77~cilkBBgdwUEDV&oaQdp5-)11IQFp&8?&CHwK=&MeiDZNEVD{4(D=Sr2?p>G+*8S15d7f$+mzR7%TSgtXdI$g_gax%5x(7d8waX* zLLOD##>L9p_*{7#CoFH{mE~>Rv}N(l-=w&XU`9fVbqsTH(}qv^ckke@>=X)#djkYW%*F zDGNy(DPN;$4bO2IOSh{On^)b(2p~qP9jnt=vxILIfLPrb6N}F>F{7pd-b2h+{Sl-p zo!&jYB@}y?6;IZnwBPEnzoMn`cxDA>p4ij-a}`%$;=j0o$-g8((cnEE0%!kGl)2$= zzvz4XDC0kI8F}tO^y?f%xB5zA*d6OcgR*$Wx1^YJ&XOQpq|fuvJ%OXLBEU*IxO;zg zYc|w6g1nG&dKW35-(_a985({daXi%9kiKAkBV%pfkg~I#?9u zd^>F>d9ZfQ29CddiHUF@HW0v6ev$KUYrn(}I&Zvo3$-)0diM`6NNremuHMLlOqSM ziEth?o;N|e6-`!^9PQ@C0cy=$UO!IUUxeaw#^IPA?~PYZ0O87Ua_{cPoU*~ZayC%; zFuhXm)Km6Khtx|-VTRetx|U_Fs4})0wW;lJe@I8vo%M znmC~??at!JH~vI=Y|?&bDMKbcM;RZ|QXyl6m}MoW)@hV;r(~X23g^~YG>E~46kkq} z&B1b(4dQ)gk>jk-XgQ$tbUs>V>G?=FyQ8&@Kv$HHL(NauoPps&4dv-KxZK~)0R>6k@JM99_Ah4 zOM~%J^-S4eBwX{odt@DOd{z}svPPAtgN2oGhKRI^6M+)rGCl~!_Jr2&@Q^x~WL>41 zeg14LDonaYrx>l^d-W>cFHTROzwh?=zOR-VE9qj;XcYA@NA77b(U)~r?$aMg=Y-80 z^4&k?Vzzj9<`YV1TI~5Yop~}LuWX4NcmGB0)zjVz->lq)RdhsTzyW{*a16NbfE;=l_fXo(<+9zo;dp_rUGu%#~{GGcnCLYw{fEmx^C28f;Dc` zi^h$*XI}Lw+^AU+2yg#K{{9-+cJ$;`uq~=`iz-3UXE`S475r> zLc4YcT)5io9pw>c7+Mrz;r_gT!a=XRpxP!_X2p7eqQ=1gz_Vvz1o69?I;fDAh{4H!=A?59 z0%PeOD$0|JcEYs&u#`%gbAT2f#?=1i`j>$l4Oj(KRZQF@p7+#e5I?b>voBT%Z-}jmVr%VApMJwV=WX%$X zIwzSYiw;AAd;u%P#VQ)?hX#HnKr8S8b!tpYcg2Y=A?lbchk6F+16l=>quv!{3HZBU z+Nu=bHnD8}p_^Ob^g5EoslZ04BlpKsAZ6Tpm=fzLM=~A)vpL1f=P@M4p6?tOgE9r# z#t@QiC=k&}#$W(iR~IR8Q0}$8B0wG59jha{h;z#d?QT zdol22s=_NGhnZp^%ED7CXIwqCm78wBR^e*nNyN{fvVxp(o5+@NJF?!&nf8%VjWVv#Ff0; z83QwMXgXK|R592u082FAyo^y*r#?v$volQMWqtY%hKLaM&JCpF?EEg4uzG^{w{Tw? zF++oVj$+i6aNz43&A#CX?y>Kd))xJEe(Y*>+GXKa_o`1}gFI!-o`Gz$8yHK2af#hv zGG-t*Nz7-`u3!nkgay(;< z8kuoh$h)6|xMi{ZWwFM1$!Nimc7XMNR37^nS*;qzCOxo;qrwp2tKu05S};ZgE-;2o z9nC~6eTADf&P0MnM%@R=3x+43MhbKs%&DOnW56R#jn%f^`f(K7giB-U`@F7-B&WA-eO+x_aDnYvlmoQ}|S58$wsyCOswdpwx^}y=EAg zRn@7nnnI*!==WA$u5Zr+Y!topKE6G#=2I3^s4CD$aAiNIFue3FxKGt7EFvSq-)8y~ zBySI8y?vC%Bl<0b|J#n;pGr#TTUw<_rZH6$}j8@wt38F1>2b4bYjx z>I`U$>p?r8my32QK`@aFgyQ9LXt>yl*1{HRrHE}7D-`rA?3^3m+y6TyANVZ3Wu}Al z1eF$XF?fcdG*bW=*-A*$9(f>1v~Sn2Ndn{y^FjNd`S4u#d2l5KRao_i@+-$+P}rn> zwrVmFv(%-?d}@SGfLbxAsdy!yi`CXFAf{SQY>As!p2g@n#<$uUDK8ZSbKp>?!3(#E+FmZZIktj|!crHQA(NGz$3i6?}9&qlm5IO-viGccm9D~Xs>WF&?6E9&rlo2#rwW=g%jcMF&K|e7!@7ym9?YP6v+*ryLY)|!kBXD#vrLj> z+C-4&NYj=yyUw1~jvdH1FgAHaP9NJ>bEJE4a9-@A&l>(}<1%3RYJTU8@%hYxL^u=} z{R{Yf*p&KnF9U;U;{5OUzEp*vc`*37{+>ihta0A#y~f|;D82&N@IUhR^r*q0@t43t z#(%=wI8=BW4-0SOa^Y=!FuaXZhPUz3@ZMg9R{?*|4|)Aof6oi1obx>&U*KeXmcweE}rj*sr##PZj%+JGjd}TF! zeenmbpNDVw_>M1>&yMM*(AMq<@QET09j-3O{K?@_f>ipHKJuf0)IY&#>96_N#w1kd zyp{QFr)bRv=+B3KIChN3uB>`cAMr zLISFbLqnBD_!{xUI8R;+`QCc3Ht<*J z)v0=$al4-7w`b{9al7AHJ=uM%Ewb$12}M5pEDI&S-4c1f+U$B$$uU;5UCXr>1a|Q; zehZNn`>%Olw~~Cg{5JnB_7p3ndoh~0Aj1SwT#(U{YMM9p3iyY{a&>^(=23aO?%r5? z`;cpfI|nA@jM04?E5~qOfOo>$F@AeqWJfAFySc|}xA3|BFZt$qZ}`037M?i%(5Rsu zmc2Cak^Rf?#ODvf5iu_6*c-maB?tRQ+HYF>=->Eudn4BXoDCmHC9|#f?I*&YsMmqp z^jvnEA;HSq&|l8MooeTHyHxUt34!cX(weX=n<9@+SeZ=`!?o!poHL)A%MwO{+&=Cf zY2s~~0+CZ{5=6T6Id;))beFZhfs{oDXHUq;vX4^em-MWITMn*+i{G@D<)`*ZlepVD z&223w?SHU7G#4Q3h6I1jMF_fNV148Q-x;3-NVlCNt%KVNjsol~w-V^;5$7rr_xcf> zW5aJDa3@=qisI%}!;4mi7CBM?P6YXo%%;}AI4sD^y03*ib0(!gG`<%CO zZ&&K|@G)-U00_OihU2?^A;aOq0al@EPiH_<%5u;jrfLi*LzWW}DPMoiFKH@K&?{|i*ur!|lUB_KRz>tp%mP(6>j9p6SrYB$) zqsi>6%_2`>=>k_Tab*9GFEJaMzoGBPQNU$tzwhPw8|Zv6kkm7h^xJ*U^fE*PW6$k{ zf}Lw-CiG|#!AIC@RVF zN9IOLnHCrEL>S}g5s}rHo@ZvDdBp>R7_-pqY}xrA}7!f5qP`Mm~h2s_7l1$fD4}XF6wqkPd~x|nb0(-sbrx% zDyeU%+XCB_oeDh>>C}Ru3Qj~VybaLC))DsJ#uFoGsPS#D{bt2JdtPIU0!?bI3Q3+*Rz?UaOp6C?uKCR zqx-I-@QV&Bnat3OMaNhcEKpnST`(^eUE{hCnYR66(AkZ4h8=1?%*8J+7IlJa#Q`R- zB>($lV1keRRL1zbfpvsb5r+Oduv^}5cI4?Y+HARHiYepOZKkU3>{n8gn1X_OawJ)= zl|lIn?s5uMr3;~-DRk$A%3zl5J+Qq;NH_R(NDo;wyTR1bzLy4Ip1lxo{#F9!6Jx2U zcl>tmhIYVk z$^!f1raK^XD4vI*6chE2fRaz%r!Hg#(q-lWf6){|Z}Ttb&Qpd2%wkirl*f@o)-Wf4 z`eUHxJ5c%1WVZn4{Fot~W&q=e;ULP7tBv_D)$r(qqA96{H51kfiZ!8Rir;=S(j}2W z2RdHPRB~zLqV8FY|5q>5<9l{COOIcM+gQ)E@Xtpt=HsFuP0n>#VH!b9HVbHwOBu6O zkTWv(t1uWMC9mTq6DuAE08_N{smZJ5Be(e>wA!? zUxq*7da*6-pu!xFW-44*FOhCV5xAFwaVH14a3nq8g8(yp4*$b9UK4Zb4 zZ_WrKlVXQ>2A4-99>nS-vKx`v3aHwgzoMLuWz%IbNQsdOA{teSXFJ7f^Zv@+)w{!G zWQF0%m}39Tde>fWozT^08h^g>FgMCf(Ty?*ddY+tZgO(`P7ceNJ6X7>^N5K0#~AAd z6kMAa@-YD}&PkRnBchHnh%PY~i002!or5@gw-;WK{0`LXxs z#`BQkHXIY{`Q8bMX9Qi>gt;Jr#f(3rNYC;jaHmtjRKwE~%8~G+^k6nuL#;$!_Q+SgkeUWv_bCnPiyRf@sI`VCRGarda0jS3-4NLg)l0mR z5fxW~Pt!XOq?S&|n*#4}J(tuIxU&32UP6;#lAkf1bcBz?LIzccX(*_od^)6VNv6a~ z=5iV5BS*R*alvd1nVqBSp?Jg?c0gr*>=4+<=Bh$sefDRPDMMMvI=ft-rnK?#2^QB3 za+_~SSwm7gNz-eBJBH}&P}rd?CSLQI12~9VAC@(hq*jeG#bTp1mPAl7|OOVx2|euM8$kiR`U%GA$=TV+@|q}-|XfL zDg{5_%sV$wJB*mdNig$ZMF#qAS0v1dMyLN-sr8KF-D1n#%pFE47%ZU7da%&3abt~s zC|==FtJ6f8c!Wz|AjWT_KpqqrVy>HeuL~5HhA%Vq=l*=BSymlm-60-3Yp@apT;AdW zH;H^qmlqY9?t?@9Fe>PajGL$sfC>dr!MTn%O$`UG)7zpf^;9W2cdL^*MX3H3#vuW0 zsWAFy`YI&`AJ0Pg%Jc=G~0%C&sq z7ByE%$JWo|(c|{y6iUn? zXsK6!QPoCXty^VJ~LKONMpqpB3hxL z5+`v_XdxVNMcHdbu+Rj)(@KTLu`plHjioK&k4=_3O;({gJ;+=;C;;YTO0z}P6pfm6 zx3>Unqo&cbl}6EP-DRl8G_G}jl1bqxi}$01*;D+Jjbx1DR=*d=Kkz>Zvqa@i#9vwm zwE9o#p^GK0gW4S&W>V*}S~hKep;~9dd*SeX+{M^Ig_A6HYN}uw_Oa{-fj&-E1#LEj zXG^lxUSqY|h5V1Rj!5Ws|+;2Y}IYQ&_ zQT2#2G-LBN+L637fIVlA3!hP*$IX*^C-185wLY=E;j8wz$cOX~C9_d`AaCTn_DXCP zCoz{%PXcGJiF6xS8!=~)CdI0D@*P-e=Yw=R;#l@JM?iRgDjDK-M%h04!n;sQP4r|B zL~dc58?j*oHmP)aBl@a0@{TR}AfpMgC8PzQB^g!@f^W%0U29ByspQjEP9p`alE$=g zh*)6`qqExVqYF!%j~B4^jh)@Zg#)vjeKi|;WTrNT%(QjHHJ|uuW}H`Ejj?ss7PI?a zl$ehF%s=*7YU^^w^Pe~OIb!{=v5V|4;~2uOve}29&)3%qGFKJsgdTjY!1E~oXDbiz z{NVgMltxb)D=PN5P!>1I8uKr2#~l0uyA4>ITO=KG@e9tUEW{mi@(cK|oP5XJ`~tTf zIB{c+#*BTH=uKwq8|URe{rv*JSd#vJfvIwP)omKu!fj7E?=4H5_vQu8dsDIVuA64w zFURwpc#U6g?AadYy{#u()2+8G9rJgxvU^uLyIhJe#+h>(A$R|{nkA-+HH~6N4D6?8@#RCY%-Mx1!aVR4=`v@RRbRzW z!UuvX$CY*FRoAXuj`HyGRqr?7B5*ACN@?s8KR1EFx*2=SyB&Osz|xg6XPaI7^!3M^ z7~{+g8e!)*1{(nP2baW-qLze+^mLvM=Lx>FS^>AVu6&OLT?^akMr{`NW|zmXiEy_= zZJoL}FP`(A()goV0O&^{`0Tg`%d%MyU;^HV;pPN!uCQEs*9mV~tl8*qzN;I7U3TeR zM@P(y9ktIka}_7CGhDJZkPv*F4Al#~v% zR)Ui?_J~*4?9Ti{((Ju$hwQ!2OS~7grJ|2+$lI7|nmlX2*Y00^&vsR6T=@HE?e2kt zb~hyts-(G(cj1PdjYveUS3s_#F0cFV=}!t=J= zZ|9ou%%2Nzbdu*pDmvYRt!8?3H#+!f>`K$y9{6)*eq&9b&y$+_=-6E{F<6m3U_ZsG z*uzBfCLS5-r<& zoT{JG)zREtD?0~74Nbv36|#V*_QEK_JDU%X%vHEU%}n9GYc-M~!~~Feq*91Y2_M3w z#;Jajota90opb{SJv!Yg-;>3CvkEq2&-qID9aC%+n`jlY=QSVl+W&w;e^fX1n$G&U z$oG&bHCn)gRC3J(+F}8O0qVu^bdhQ6BvVzFy_XG~g*s|t7u$sPW^9)KJLA!0T_ikt zslvO%9}(C6;}2MhSJT{n>RqyA>b=7MHM@yj?Q$bLFWrft#00(-P{gWejU4)?%dUl+}Ba? zx>55}_wP#oYSbDqd0(9$yNIk=83R1WWqcBf4TNHYA?HRpq_-^AT0Z+48rFzC!kM6^ z@6~zSV^$LTcy864Ss1$6&ZZ6BQWl))Vmk%A@qs=R33ymzGyV;tTnd}F?#aa>bjcd~ z`%1meYM}4QWU6z`3(PCBbNBn>(o2+kq^61h69Z8Ctg#bRzBfGfi@aUCQm-{OU#Wc% zRkp(OtP~o)9xfP2by;H$WOD~G*iStoBlnMFx9(`%k+yMnjljkY+?RxZ)0HGP?&Zd` z9o%s?VglH>f0ZjuUWQH^tnM51y^=4ZcJgd~=-l6Ti`~O5ACGGA>?Eq9VH~5l<-6)$dI#B&(*~&1zI-{W_P*8P%)Yd=i*)$Nt~{ zI!fPqL8?14|DG^*xSIAX6((Vmo6ti7d{iTyC@2X*D0V?+zNk_pEJ-Ik<0e$46P|Dr z>eC5J+=K)PkNYo3e*Gwj|AEg17*%yoD5*aI(b znYj!?Mu{Fjn%>bIpSNY z6tClw>^k&!*ShSJSgzM)cW0Bgxn6mjSRijg+gy&kZFfhyQw`6BGt|+VdUd;DVR)J8 z=;n4ssmcsWW`daAe;Tf4i^k8evDMq8;jkMjb~>HEtiHw%IMQ}ACKn>B#;aj2DP;kbGrY%{IVD}p#uldB@F zsfJbIz=!m*E+3rKXS`j+iX9A%U!zJG?j2~g8`|jEx|#dzZoZ+RzxYHiFvIHF`_2b8 z7Ns%V(k=`;*WI_@U^j$K-0;t3u&Lpf7wXWjG=~#?6WX>rvR|lFdZ)$SrG6ML z^}?m)Gq~8`vTpTZTUY3(@tKpxXM4V+x8J`>>Or)UVjx~4=1X<0=Yv-!(wtZ+~lz@qlIhOjny$J zIn>_-f%+qjsfKmol5--5TRh!z!fAP@og7HwO*_l57#U1BGPvbHtKD}YG1O{?Lx!`@ z@M{5UR&Vx;Bi?A+XMS;@cTr2KanEZx`)MWKE~2N0cO<^DCoiR<>(~=+hq(HE0+sIE z^LkD$-GzSR=2zXE?01vv(Ge(vH`)?*_w)NqwZ`t(a+*}-2Bsr{d!s;S_v<;CX}F%D zdL}syx83f09(u2i=Zgt?uO;k`=lMB}n|dFo_t4xd5<3?TlNyi4sBd+RM|JzI?f(=r zZ=sJ+%|814$yDd!jV{q#;Z2x2t81!m&3p(#Dzt7jo*9%|%kwxX=Won$nQt}wsh8tR zoW?iT3&7ri1Gi{gvK;b0&o4k^4pn|RUhGudZaA!C;P9s^nwGs6n1P>XIlO$M%kmos z_Hxh%_ux(Py5+zz`|O2%X$%7srBGPUJ@c`~s?8Zoune)*gF1L)! z1{2-ka)x6#gG@}1SiXJs88n61?G!_0*zJ_VZn#B9sz4{Xku@=z4Q+rJckGqD<%VVT@2El3Le#?btwkiBk`@))Z#6_!en#7~xY| zobM1-uVvs}+8JJc8|6h+x1E5a^*0Xe{8YIu2aevvABOrjlSk^l@ziTGB#qnDrCU%) z`UDEiNXwv^4Ge{ba$E{+t5WBBpJ8TtqpihAR0o?TBr#8GrA_s21E${^-5X_)y1cRO z=x$1A2qedR`~W! z;pnQ3Gn~SGt3Sp9vg5OWC2M^*(cG!9=H&~V#2K(ZRb$Hmj7totwvA|nj9U=?=!KIa ze1QnRSs31e@JAP+7*4%jkS6?Q(`&KjP>xHN&F$*zO)4-=c&WfmGG(a&qpxph!8Cc( zT5yvwMGYr>W}5KEaD}F_TdsY8I;m;@EKxN8Kuixno_2N^?!4PB5>ji1$u-UXF8zPgVLx!mB^5^DyS?q2(4pqw8J5aaev)!!$n#kj-`#e`$qe(tncFTdz zoAl=7n<;Z#;~>dB{@M&tW}85KiZZ5*B=`91Gt!jVX3)AR$EA$9-s!hcru~1DGP3XA znlc{cK$)t~_VzbYCh^&_|5?hYx&Ix?n5J$?nPMUSTX=?SHSizu%x|Pjm2!FqaIfE_ zsm9B%@l$JB)jNCO;mRgmrt|XafwZ!zTOu$w<+u&h6}yY&d@VcIMK%|k<)^=< z%Df}n>-Cl_opQ}Cry6&TNF~=b9`M?iD|V6W*t5DOKC;tqpVBEf9qsCF;$#)2{FqYhjh$A8xd5 zet&JVg}dUEwOlB^ZO5(U^<;Km>&XJ%i%qMU6l`5TTl#ofL)RXYw!lQV(I!V;fwYjYs(Ua`*CV) zwrc7XK82s+Ics}D5NkkZthK#L={1yL|8DxY-P%&Ge77Ofe^5-Q956i+bLAZ;ECmX@ zV!Z_ec3DxbVuF@9Lo=tlSAGgdcP0(>u zyW-v0b1z$QBV5P%F~k}x?vXfkZXQjLT;0GQ1xzR;g38({G)(_rPY0t(~dlL@bw^@B$JzZeVD? z-S2mV$>uzrja8(M-)^kIzG}1Ubs#hW;V3=oN+ri~JSVP($I^~U#c>QKAK>R8x9c3_ z1M(0~mWS{Gyrz5b|DB@3cHV*pUPeY}F(LfyNFVl_LHK%5c}85;`Hcm}ejt)eg=W(K z2SA=oj|jcaziElylaa&8bYZeO$R=LE*7-LZhMO4aP{ot>L=2yk`R%mH))9mZ{AQ_JYi87h&0ehoq+TDbG49Y&(v_-6!NZF81>Al?qc&Yyk3S5x`{_nOVVnzkqVY7U%|KhQXn@K_mt zAfL2gz8rh(rx*fVhhVX+#Ks>uk6$N_CCg{;e0~zf=Mf(Hi3(UK-sBNvd#wA*??DJO0`dHSM-=x|q{ ztzV)Wc7N8V{DI%ZTQ}Sv*m^sy;15hV?=4%M_vU)%y=jBoG^s@%aZr(q+kXAh`fPO~z<^LL{Z zDjQdNH}{@RE338mwW_C$CvY`cD%?(Ce#xqAdVeeRTbw#bDG$Bg+x2s2<9IejH}V^N7gKZBWbe$5 zhWfKkC!?WtSuZ7`-&mK$fo%@Ja9Gc|dYq$A8>4fVd+yrhiF#*x227biV+P6mF5rD~ zR`P?pF+PeJ>3qS66LeEP-IyP{#K$rGWbd3&@sj&onB2bZnQJZ=jcr~+p@-tjbmcGj3@I25;z>D|m}l=YlV;YscRzTf6`Xnu_ItENGI4V;9- z`b}MPti})_BWPabLFd=wBEb>PF{9`tej=`93aQ&X69QABlQw%e8a5(*yvg~P#v$Xq z=C$lzZ`4bnyt_rt3Cr$w0Is}u1V;rlG*NFIKRS2!2Bx zzf6gGt{7nU9C+PV*luYapy#%J2+zAGi=W5yZt?K5k(0isAIuAF z@vJ-)oxUe4yhG_U=WtnQdlsKYM7`TQ*6!$1e){i(h-ba0l<;m-^tc;dx+kmo1Vza{ z5e-qaT?DpZ-pw9>EaZSVxtNkILb$NJ=KX4=h}YkrtYH>#a9-|{>y!rb^_s-U@1 z?d^HhCwod_9np*ZWq0ZaH<>Jc$3!wK8X!UM+A{zAyOl73+_mI>!Q?hMMf1}2lrzyp z8l9XknutSgQ}0W5|LV*;fN8v)>+Tp^L>RYaJdtDALW){-)d#vAQj7kp-$GP3Oc0YeRb(6wx4!aR?Pn)o)O9Rp>n7w1QNPZb}ynrirOFX`0p ze|w2Tmi)|X*6;3&UVJ|5clWBWfBx^7*FpxWd*1RS&p%q;Vv?5Unb#@?>fpTP2YCL` z@{`ftZN)Hc@8)8dyo377C-Ez!WhjY;%Vj7;C9S$E`sTqhG)6;zF+WdwslN=jCO?x~ zhBMC;c<#$8!0(q*z-m@25t|hg?eH8l=7H5O(@r}tCiLUj7&vT)>rH~KX)DY~CmlT)K6 zUybhVFFXB#Ae?48;>nM)@{}ekJDsBMWv9=<5oOO_8WG)iFxb{L1uW+P`!q~+9Ssv~J7#LMrGa$RlpScYJ1(>h)o?7g(GGrZxG}nZ zU4C0*^luDvLLsqb18p#R%RO$LPFhLVSsoln-m(s@Q0KB|QHyCubsQ6Ybp7&tmQ%mE z9sR2FP@RC&n}+A|2kcwcaoRcVS>#Ya?YIrRt51Zv@Q<5N3$?ZfEC zg~7ISW~lYM$YuP#U$mEj>vk%y^IS-5vJS1dbwqJfr@&a$qYb6`0|#$e=SDkwJd3WFI*xz*rcrn3 zqmehPksft^g&P@gk#@%DCImFx#!=%8sq?emh8?0Bqk`O_FM~aUpr(UALxv3T>Ke@A)atV#YGmLD41+`;_|h3>{A0rqZ^lhMrnvI z9EIV{_3&%tQEC+#Hy3T>4;;B=9a=JVq@Lgm+Hq>UnVUaw{FXI_S~22k_|@?t+ozCH zdpj;>KO_k*&Vm?=n86g;d-11S; zHbmaX z{?*5*TUSM0vBAL3Th^xKTh$gh2KL;t21Df37{ZlF4E!!Ta&^VF?rFhsSRX%OvQn1Ah1w3oSR*KeoYkU*qqH>_8Jsb1wdv|Bv>H}wjKh@p_< zM>n)v{5jLB8#zP0S{#6OMugnaqw?@pa{n`}eEv7KGQqB6ZokDeAs%-omZ8486ASA5 zmJ^Fq4#zJUXJ=wz>N3=dcErmf^QWl4oHr#&q7a#1BnMHngGD0KHjBhIe!sq~aTmI` zJH+SWW>I=h3(CiNlCvAxIc0MG=JLGd=UJQqRrMsDkjR;>P_RS}6{< zn0?0sQLtAatmmdiJNA@W>oA_|DGq-e z?KoI$t(#`|>s-}#&Q+xqnYg5&Nt1W@*QU?L>SXX;INfj!t9-2<9Jw|XquvL!-jG_% zaMna`(#A2Tx*4;lA@t(O(MRKD;VuXTx~z?auGdBP(Rgu~k=#;dJ(uMm%GTfpq8isU zLW`#uWQVxk9AYqSExtFpm+8s=tsF^%tT>&?#^p&SsNcwwZU=8ngKizhuXHfl3yOvw z<4TL7dl`nLnf$d5)e=*P(hV&~H2KfiA5Cs94quNhwTrF!xBbQ?fLWb5)4XAzjchHF z(WQ*y{4vo-_ms)D^#9WKHtk+D>_joy(o}GnYHy6sMgI_ns4SLc)uLHw7Y+ zgcpMtNkEVn3HSS3`+t%^@#)Ox`P_UyIsgBDUwiGf*WP=r{ah)W?QT6+FpMF86bcD+|pYX_a~#3R^+P zQEK``Bl;(^1N{r;MN+eK+0PE6p`OUupXFl(HX--2ahx#GR-Ya&8rVvA zgVhy2;&yIBwk~e#*e8@^(v2bnXwdLT2wEtaHG)T3GOi%f$2iU!WoOUXpVz7lW%hym z+1W;!rMHWt1P??WV?tk!aKH+cWlNne;mMv2vf zWGY~Kt5@J6MIj z_qD9VByXz&VDsQ69Dm_afVAL$L5`)v{>G@C>lvU%V8PC38+pWM=eBoT zPryuG=3!f8HFd|0eyQPb(RV*~TV}V@972oZ9Rmae>IH<;;-7RNPH*-`UZ(@+C45BJ zFCrHCjvjARAzFIhc*DNPO2FzgZt$^T`-H_GvkKuEOu;=i_uh`Iq#NgL{PtLv&g)q1 zpZ8@F%0i!7U_l_VGEo$~Lb}Z$O6;(Cv=ec)ny#_ALr(CPNjazo!n_h0 zdt9aTPMy3XpX5DR!%E&yS^Pi@dDQhF_-gB%GbpLyWEfEFv(#x(=e~k$k^L>sLQv8Ds8RaT(TxbC zw;>|#jPxBD?mvp2bYG(_|2=y3s_%&Q0-jwt9(r`c(@)4sRbxk-%DaMqsnNB@}zn8YMtBqw3m}OMoiw{zNd0od1 zwYhRAfkwCb_Y@Xzn0(A@<6ZFbPDR86C+|2Ns1NZ0FB;#ZvBIUM_{HWU?k=)0PM=@7 z0^^h;LO+nrNPM4E)IW*3S08>ZEh3_{09*UcuI&oyFRL#vqmL=v5s`~3bgdb@%KQj` z@a1QlJ=WP5y>+g`_`Om=9;Bgw)rZiSYq`L~SMw^{oWj<^SY31c1WMwQ7r9vS)GC^M zA_3CUw*ZxS`u@|Up8s;}NXGEE6iU?fa#O&njE-K3;mxdfpTaWjS$PJyXwJj2k=uRt z*}-Bh^cka@p2zWngDF``kum?U{8F62`0eu;DqVDF7B2_U$f7B&$C5ghGA0rLY^9<)!3tmR!vRC z5WBo_zr}Z{^~I@MFds*#9SeqkpEQCDKu4et`{(@~(bxF5>u}M-VC++9Vw(#E_mj`k zCpUXBZxgeZQk^{QpZ9|*_%1)e!^A2Se2bnPxDROdHVp z$}aBcsroGeG8qq^>X)rmGja@V2Tnpj6Myi1k#!bEVgs0S4bC|8vclMg1pzV+i|?0g zIoHOMs%XHMrQ+r-Y1g&p7-ZvBR$OKAH5d~}nNtj~DcDcX+VvD>sDaT63wae&%rbT6 zG-0b^nt#BW17tyy=N+Wf8JD%+3Jq z$4IS#&oGFZ04tHmp(-^TI;x&uoJ%$e0D$9tTMEU14% z3*h^+cIh{7hj<_LYFdnL5uxMMi2mZP=9@1;*zgfO#T3fx-s(P5?ePS;_VR^he9h(Q zFZw>NcAnlU&sUJhcY43bPX|8>#UR^3>=C(kJQW93ft|vadEIPLP)_zj>EEh_wlH;^ zKARU~LD=t(buS!jub0f(vB9LOfut-5KQy^XH8Aph1uF#ugBW9 zcHfaK%haH__~)X%7IkJOC|$_9 zv2ZqBKO)}yF)>vba{m@awc$J@zilbRo*=f%%VM6SXEXM*R7hN@3a+H&{B^5@TZWN> zq>_RG>~DQQirp^t0Hu`NhvIQS*Er>di(W5k{YY;+<&kL{V8qYAXIv3@1LjJ#4Pv~g)G@Cr1&TbYpUWQcHFy90Vs8$Shc0KIA4pj_n3B~o zK&*e0jFmxqd;tw4d}APi89-$d3)LW;O1NMiV{lvE$?|L0s(Tm+e_J7EE*zI%3^kL_ zW*`WH0I;M!az^|ZqaAqVEJIm5FGqfGWL2L{Fi|QaPS9R4{Js8{Opow{?j(C&q9^CsZ@;bPWEc`6E6N4#3;Qr@i?DcT}cp%|=xMlU9LGp%-x#&EgiA4qW zk`_wNGbXdR8&SbDoP5~aQ=lS_Ve}7kH4?Gzdy?&dhXWSHk7J|VqmpTmi0rdaIO{;N zXuSLE(_UW%JC(^lQ+8bG-3JDe@@qG!q|bVR#doNTkq2bB=2n=%fcrI|f1fTicBGOA zr-1xcb*Ngo25!7)v6YKpvsyfvS&*I<)>$27{FY^8+Epoj7OU#FgpMcPC-eli_5mdHyQ!Ky2~=5O}9VUbfd;@p60%6Nmsz-h@PYar<^ESNcT1gi%}Fy1{s*{=A@ zwZrgOGKreB&2aOJJk8zr4@olY$+?`jeZMQ2#<=G?KQpN7Im@W}%VrIMG> zT7{gmAssg5xsKBpL*^DF{9YEvJJQ|hj-GNf%wI2Vr(;{@6b(G5Ho&(Tdmwqn8$91> zH-V!+E zZ6&F@nNs+4d$G8F8E)yELCe(Mt1$@(pb5=9GF^=u4Ykrav$YIlp-$C}ce*>0b*uBB zzl+5Dbp?P*?`eB2c&_o>zind{{EDzmo+%PaiKd*-8#9jv^dnPkHcW#KAzPSA+Kb>I zZs5DqEtPJd5^xucfflPH*@82*TX#uRGvA$K-x01PRbUz76M=N3M=`-1WbS&?F-A#-NB-$Z7)#4@*)( zFQ6&hR~&?vYM`YRmSLSJ@uB*(SLlq8j1w187xu6Nu&o0a>0N8F%y9BWmlbl~KC{4T zs$U~U@?bRlXQ{A~t4MUXa{s$BjI!2G|G5b}=o&uR(*aC``(Si>;Z-g&8$mx99i4x5 zi^>(pyV5BOr}QryrJ0wr?DmZM>A%G_=+CJDZ1qz|(sxB!bi@lk;*87Dx7wFMXfYv% zI|qLfqK5Mi)@R`E^^AE>626y7$3%aKox;^&1qZ~ z&@~FPaf_yZ&TDYlFd9rxQN9wO#ECZz_A5Kvr~D-wcne-)FcwjkZidky6A1VN!ra969kukinUc8Z4stfQiJG|WDF5n#l*Wzuh6jMfeNe7b*4UGJ^ z*IcJ=<^7wy3#E8?;`FczWbP(*hRacnXSwKz0xwH7PMLfmd};;H5PKJ~+&Nz>ngl_g zBkC5SAP>zORau3v#OR1KuhJMpm#5Z_6Hro~@DA;Q4|%{2!8GJqE*f+lYU5GjnGY5B zqmLrfl4#gmD$2xnJF8-SSzT0!gE}#yT&u#eB+8>WRe3O^ZfheR)-sB*D2hC>9z(&e zBxQ=p#TmdVJSeknBk(m3N?bkyBoX&06~Ri$ZNf*ck|C%Pyi>7RjH?%o#AhfMOTg#+ z1TL?3J)Jv3>wLhSU0E)7i1SF8#qt5^vD3_7Cpw@=3q|N@%wcqHhN}6Ei_^@9^oUc* zV_*x6<*(C}Bh&v*CZV_l7g^B}E4`MP0BX5m1(_)2{0HI!3MbCK;8uzoPAM=`#gnBU zN|ts_DK7_Rca(s(^GFs}9gJyMT1+2lQ4PO!Aj9)t1{#8NusVc8#gGKFnAdUcC z&r+ols=g-V6%kVJ2&srTND$YI`3*uI=su_AP=lc{VY7-KZxs%^y%`Tr6ie3AN~oAh zt){_I2??sH^`I(Yu&g1rx~K=J2=x$W9cdg$o%;YanY32Wq5)93E<8M=Rw*#?hl`AZAZR8ud6f$>I;<9Kqa>Zk-y{lA1#-xKjsI|hMq%>m8a%I>&jQql z|EgM;GGa%zAMI1+%)$`WY5oLC=kU`_g!?D;fcV#8aisTD3eg^3XZUF$6aRKJMR@LZ zenHd)2!F_YM47eaU%$!_F1f0N=B_`I1S75|pJ@*C2Jf*`HK3d0x}Ib(_>tgOTo5^& zN!S)Hn5nX+f@o0}>_9_fW>c7maGOfE@fK`Tbb;ue?Ox1a2@7^sTgm0w9}Kh8!aa=n)Xixl$!r~&7+!OuI&{T@Fdq^oF%9q=b8&ejezIQ7E` zWra3{jo`#@A^=f|56NNB&_Ea`r*gy^SfDIK$m?lfUJUlwZFPo54+tuJA*83whBnwa z>A9q{t4L;R8+@;7BbzHtiLZa+A;}}A#(lMqokT!hZ(32*8`_u6 z_#A0`!=}oeBE(la0>$nDYbGx<%g0nmOJ}!%XibDkB3bRa>AJs>yZFO@5vn1amxXM7VCDSJyzB1%r-aq2$w3*Z)#urYvH`Ym-IdyG&~hBWir zusdD$fVn>Qz4+c;YM`sK{*JFl(+)ek%N^KVMtlOTN;BVHE&38zj9_Mq=*qok*}K!s zr?r&VKkF~A58hE;Uz7{5(%|u`PqKS?WdLDNS&(o3>=h97mMcas4*k?`TGnS!RC?*U5-TTAL%%==*cq%LCUe#RN!6NkXzm@0%?Px+?dUy$|> z)x|5Lc|Q<+W}VW)GvOmT^ns)_Oi08bzG=g|*>eV%reVnT1iQ6qw|BA_%aiddIAPD> zl|%3=C{`v9SJlP7Y2(h)!t!?D6EJhzkM6-w`7=kyAv z0elhE$&|XZG}UZ#qyC2i??O(S21#r(vle3!^{4pw{cF#oQzfDUP`%z zr6QXB-)K^Tjm+B#e8UTr+zvF^B_3>qR9hoah0374*X@2nU-B|o*&GFc-I8Wx5UR2B zv)kGMTTGQmA?#`O8AHTI+?XJm8wpN&0kt>xIIX_P7qxdKUA|+3Eoyl_+b?b9*kEZt zP2Ukw1r9(p_D^Pa17=W{FHN!2l^Ko~iOpMx=gb9i>gB(SB&e$-gp!62Q~=a#~R*6a`XBzR#KH zX{ZF(hf>^rjWWgtpYlz~p1pz|bp@nd~apl zai(1`^Mvp5V`{RKCd2G^@7c==AO=jg#9!Iri?1$0uRej#qiF>4Y&^)cdAx_0512Vs z9D1JD7#%XJ(WjkO{ou(H_VUFNxNr2Kfp4jQ?ojPqxUN)*%dgrDx55VU>_PXx~qR^J-RsDdWKAlzZC< zF)~xeb%?rW6+!fDoqY=ptl0$wr(}cVh0>-?$(A4`uw;%hp;{ypXV00G&gNu5mJ)j_ z_f5;Wlq$Wq*asU#+eO=@2=wfnO1kG7bJ2Ys4XHVu4$SCMEK_c)evUvGMn2Op_Nzum z>~+bUTc}fe%?^;fZYKSv{>R&iosy6HbzB__|CBflNaAnv78zV0S`O;~l`oWvI3)cnl^Wb6*$e0gOTlZ62?8|Xxu=PtMG8Qeh!fnDHe71 zQkWe)XLT#o2~Q6M;>c}O7KJixH04d1Vprt!hCNUXrY44a`j_AG{m z#&;R+$8`;*@xm~EnVP&Nm^M{vpEaN0UInGl&R^f5N{CnQ7z+Dg=d)IE9LWOX>;BS) zOOCk%GhR`EUZ{SZ*z%We0N^RvLT^B)2kpGlkd@QMj3L7)RksZPSLwMVn!` z=!6(^x=6WLiwO3z5=_{`es_b#P2($cK+4`D1t6CO!oAvy0UMcl=|*YrTh&Yx4K^O= zvi2bpVf*kFuA6E~>(hQv98Jr52#L1|lUk9S)1vaS>-YgX#znlAq#huZq(N~~rc)$M z$q?V4J4Nm#^T~s}=X)-b7CfhWkqn0bnV27I#8EVdkFZ7cdJS2t({Ed+&j`iu4Pq^@ zu2&@>-npQ!3?6VO)v~{9*;#QWYysjq4o9Mt^#v3ldMD7wTy&4HvCY%8f*l-)(H-fg zipXmo>s)XJLFYG+u-99s$X%sM>FekzRDA{D1y-_JYmvk%hmZMF{Fvuql+;-;LOY89 zB=v$dtKdw6$##YyY_tiIsKvGm613UiUyb2tvCip(AhuRfobJYIt!2&U1vX+^Bs-|X zLI=gGN2>(jYb_Y_REN$k|V^L9)#WKIGIs`mqDdi4O8S91Y)@%KIYT z*~X8RkgZ+2S;XOgk8Jx5szf-*&?)zj22?=!pKeO%=~GKm8?QD3M=tS?M8kJW;-|W} z!-_x5pAA$a$#acK{uqxo0;eg%kMmMoqc&n&u_!SRtesu#<(xYW2Q^=cv zZG$=18m&-WtR%Hl8747rNB_X>QIk7=^(xDb7AV(1`EEs&uw7*|q*x1(<9%{e+ta`PYY4sbbHHv_{e$_g^UB8c}9 zw}N-iNu0^gqgA)2me=j{y@|`)M%_6^NmP@h8D$|#Pt5HOP-s{32S^8A*Ij#vQZT~ui#Olw<{Ag7rq;<#^lY)1ZKkbX;OtG-D6eC zUn7#@%=q8E8cXirNS40Js}Y5Wn7>=NiGP-ONUeh&>Tq3=O7rP|=^_^qsRJpI)Xf^6 z@2H0Im#8Epr%}1&0Yhy(9*&lb!iV8zF*Tyr0tTFDnu+*94lY>jz6tjczQ?Pk=fm|A zel~>tsFLEE<47Zprvt^|XB#_j=X>(iWXWchN<&UxyV&SYK0(DWp|cDOKxJ`ZYm%?T2koU zLxDPMUxB`OC9iWxl;2o4KZdB$cv|`NO9Cy@?PA%n7HNa!|r`PU(QTpR6aA zkd^iR76bgPNKu1jOLGZQd;Akhg(Ca98GnElX761~hF& zMV-|+aKkB@HHFcax8-_ttE-aZon)$r3qG!P1D!S!K;I{z9_wPUM9}%R4I|fEUaqQ< zLQKrdR%PQGSduFkoAtxi-fq@wHq#fe-lSL6UKgdw9bINx;yp#aH;<5OCMgeM&5wR) z4C_b##ACWRbG}93mqe%E=Vo};>W8^kkd)gbXE-2D6o3p$n(hJQCcANq2KQ3|)4R-}yMeIyJI1(1GkbEJ7-5=_B&pMw5&k-%`flr^J6kaqjLT=N4e9 z%XBI+^;YUGuEa2gyl@VK>*%N&)Dh4WD~L0b3KqQVQ68w>9K$Xu?)cuHnNbsHQP>xw+mT7OlHUZh6~4h=t4cAI6v2g$lp# zxy^Zvt^9%~%eKy3Q3PzA5<_pJ6TXFdO@PIWDWE1eh8U$uH<>u_ikZZ+g?Nqm6C*f> zAj@n`l1X;J@H zAYvapY^o<$1@Q%|ahlz|6G1z|0iA*D?^QvK5ZFB1An49lXkM=(Hq8;DDXe?&p``MU zOFD}2g_f+P^m6!Y@DicrF%_|$or-Kzk>oYs{DEjAT}RTK7>iu}0ow>tAiPI}p^L*@ zdO@hu!CSyC8Yyv`5~VSvatdqZQ}hRlwkeMIC*Yl1i3K?*50GGTB$b?j5NABahY`Gm zOdKK!oIAJr!ct4y#Sx|}5HU_1cTE(c%?08fE{Z6UCT#;Cet-a+LYk-G0BV%*DJ*#d z3T=~)+p?>PP7-kFo^;3#b|gzgmXZrl<$RzURShXgW zhKy~*;cwi`w@^*K+PN}_?Cw07IMpet@DL7qwCK**PR^40s6>Rm+UgUMN1A*Q=c)S6 zrx2mxqK(&R3pS9 z4I5G{Dcl$}Q^48ixv;6|2%FB1rz!DxnpTgesrh)C-jA<)e@n5UU-xdgPG9&F(v$i^ z-2ZG&AuM~yy>yeO35R&DYja`S5)rmd7*D$c#nYI)>>+DtdD=Z$PMbQ%v6hP$ajJ{) zsIUD#P3Sw`Ramw(U7`3~q2wn)3bQB8M`6g8;0{8kit$yb{gDD?oOQ`+mZX`ds*R18 z3gQ2fPjC5#*SppFfce1oy*N?EGwZBWS^dJ-O9E08v|CM7>9^;#D0!3ty z(ohn5jBE3KZ*dVpEdnIZD!^A__OY3kch(#dr7rAMy){=+7rNV1bq&8&X(uV+WZI$h zm4Bdb(DM%Hcf!HM z?yO^YQQe2TRop2o9e!wd;^K*;O2~JQG8pvQce#i zhE@NT$HsjiA0fvmm(`K>FuPbXW-22aqm_Jodpjb4+f0_g2I!}ys0#7^nyCn67kPDD zD9kF2&L4%ou=;i>+HU7=^V}@V_%6-rN^zEw+ql~)1Ic^2J*ww!f|TD$g>^jnw0CqJ z$1*PAW!91WZhhuYlN&5^Tl^LM&i_h=9@Ss*yHjuCV9v6yWF#KT$^1&9HHYZLx5HbK z+e;{}ga2bV6`fe2ae58c)zA4Sm+8G8Q~#GSsgJ|aB86)Rw}*~5#UCKk@8wnc0r}uI zcDaF+9D!FLk>L_D5jV|47v5v{2LCF?%@hVR46Dkhfi<6~RrpuVa2+c~4}#pPB` z_+HZTbLQTwYegD=d{pmlisv^sGD4wUyoGi%gsq9+2eYGpe)E7(rYZjIthU6jgEM;n z3-HO4cwg|XM7;Tvn-Z_fS@(0J8gWo<&c|^>eFoRQ?+KJig@UA{&;Y6^q*2=kCy%8~ zalE1zV6-`>q<}1iITZzdw;I>jS}%9Adv3zvwVai|BL~|tT%e!F-8ZfAEN;gC=X3td zLVl_WGZIvAZ$=_NC+lA7#G`@dGM&_tLVto*?)3xZ9=xFAh!-ycmvIO1dHE@SAlM9^ zW9GSa3(rnW{$Cyy10^^Q3vN%G48P5LBi^-%3u9Dx)IM`#+NUroSG=>v%{!~(G&b?v znUprtxV0D69JhCNCmVPNJMf$O=v*d4aozUx6ymt1P;VdBS4`zBV3XDN(w~on#aT8K za(D4vZ5(3{r3KaLddy+sjT(K>d7BN5_=idchl#4^pkvlGioJ|ugNskMCN3^&ElKo! zx9@8dpH&0_JzBZK52IByPc;d@SfrHGJAo__kl9psL@gq@8n-oOw>Z#p z@<+VrK0;aa5%g{C_5tgB;`9Nlr*8Xi1k39#m)9Ic-F5)$>cXEJDkSzoNf86mc9VEw z2l2F$U)h_?pq!-zcr`7RL*UXmddrn2ihRO;WA>wW_^{nhjUNfxNhW!*OhJ z@Tn?}tVaCoQ4rL%R6a09A`_ z4M)#U>oSQ8mJCz)0$%uVsJF6uH+7EN!^ML$3D0OQU_xBHc{%Tk)Q z-1+EbKaPvT7$ad#r@z$E1ZBxFW@snvGl6f|WU?u2w}a?neEc3{j&?HXM~1fX|Mnj% z_idbT#LL6rEOgAnDs2YS8i~*$NNA?w;Ic-)df9?^)}_Ng!5Qh)@JTiv3uY$b;kObi z8$TVENX7Ivo~;}ArzU<~bv5y1<8GWxPpyL5M?U)LqD1_OPlchs`8pVi2QR3etHS~J zDh2nYaSYRXoNTf9&R|*-`$5f!WL*5QV4C@E?_gh`;@zNpLIR2t1Hh^EaoT%s!l$qY z>|oSwUZGeZ0R_0jR_sA!%s)|KdeT83o}2KY+;8u&a=Tg8Gel!1<=l#fhP(K7=@5}S zx(K^;g#?;a^iG#YS{&u^(@;g-E(s3VE))?eNtH?|#j7o~jdihw}d^cm0%xPuu$ z_%mVSFyR6*;Ryxs;A(O7=lpQ=#0J>AU%rGT$dNBh&4#8<=c~C>Gjwg!UoL)h`-Vi} z6WbGo!4c1uL7(&we@um;?Tk!&TB5LO`v4NZ9jt1STl3+UhKu5GvhwZlJ6^V0-nP{S@|Si$Z*l(LrnshrBCGE=#LT^4 zpFN!AYkVY98`+clW?AqAl3x<#40aQlQx#uZA-QSQ_9J_Uu&qUjNh44 zaz7l3LzJ+55--D`W-pVqbY@~|_W|U4d@!viiH=RaMUav9h|0 z_^;rjI=f`fcI%grU=eV^*F}jBM zmPGR&a5*Q?1GZOrzf1~Plfp?6OUY!|5*w?@U}$R2=t)>~%UL$b&{Z;7&K+{*PvQ{@ z_u)Av`WLCJPOXVLZ!_}TsPy)SnU_=XXw?}qeHU}|1mv~O=V+K`NJ{b!KCOhdrBwZwR$_{zGS4@XB2r$_B3u`hj=ds;t(9#)VC zT#1xQQe#xlhDYOGn!4S~tc-5f4DJm5X$2a~Gt}uY@7Ik?f4e5aQ6j z?L{kq{9;a8L86bnT}EQJHd}WFy8)P$IS9LlLjb>0F@@3vfSP4V-IS&TqWk;-2imPrfTPdO0u*^i$^m!yYda8%*m&-Lr)Bfc~2zQv?C|6HHE8dz6=?>TE z^frsOAJ(ae(HB)e62*0kGOHgKbCwn8i`!;e`k0dzQ(t-bUTFi!R#Db6Q8rxQi{#PV z7Hu=PC0K_GOxpNJecsy2y2d1j8kpNOkp`!N#wS6V?{a2<4p=V43UT)`iA}qqI;Q=C zN6Ds8wVvD$t{3N2O!Bjy<9auLc?<;mt?6n%{PF_9A_HtLCYn56_DKDNjg?x^^8Vou zSPkGTgVn@~bEZ#9P7~EN}7y8jmiC4r$v% z1w78X(puvbFsfxbc(8U)YG*VQ2RNf;-y?dPio>VZ$cSu2z4cGYp-AGw>VvhazR4NtreZQDSq~?tBsS7KoRa#U zg<`CmDOZx0bDmPy7n3Vh;^n7W3-plls+2IgAw6|&W zO`S!!;LQ+qAG)IZ9y_9PF%{U47^JZiY-1%S?s;}f+?#kK$wj=i9)V7Q>dCy{mLs~R zoey0D3wMT2B<>7$3bl~0Yt27&Xf<$&L3Y9duKwO>gX&m_#?+huG7Q7?u1U!d-=%lW z%IGr3!Qm&Gy>Mh1?rPGu&K)~ZExm)-`_=0X-`8>wj@>p=1p+P{BcZ)njA^ zc7TWT6i8nF*Yv}k&*_weRCd&RV?&2@tn2$KC3fSRZzDAJdhn!W=9D-yv6KUjP97sUo?a zi0r1@dlIYhBIt5@+hM+VODx7&;MmZ+iLpT>f4C}wta;jojWpGEoQ6A;Rv`S5wnERv z;chDbFfo!Pm9$(h&F|8B8KrXx7BZj+pAz^;lEwycnm9bnp&?(vJA=nnQY=QbYW`hc z>rBNh#ru_NtAjm}f05!^!@aD2C$!J}gh}-gtaL|WWiSDqB}6kGc3MAjvYylwz;QsJdZqngTlz;*YaeW zGRGdZf6W6~`Ai=5wZEk*$>SUOy2j-3L8AE%3Q7DgW|<^@5fP_mB<0ES3RYW06g;U+ zkGdTQ$IPClDr|Zk;aH5%r79k%RZYGZziprGbe7elnorw5L;_z{_r8Ci><}2)`zU?`9y>F$bnG z(ztY$cU_6%v9pOw#quKZG^~=#S>*3c%0oDX@)h;pE-S0Kl)=_%g@PEq;Ne4MgrcGy zi<$9wCl)g~X-pyh!{B5;(cqyCgf)4qpP-0YUHRm)rZNHl9YD|rvg<*%4mPj@{Mt(t)} zDKxH&YlGY!EU(-5NOakZRErvx=xjb^ub6-!AAhd>?jtO_9n~nLnY=V>YOgvvAzY{>lHB-jBlc z885#Ogg|`qc_N3gg*o5!(DJFy{W|s5&Scv{6RM9M{LsnZV@7$MfEn*lE)g;Cr zfV*EFxn_|lB1Cyhy$Lqk+=QUv3otrl&0A=>`5@u@5G_9z8r`fnWN~Agj}rN%;Jef| zm)i7d6{G=6UNfA&TCjW}cmfXIjAKH;2Dy`_W9q*b&Saqi_v~+fMitN`X_9`5W@OJbOsnL)spO z@fiP64IOcTuy31I*lvJKo2{zBLvoP=`7EoDt zvb+w-&sHiOHTp>6D9v6DjcC3fv>>F{b5KZsL9tFq--1;2S=J;8={fePQ3=!2!D!)M@_Zvy&wTA7Gami=(-QH~3b$C{2_NsZ4Uxpsw@Y)Dna+GK=qtB4p8tlbwErvJrc-Jh!!pJPEWep|mr z+|gTaB!DM`bNIl7T{O4eNUe_#Wq@XK98u)cZLlU3JXa9ji?Lf2CSi zSD}DdJ{b&3=P-F6JK3?%un9dKYx?DQ&*c{Q>6Hcoy`mu?yen6{F#7>j@az6oRnVF^ zCtbzneJ7iWmp)x}Yg*#h)g$f)sU(hTrSnmFRaa+s_;Do-ocPohI+ldCHWe=qn3wA& z7%iXM;P5)sY`&)X36`|x(v8{zx}i?R)T!E;>eQ7JOc~@UfJHx``2jFy?TaUtFFei} z_Zf)v@ncQDOkeH1x6zMvi`0*<<@e$j8gl7cklzM0MjSk&qKT%XOCD{bS-%_R{cxBC zf)hvF*MPVn+}0Fd5X{;EObv@X2vLC$TIC?54OVwS*xJv|y?u|M+>_|RKT~4UKJ>6a z$N>mABI!wVFMXl5O&~S`qQJ}Uzfberfix1O{&9l`Wyr+fUcs{mlubm*f#2>uf~RDM zp{(s1l=Til*#-d=>SwU?+xIs;2t-ZsiGYO9aSx_M>*@2tnsaOh);9I34EUKN;)SWK zaL?McHm(aE?ZlT9{ByRHG}ps5JKWV2pB+rYy*RVb3M5BzLlUgt^>-weE@Y$MeoQzz zmN<+bLU6?SP@^b{>;gsOZF|ima`Z&IMc{+np(D5}Z};dPm+y(gVfg*LV8wPJBPwE3 zA_@UQDx6q;VmIXb9xZB$PHs;e#~ET%@$!Z5Cf4jbnuyaXPLrCtN>`6|tzN{3jVOP{ z;zB5YCC+RPKXXhv&8Duyud>o#8HpN#nqrzwH;E^lAjsirKPtSztgmI7?;2&lF50i7 z1JNC*3C|9*7LGF;t%c)W3nE>w1*>;3!;pHx$v`=xllA5%Ew%Ls8PQP z|E|CJxcAfJ)E-GJvaB)qFH!id0uwZ-#_Bl|!74@oLLF@mK>SKcP zM%6d0Rm#a{_9TigZA)zIH%9&k*m1NFKYg{SYsu>A4x_6tHKMDIE)w9^Bgq@K1*<2G zpn44#r}q~&&2-pD&-;Q#U?^gv`#{_?@Vu$a<;VhW1e)R_Z!`pKKcDH*sk*5v+ZzII zkhn3RiW$#90yyhwifn#$!rO`EBiGOLM!}e?Mqwb=oa#=phGd$J1Zk_( z)jl^Ct%)9kR?NBQ)wH)LTf4z)#nwBua!sb!(nhdJfU}Hr%Ge?0N%GpL?NFAD{X4Ej zkLE;kVgN@*d<#}*I^=ft$F3VN%{Zgdk?O({&z~mM{vqAFku{QCvf4R571A63pvD%P z_%=W0CFe(<{O_&AGah|$?6Bw6(QiS{Ya}WXU0#a{ZF-uzdR}wh1L^H*xsG zuTFZ8W;dyd8(}8df15(&Gq7%mnIZxH*_fF_vKshfm|3f8Zitx`N{RRHzHab86Du>t z_5SCuqALC)ScwU=8)BtHDW8oMU8?<$V5NwBH^xe?Qa1J@ZoLs-ADBXpWv#ve%fh!N zQ_UDmlKsPMZKrbs1~bP4_>VD|6$0yq3}&rTu3fl?GMJdxz#n5UDcb7Zkim4Q0S*s~ zST7UH!?mmQB;01t@TOipVJ&P?Ywg5%ys?GT;qpcVK;lv#uKTD<5irwD<3_C<3w15YB~2rOXBzwhZw;-#~;3%{pUGE<;M|Sc$-?CsTvb$ zi7QocMQtCR=pm}7aerE(r|O_~?%H@KmW0=Aa6sCYn6TRnlfFpl=fY9HOK&L9vnSjz zQ_mUb5YB~4*Cy74=7FzZQ=&3_nI>kZBx*zR*l!|pS{ItvVM*-rt+Gv_(p*x=KhnA{ z;L`&R_zQ_dXpLXYZ%t@jfu5?O2c7xO(7F|3s_CIkF+HP*WX6Y<%;BY-n${Rxn?iOA zCvn)t8O-NGYi3%7=rU%7HZ9Woh~QbET(d#%wZRU~C+kzRDl|}Te8|@Ors5I79@`kG zqmx7HJJhN&(3o`|NOM(aR%m@uvT$W+{R+L$21=f|-$P9^K4hO+Bq8lpg@QC$hzd6- zZhh!rVoYd#o1jud3ush%=yEQz*sBa&j0m|!XuX5RhH#S*8d{U0>Fz&-1X`%&QYUhCYob6IPt7jt4uL`Ta0}(Q%sGSmJ>)m4qE-w)u1xDk4= zgch&~iy~pO6$zE>WDJ&sDt6{lK`5=o0xh}B!!!xqDiAz%Ee)psKVunKPO*~6v4270 z_=0`Uo!Ao5DTa1i&`o`<^ zclazF)Ze|ZxqvWwxlOt5x!&)d>r&ivP48`%-q7Km(Kh$2jp-@Qx%)u4_U2_cGV^{S%rZE3anwOA-P*WquDDguVQ8 zp<=>5r!}-McH&E)f1hAZ7gyFWt-GqCZVN^RMxic574i`p!xc?iQpfdN^O?@16?~?X zqbytp8rTevcJ6UoJ+8l$RJOA-{*;$?X;h`??)i8yT2j=^XJ{m z&-{CF7fr`2?j1Jpt_N>U_#I3*`^9Lh{*~5$uk zjI8`b#a3QT32u#SZHgGSM3r}U5b zTY+;1C*CppZH?@?Qnn1|-dD=d-9;83FWYy4KoNSlruhH%eA;dLt+*{Ca_0GryIUe> zp2@g-w=(iF_jl;$B@N2x@d>pTlpJt(F{HJ#c zBRDGlFw%*i;vJDLVzWO|tb8MXAWzl7ZIQEA%7*t0kNg9V*5Q#~9)56MyX73e!^=r| za#EhU@Ue`~V>~1ag32-WSnwy2H})6LI~n=q{s)0gaP&l8H#j=?yVi?BAIpce+Qc#U z8EedI^Zao0ZcQ6Il?%wils;D!#t$|%;j){W+VStKn&g-Hm{-$AJUDk*P5XyiCyr55 zPmx&AVYxuZFLW61<;RP{$0ASC-$~nX&|MUqzdbebdHVYHaC__=9@&kP_NtwRs*$tC zMs`#0+?@<3#5HeRhjj-};Aig7fZwj)xE*Y1iPsgc*n+A`b%(Oz*W}}T z-wruMPaHgbjHKe#c_zDw6hce*t?mi5w_AyJ`do%`42Rie`?gpKG-Fm`G+PenwsUr% zKOR}RJhy#I zRpx-TG|7Ek+W3gtXM~2dn#?%RYX7yZ{;~GB@F0XHs{xk5_P(4*pzPd#31{T90_~@4 z%zNOsW#2tFEYb&NdxjQXUcNeOqDDQ@bLpMP>IH%Jy;g#m(Nl&NInoDS_qY;EQckym z*K$vV6X{!!+rRsUWoYFCm62qf>XL0l`j+JOci#vu(c*C{fz-@sal&{(6^Snh^q+{V zoSh53ix!!;hl@n-qQzqluKg*G_tjemTK{V8=ouFEEQdJssbIIZNxGSFNrIc-sjp~ zQ)Kn>0NlVWSnw)w15w5SD}xZ&%A`4MLA3v1Wc9p1;5TZceJGI7#RMx6*|gisYc>#@>3LSB^dLvf;>V)?UQjgQ!6zOC7S}k0Nfdzf%O8IM}O=lBX z4BMWaq2fc6Y@jUZ*2qShVW5UI;{dr*{_6Dij4+np_L;4r&$HGhI1Q0a+C<9ZP#z#m zH|3D4Vt8aDlj+?XqF@khj#+6sITUpE2VR)2rxFGpdg@>|MqXeEtsy?|KY& zR_0#fbirzNfHO&8VcQK$4`S0>Z_8JEbL@{`bJTyPkDvd{K4uvxe(WcalY=rgiVbc^?c*cs)`sNLb zgI8DFNQmNju6S`nQTSx!^?51Ox{=-G-pEFFm#4H#Y}^LNwFj< z>53N5YlH%do@k78H9R8lg$?2@^18RvC`XlSbAHwxpMkli;ya;OF)5Ga zZD0P>=h(Qsex)eb==SBp;UDlU_Oy-LQzHMuMiXNUhi%5q4%mvO`(bRQzH{a4uV$Ul z;;zo^FKgWEjv?>5UEv_FJiwiq$6Lw&=xdp7=Bd_BcDn4@n>1(r^TtLx50{0Pe948z zS!iA!kR3>H_k>ydXp+foToUNt^;rv#wtCEhb1AgfwfxcKJ&9;P+twQ|J27hm6B3~C zQSZ70>rqj7-Yt>NO-12ZY^Wi$Ly^^2$^$SMHvFY`tB0XVPND3|ax%uca2IfhJu4YAt;2o_3g67-btE^gT% zZI5*BEDFtL2S}4!jL(&h1)8Ez>6ck~ix5$WX`xrMD%NYSt17bWl9!#*(x1s>+@Ivk z5o5zn!PH2ny1@nrT8Im{;DtzM0ua+2h&K;yTC$O%;C5?<<4LEQPS@5vXb1a|mE$6> zgPepaMY7VlpL#L|s}5D(rb>ecB9C#hI)})sDEL-lzt-K6Y%V_$+|qMK-hh)&I3HO| zcNg5s(WNN3)3uowX=I-vA&eockd?UGdh1RQzl6%uCfcTHF|aM+w529o+40unN?&9# zU@aLLd5omf!URR`}QfiVB+kL0Vy*l@}mGnt4KYIiR zb~^5l6t61^9(GvBj64=C3-;I!W5Jgr>fM6Rc^RJ6uH$ZvYH0Q-9ri;DKA$WLu`S4s zEM|T#zdiC;LmARFu*w)Ot}P2K$+B#sXc5oiD@5g3u8$(Sspj3tVrXRE9g)5@Wx;lx zTtmAfj{zdmj!k9A+(1HTJA+vkWa~$1Bn}MkWdven9E?|k=Ytl5xBx&7C(dbJXuo*c zk*G)I<*#Ob6xm3hp(5%!h;E15T%2$M?FiRsW1GvKq-buxS+$%BX>_+SKJQH8IF5HF zaV)@w)o4PM?}@y@P#n0PKj|FW2BoGv&N=kT*P$XK1mg)*;C5ULTv3RlEEu?CVuV)C zqebdFuzPK~Qz=D8yZu;SLSwj*PITKG?J=0Q*bHI5f~Y2B2SuSn_D&4%P9iemP=J@jHTaxui7t?7V;pQ;g?hJKz^VHr9eWd?UvI?d={`GNc8k24*Nyn!wgRuxKRs8 zXh>L+YmWtRo&81OkH}RfSrGKyV+qUiT(&POF8qm=r(H(myu@h!Qdk0y<#%gH!xyYP zkkVs=H+&Wt%LFOdeoy7X=d8!X0K-R-m7B_x2dIcyr&{BQvhbB;JcGXmICBmyNn7TD|$j6;5AAQ!}GzJ%L79^CwxPC$^$&S z(@Gk12=QA*OR7XZp|rPPfdtuL5X4;-yv>Od6|C5nibB+HGQ{+nn{VbT#};4h5BQ!D z;V_zw$zLW;`Z#-{kNYcym*V(M9BygAQ=kP*frf~GqEG1<9@EqGaZm8#tNlI-(xg5T z{pu=Yo=(4dI)+ZlguQ4<$C3A`Rpz)1fwMG9SxK=o`-%65`wt;pX7M>a+`kXO@}S4F zzuoSn;ZEbl2YS?ha?-J@R}1%0{mENt-L$Smo7PoNX~?x0cnOmst|7ocLaRf-F>5dKYd<30BdF=H-z48&|1#962Gn$OGRqy#- zdBSN=|2b924MnUnT3t(Hi((&H5IS$8Tt#%k%BLRm1=8dh#Pg1bZO|5l+Aso&4^7_2 zKc!kQ3Hq@@6aU(Ug6GO=KFkHd&D{@wn9kjWCIv3g(encd69*EqE~xC}>0V88wj7p~ zU%nSpZ?QVs$PY%%&GxXhg(cI$CXSx^j3%}XB+vxq`c;iwJBH={GGIR7RK0m?+1#da zq=_@GJJ&R^VxXq$dhu5S3CSj?UG;I-!2|viv|WA>3SMnIFvU8$#n3=wTe`;&)L_0k z&Gvit7NvLJnoOd)4yZwR0(8+kj%H%R1Z}Tg&oXbSWD&PYHnwNUx9GQ&@WgrSwoX2q z3WX%cuSafYa@cpNH0i&^d_F!=; zRun(kjnpPho@hs==1zS9G zs7cO9-qO!jIf>b)pBDWjP=oK5D*Tc9@EUBljQ%l(!*9a%+~=_ZAv;glKhOGcY5lUz zIBuAJE0Le@yhVxlswc93(wi79aZ2*|3FkfcZW%0IcDFeyneb(^UVJxBV5)CQW!mH{ z`*KEUH2isaO+T-2$(LoUO1}HvG$5jrl3x2%vsY)H)me47xs<86+h2@jDOFHhU%d&Y zp=_!v?{58)O%Jk(tj)0RTfBvvabHGLGNARt;?!-$R~wTZOltDwN?^h`8Rf4YXMXy^ zg_t=kovjpkcwH-OkI-z>WXNJ?nF?xA#Z5k0b%;xPx0%4Ky_+PHn+EmgNg=( zj83>sdgj%1EmQb`ftEkjPcJ_~*)xZi$oU>oCskfkB_zAc!E>xTM@r>jgA7a> zKO|LVw7^_dG=)XB~ovmmOTck4$!w1OXLkS65{S1}hQMD|zFrX>}QTQ~BQHT@u|6 zQM(@(%N;fZnU^bU`sRX#62+!mBdX$?;9{4T|~TQz%9bl5vXmk-Z0%BXm)uTScXsbyP}} zIjojbh`@+_$_edQ)~l-f6hF9vrUAX#kNJQO9upW^L4&QzXD*euD~sN6*hcCB7bMF; zy(un`y;x09rx_T>>0-c9y1b#uPPi|~Gr79sbj^8L;2DUDe6i$>{eBGjn;n0=k^(=* z*KsI~eQ?$lkOKI;gik1m$rC)G)`TZ{b8&{pj}ndH@h8^VI{Bb4KkuFLn#N(c$xH9L zoSsrK)qD;3YHL4{sS(@eaC>JJf`iw@1=zCg1D=kfgF z=lmL$GuViF)+OAy9|aEy#}j{WDP8U>n zG~>qACIk)k5g9y7^SJa3rvKYT&C{&7axyN#5o0X^-sB51KJxCKt=%b(YssOqSr)Xk z2cs3Toac>O)t_S0%9td^KeYhDtOwytjbDv>S!E6kfhi|X3O4>$y)?nl9k3SQAb z_Ulc-;8lhcHi%mSUl!*0jmPk=8Gu&{xrO(I;X^u~$YW&iFj%*94E{#t(Dhx0=>Qcy zaiF}8_mQKIlwWNsU)ih;eZ|xh9{cvactVyD+%8bPoY3LzF%`%(W}n9F!SW9iS-{cj z03KaK=`VZ&=|T#6+LrLWBOV`sU$#Ndq3rp}0xI4HlWd>i0Ubh~-Rf7r<=kY(Kb{vU z1!w#-PmiZ;?S6GgT?e40s{<*|4yI)3AQ2jqLo3uBl)p>%{IEM_LP&dM?fEO}j?$H2 zR!5TOGd^mwG@!d1|3Dtvh$t7G8n+jIcQJ`#WWGC`CMBexothTBJrqd!r z6y}eWPMkS;oEu*?Cm5_GM@^ppKu_j7{8YTd5u0DX{oY}}x%L~d-vaxcQdZN*1@pI; zw3OAx*pcfTU5%^bw0Fd(<&Qyh!KGk)0ZeUMa)yKRi2n-GI9F%8XK)(>GvcFj>c>6X zzQZO0rON&V6}mG};L z48n<~2#Z+u#}H8w7mmwt5-_u@zC2iL7W^AxA}HtI04ts5G63HQ7HJMB%j#woe0OAN zVb96GK(z%ZNL7HOL{=|9IHNc6zs)CKX92BCQcxIqeTV*2orKZ4*!| zU!+dpQ7JH@ug^N6l}$$(_OW#0LyNqw6I%~`RwvdErVQ!C-z8(O@5J*133P(ieyG-9 zfQ&iFq#I(c&YKA@;kOks=byX;W|@pYkZw+nhzwKPbbxnGkmti>paQRF{~vR2A752< z=lds^)I@?g!6bqP4L-CdiZ)p67z#DfM8ouC2Iq!y&h3$lO6Oj0=@=_`wN%&jxS?_xVRW2a0{!r9?j!ZR2j!h@Qq)F6sPgxvS%yY|ilqP3lw`}+NH z&fa_Nwb%Nt@A|Iq`aZ3-d>eq*oik9Ykz!b9&~UsKh6PWVy1V!J%|rR?5+&pdx-v7Z z1*Nohw*|QXMKkwMIq&a+cQU{^S}K%@0$G?wF~>P$OFP^bHVi}FG(s>)x^GKMKymvh zb5n0t4-JpDBlf}|C_I*zFwe)x$+1YV z-=oA9p)O&-VY3-4eS{ss_2xKlbgm$V=TdHcP33!mMkDDrZ=(Jw+a%?QAtCEHRucBh zlkANSRhQULakR&_^2EOJBkfB_anlm%+C)tU!m~83WY!pc08C4(bt*bW{@tPb&gvT!0FvS7!oG#uQz6a(3J1IvYgRC|vHhKQ1D1Y*fVp&bQhf}Ptb zm8SX{1D?}cgsXK@b~r#qLO!Rd&^4>OOcIEjEC`I8Xt7$NAggd51EPQ7mQ;3NF8m&d zN*Ty2%ejC$Z4S?l)YdaKn1kruHdgUGMiaYef@@PFJLzid}@VH%H zQ2dV!@rz}d*`FXIzsY=ciFAJMX zK>3(zbeftDCx?!A=ympDpo)L5NV6`*OeD^2b;2Lr8d z;X(QyyCh4+Wy@sH%{g}v)w?Q>!is6 zXqu_+v_yR!?E5XgnKi}`a}g9DTc?CMV~lsqxhO|TuK{lt5Q!y#aPa^isFbDH5r2a~ z`Fx}rYKA2$3wF;8Km=NjskdUFs|HdykReA@!!E08Lh~9i3u@}JclFn0-e~7-+t#UI z9|L?2{!NaP-mX!FdjuoMLyHgQ2WBdY)flVE?88!p6}p#lGS~I56Z3(rD)E!3d2=sx z#+)%ZtWA6jYuK-}ypKM>V15WA@t|dEVToCF7ml^Qmd7u=ySeZ&zk8tWheR**^fm~F z?_?boLN5>{WQ`T})+%A%1x6e5D2F5jC}gJKxJzTIHzXH+UKSp)5lV01Qj?sUEmm__ zU^h?sH`tBdGGqKb2osa3&g|Da@Fry5l$@ZQ{13Br{@;@wE>gAFoq`NE5fD=@RyQvk z2O+ZAG)2+k<9bnlvEieBoIwX;jP9Y!G~h&~Q!b!KHLldADHmx7WL>Cv1hSzqI&#qj zv$Z07@mN;as&II0H%+l|$Tu7y&6ABnMh{Owv8F>A8AUo%+0J}~eUW6t=>i)Z@ryM1 zhF?+Pk}WVdQz;5NH`F9VQEZn!qLK*30$Id9a!0UkRtx7`=q&B7*4e66WdfpfO__{2 z9J|a+E|%TZJ!h;2=Igv=2$Zc5jZ&AGf->ceVKB7tAiU&_rNH@JWVdrQ_gg8{#w$uu zd-6(zedTkObu+^s6bPJfTehN-!i^`CIIcYwZ-JeXK#i5aYX_@C-=zkGF;R~)gKSL{1RE(!vJ+u9Xk0fh&j4HwSz+;Hh<6N_$AHR#aBYWJ=kW zU7$|5OelB_WUc}xP9H2#6EvXMIoS>xH=iUh%T7aKNGgSo2W%mb#MD`n#;UkU7f_rt zsZqsThB}FJ9r)(6T6nQh<-&0yWp|1!LPyj@nK`tU>~r{vMjjB53!y%e7INacFYU{S zt(j*mL&&T~HUU9id~!m7)YCD1Qh$MbOevtIcsn8$(vI!W;Mz}KQwUs_s$Wr6>FTR~ zdcXuC%n>BnxzJZV>5_0WQb8|eA&P}pG=eRzSlVT6=f~Fyr!YeXdAYObilYDNe-59+ zBJR(Fv7>xzo91)XsFPh%8ee#ezVs}Gyq=zv+|;G9MIJL?@&!7vbA(0(rQ&YPIT*F0 za3POyQ-^~P6BVtrScKT+YxdJTN5~rFu`b$Vhu7loqHsG$SS{C(uyJ93VOfmFEz-AS ze@X8;b$5Vdp;E`#3IeyIKi9fds5wYhL?f7l6|R#sHh8%1Ht-OWwSn`5sZFQ)hL@EK zUIceL<5x~`4Btv`R1cDFqMf2a5b~5>sdR3P?J0x0mQu-}yYO<2X5H9U^va+^;W~S@T7@dK?Z!G_!i7j-0h$cIp=_m zKgrm9ARjh2oSzQik!&CGG|QP(@@LIxfTbx|306`zg}(9ai#*k5z;JhFZ|urui-lBa zq{7UV`Av5cFozV#L>C2qrpfhuURQCF@gdcC{uO2?JsVpqe!EHLw|kqN7_j;Jc3HEr z_B8Q^9S~}9;ngOmrezhD3-3!**R#vH@SOeF`R|UJ1>BDh`t15Zi}(F^zWYvG5149` zC5ymcp$tEqE{(=@G%zZpy5Cqg@U8jiQsBiIM!L5Syuv5|T|l;HcHeRCJqm+V!|LX| z$H{8myV-y^)O)uC@7)jNy<37Zu{aQm#JvWbcW7PB0WSkiIfgj-eICv`F3x#(4Lpx+ z8}gcIjCsDQ%z4NEaOd67It0A=+}xiY_X)dKZFw0qLov>Ix6VB|=iNTie{bhqtAH-K zPX7^{cRICx(0MluB7iTsPE8;m@CqGTLB!qhww6>5X^8oN#Sb4lR48w#fHyCdh~0oR?h-)=$E zBNXOUK*$R#a)3sbxB<`Ifa8uP=3S@m;^i>E9W>y8@CS;Ucn9h!N`W1l52AglC_)|8 zDogFatzfL*cix%rE~wjFcUyr#Cs} zNxa!TG3^grSn7H(6dUy$<$@iGSu+zd;x!G%Zmm>9Wv8Ed5>qfFGBw*Thw|5Tcds2R z2m%`7se69#O<{+MDw@lO6zn+q{MitC>|GiW&HHV@QChp782l={7kFUaa?Z8tQ{Ook zAL5+DLwzRa+-PM1?jt=p+_8DdaYZ_Ogl{TZb6C8lhIlkfVQbOqof?8%LhdrgT}s_0 z>@MZ*Vy?Ss6?^8d8RELTxGtHs6W85fJg73e9f6$xE|$F!lyb%hZD3aR9d(Om5`HYt z+#=~IXL8Oxrx*C=@*ln)PZ~bDrX1#qA-=l*fv>IsU)_DR%}X0qNOMBkqwlfP#De}h z-7dDxoK$N~KZ&GfA&drB`sx7cI6x7s;rr#z z;Geq>Zvhi)(QLl_J^XVGR_-6*pCgjN`R5P`!lORGRLoFNqIu_FpV;FVL*sh&hWov$SM3_9mt(^!EWhO@`I2HK1*_dMe!oLy*%r}?$s{>{g=Y}eqM zAuan&ka~K{{%+ufc@LbVDmP+S%C`w`*ez*{f=8Tl&O)?Ibd@WoY<{`mKg#P-vE9Qv zsM|V-HcYn{r?uhAplXmaymtx)-wbKPiGTCke0m!W47{KX)b`FUz<}jZC{qoskfdMc zByw8vTRmCK3pe3gOVwD(?6CoN>B~rxHWpx)c|)CZ^@Gm2*6wc%RO_5`ipTK-bf*uz z8t#`Xg%$#C+tz)>KrR>hJS1W`f8bSFlhcogerEN+5=Tr%T9SpGcW&K)cW(0tb%wZa zTc2~n^}>3HoG`b`yGnn@((OE0(iIp~ulzG4P=B>wk0Qt!3IqSDKA3emz2ct#)c<{|YH@0_A3Q|vxh`(^)1#M-Nm{F6tk2d0r>o#Xxw{G`l z2};GVm)$C%UtJX_u=MDF2QF{Gt%X~nLzv5t&Kq=UwRS)K>p{>Zstt9oC5OBd{jdjI z;2HnckXIU_sAfg&dfOch-pF3=y2{#uCWh|97%ww80`dYZ@d{v{?x34A05R<_(*`vU zz$~}sv40%|8TzJfa$-pF0oW6~))*P;N^I}`_24TbhIT;mzk14lg`dZ{;6$ebE;!sm zD@Aum3=>isPF+l;6C}_&HykZ{6;BP$DD%TDp+z6@!%4&Zh#yXb&8(*jMjQNa3F6vi zsc?R{9lDhtj(T5_c!lR-&<|J1S7-La^-`DbhYN8lr2|*j$N1p_ztzXO;mTEnbHg=q z>zoEMTb|JqSIP_K^OUu0jeH!wCr-`<*1TlchkuwS4%YQPeOhL8q23XNnQd>-B}ZSG zOD^tQa`9mwOC;E($k-mua~-95w~rye@ZA1A@a`Qv2Z!Vgla zJj48P0*|6U(I1EKb!LCuZ`H7m^~X7nwwQ!DsAQ=rt7*^()?5`X3U$D!5CyO~9I&=We;&NKVo z_R$pV$l`J->*isOw+=v}bpwvKRWOw$f;c(M@up!i-0>z7^&LFQk6~A2Dvuh3tLzYn z$TV=gg>?VPj<-t7KHzxkwJ$!s<1Iy{gN`?iltIUv%7VB2XFA^GV8e9~caAq_eiXjX z=y-!*-cpyWSOs8^n3Dj1hwDxFp?k{hVtz^A(bZ@vXZF4c4@10fLed}RePbpSCq1+C zt;@lr0q?^k7T^b{4s*UW2mt4NGcM7*tbuQR=bMVb@Lf)xLFb#=i*2pc3TzU`z_32l z`6kx!X`F8wWdqJP4YdCx=bHfj-Oe`{%!&|gvfdYF&+LU;MHWnoOAIB>3)e#V&_N?f zXYj)5t?z}?w}W1|a-Mzr`?M4C_SiR-0yIblA*6Q}Sks2{%iI~{Pd0T~#E zI^cu|8&<;{aIdRObHK^`EeC*ezzIj7s!Ja+^?y1CT!(7?{T*=XcIx^x4!C`8B>heY z+#z8JCyDd!8TTIQfXi?-F5wb5;6j#U40yN$u9O$f0q6Mir*y!nYy;N0y?ze|T$h8& z9B`%94M`1npem5<(K+B61eH198hH2balk1&hF_VvBVjz^dw~Nk#r+@RfYVS49B`|w zjNuNr6z`pA@;e=H^E-sa|3vRw+`MnH0NE_$yl)?hWQKX)qBH|cnfFajB=f%QgUrtC zeY*$;fCkhM@7skg=``)%IV&>*$zTk zd-!Q3aP0Ey1&nWF9`1e9?CghTf*Z5X4E$Lu$DQRj#7r0#j`b1mTTP0J>KaaWy{%H& zfwwGvM%UXC0gH}wy6df6{Y(1kuD3>|3*tuqLkY0X^=6{%3XNlB7;wG4N-?<35no@m z^h$k*4mRL=lY7iuf}h6qrp!ZJZ#ZO!xZcb!_i0>j<*?6BbiMtlcbe<%u>sfHbQ(C! z^){Ub&Hv(1*IUF~Z!@<~+z`0lxSH8jwp-*58BTj18|H?a+G!T`(A0mz*xvK9+;GF~ z%_y05*hQ%C6_hml9O%NTyi&H&6i6~SB+k|fVZ zxcaZ(Z2q|V&E}7*-)#Q4`pxE#tKV$?xcbfJkE`E|Kdv=rTE$lL$IUC6mdx9XBW?*8 zFWlxYpF7G@xuUPodUL;Bz3+dO;D8%7nuL2lLoc;X07h>Z&r5rH!D*9&GJ?L&9WB%Cb`7FX=>CB7X1cyT|JvCBW-X$ zAf86!Ume^Fs24okR?0VD4^=lWO`!(k*V{E@?O<(!m_jXLnYp2^X*<@OT6jVJ!$;UM zo08pTK-_6FpwZe4D10kJ!fgZ;8nO`(I{>v%IKiMTn(>2GfHa`dvFI6UG7%PO6pgsz=IvDtVz6+ zdt$EqaP$1p3M#kt4_aI}4G#h5`T$ueISE{C;bT@jWB>P&Ehyw~a&c>BU9!F@V_GtvlYYWIYd z=1=D6YEmvg;@+agu$m3}vd|rF2-+|)z?ta=J}{e+i2!o1hnJgdNN%i1Wwz8;Uy<94 zdqcm%0=ySVON|HIXddG5#sMxtNV9 zF#aZtsgwIn)E|F=8{8O0DYzZ`#qdWIVbONoyiv`vgyh|*eJM0QY&{QEH2iw`KGQ@~9!5bLQPSSDSf|9bW~;Y~HWU+~198wyZ$K97fJ z!F9u1n|NJ~n_|T*bS>q}8>s=&cn5@w3N_AAlwWR4amCn;^& zQT#qxeqsCgnBS%0|sD}a~5?SK(s_4`XRN6_KC$6y_-P> zz>`KMbjK#>ftYz>4wS+|01Xc^Ye?LbtUq7q$LjG0i@&h-+hTwln)7~sb8^BtBp(iz zvk~b3k3{J`5`_5&QGK<^?CvclJnqAarB7E?Ab&P zJH|}@xj0{&gUDFoWb@g32YGbiXF{~Gc;Q*0X$iPfbPrGPKOg@tZuThHzV^9)76nJI zZPk@(jtVQ|9@i6@Pw43b)tX1H;yewQ*k&beQ{uu5#XYYAGrU|l0!ZA%MWz#aUw^i> zp#C$uSm-HAGiWat=H&+k)2O8lR0b1D&=s1g z1ka|Svbk7F;TUn0!m+e#qn;JuO~PEvcC<-L#$s9~&2AeaJ{S5g!w_XjcaPd8JVn2G z)-Zxci}Y5dE0B1(u>v0C%vaQ1*iH@xB}74M#G?Cmk*`Z1bXl@bOSZF4x01UV`)`p) zy(QakT>D|jfgI%q8;G%zV1^|KISA2JH|De1j!Y4PGJjUEQ()mMwTst|P(bcjpq`f4 zH?888{HD6&>{-)1qqMF@dF3o|tM+PSN-13aOjcyS}0Awg&t(q++3(oJfbJhF}XG3Et3 z=n(wXq>PbF%-we6rn;c;14aI2DFCTZe#huqO_VY*P0sDkE*j^0d(T6(cWFRDkfZBWVR(<`aU z-0p82r;Nec3<&vcDrBaOdZL2Db7ZDnc#h2o3tbg`N}79)t+LgOR5%h7O{0T>stWm4 zhqR@cj?kl8E#hqS8Izj~9ESiEE{?WC&9hB#A~_EC=g>?Tn{7xWfZ7jb8b(cA5;TgH zhcdS+bxw|RR-7}y@cfkq^0yFu3Yy%X9?j$>ZW#SgFZ9U9b&9fMUp*vqCKqcxGKDDD z_R8W)L`eQ<1s4MpQT`^66rr}o*YmAZIMpyq;1uNbPwQXj?}Rfgu`yE77fE#GKRo(F zMt@?P2yhD0XV@0@2?Ur(xQ$Y>Pd>xQMrqg8f=(xoT(99e>RtC@Be_Sf0|r~)Wa zv2lOIOJEvzypx` z>QdaLxT%p+OMD`CU1+BAETbKJe2j_5cBtu6LDl(pjXLZ9WM@o7Ramo^6gH~q00Q>^ zl@i13K$dSOdzF_3B2ltc{hAW0@;g(eRn{d7A2!W0g=RVFVGXH5 zn-i*Y0nHZkXm>8#U`7+P>3;%05ZW17;maTT|5OQDqC|Yj0GhrYP$I)oVOugxI^ENx zd0vnD3-{XQer=EyhaSxd5x&*fuVTpg5$H%Hq|N>CTMYmW8O>g{GReFlRs$+PPy=Iz z@gbo?dtHS!t#`nkEM^i-lG?v+&sZ~~h2l+7*dG@h36$}IzMh=xG4O&|NLyYc31cQ} znA_fF^au*u>Z#r%n=N`Lkp=QG|5vuMoE|0Zfa;sNYNa7VYbe84{ZbFUH{?A&IY1FB zTIf{Ej)p`or$g;99ZD;*+C1hblqlMLn7`|atR1cwKunijXm$^edk@P!Pe`HEdt48; z!qJdpXkmgAi*~LiiLbO}*xN3?d_aG^;2>;k$1Ib!#;tdS%Tqi#_mFr~7npS$t+Yxr z6Afs4LV3z&nd_YnoXkOdWYuHa{;b-B3BHB-JVqytKl%qbNBc5J01H*wZZOGiVu zscD|@5USc^2*}qGd6aj(dn4Ncz|7`DBw}`g@OR;OzdDl-Q+n;@y-2d?AuRBkx+aA5 zUmbF)cPNz1DsWl9<4=)@AQRlm(NGS?5V zzbyGAw+}qVx2mOE;YbqVX(XG!P1p*6v(-~{toaH3KL$^wBSZ_@(U&g}{MKtY#%99q zA)u$r;n#g>%Yn6+Cx%d1wtN-uShmx_g(V7F%Dx_EgUG9tNOICD_VHTcQhat4opY9k z6H>Uk?8TtVq}(YP+9~Qpo@iykc_Xc8Ae|?$%%%~xiwLXT*$N`X)+BThm~Nyaget!y z8mZWVy?}lP(3k3Yo1ayAhN&#{2$2j@N{sr!55fWBkVFl7fi5X2;PpP;%_%Uc3wn$m zRE9YHL2|r6T2uzrU|oX10GQ{Ho@+Cqf9zf`kraPrZfh@UQeX(bvgP^={E1*WJNR03 zu$5AV@%DdS;x-Z$O)UJu4b79jhpxGV+~~CzD49c3Q#{_3oN^J5{e;^5C8F7HTW;WY#5GrzKL=$@pjdmUJ>Zg@A-U`vIdegWDUL z3x5_s+%%m8Co{6oTczO5G6ZLhrU#fx%4*8tAKd7{C6rnMy|zdFr}b*gDE_;~{AaB* zT-R$)o$G+rZElkTj*09lhmNp!vD>EzNm)X?a9PH1SDh(NP@UOo8A=|e7ZxAP$GWq4 zaFgfrL<5%<14#DX|84w~xvFe_1BgHXr4iNV868y@GpMylTszlB)8JbJ)JUe$4G@h` zd#(gd26?Ytnlu9ZV74wxv4yYNQW)5b5Nx4mO>=bus^;$LJoMIWk8Udp^ zId6paT{y(=*v-vzR#4oeXS73vc88#+l_{`j!35M;(Md_6=%l1rbg~cS;kwLei4UhG z+F`I~`CG=YDqT%UY-Qh$K_af19uP{+5`pMgCI6;4l&#dbqZ4}EvvxQ2*QB_#H$)r1 z4Kh2{8a{&4xk=dPYO8{8bTwNV#-t}8v>c?iRxsDAnpu4>YyNo;LlKa zOCyQ>_R7(i?Ga9VaWHONOVkPm4CIq1eUU{ofcU;CT#jVsY927FJ2pTqbA*~A=h>Ku z6r0Ti?NS;_nK4FvS?ykHs$f7c-?CC1z9Sj}>e)c8LMWIg4PDi4)!1~-nm`)?^zjm2 zO10xwBf;l34D1UW*=;8!M5#5+C5!n=gSj~i)zVo%VGJ5cL60@52=)F#8pcevkcQF! z+7?qA#73WwG~YVgfe&D-#jXUH8#3sp5**9wTBt`iw_ zVOVOpz@>ZJYMSSWs7^qp^n)YQfIbCUb)juyqB`N(o}h`t@EHd2^OXegP`p-(hY>!o zOORoX!}c848AxiFog}RRzqO{o?NV|6KxZIOmDS7!M6n`JzuYlB%GS)HBSb&9H7&QPo_( z1~ixzwKlO&0T)aVs#Uy$Pi!xl6q8Mg2lmY$TWOo8*hY<2nc8=ssmG#VYcjQEOuzPH zYM%11wnaHCeXaw)B0MPVt@Kn z-?(&&RKPuwhMxn^0Dxovq{U*7ZJtqSBY3pB^_wkx zz%Gr^y7EXfE04v{igO~(Uw6hWMkwGj!AEUkR}l+c#S2G=Yz$K)vr9GgyIsF*U--0e z07VCb&at)}hLeqmWMr&{5oD~9#7OZ8cyOO0CxsNK@;1icD!^x0YV1}e>qE$FHn%d? zG>i!FJd5&h*VQjmRA!2F&B<)F!Uiv{y1 zh)hT>4ZHo2cQ)JtwVJB6VkG9s8QmscMZwVVn*j%$4^T4x>8J zflx@jc9@DERaIKiO6;sw2(#Vjnl3}?tku1`*05cOl;^T}`QlnTM_uw)iUn#dt*x*v zO!J%eAC_;zj7kW0vPjicw9lVtHY(f`$8k^mlXFk_tK6s4YzRw6I{a@karK-eLnP5# z+uFq@A>)3ANfG5SH16KzzQba^e{bU-`)PfHg5Kn$Sps3<&3YsH+0A}uvl5sQ;Kcb?vuF;(gzi*Mte%ybIvtpMJ zS9r04BE0XD&I%kAer7UF9)(MS zR*l+Vo{CoPhkGgzn=ev*k&9GS^+hI_nEOl6nxEn)KOrh5b8S#{Akhu_DQqPGZVKD6 zUZG<}+RCPQ!i5m-+cH~gxodHR-Qz$4+h2_Skh0{V_`b4LU&!G!B_mERz9zG6&_nV4 z0S^Vkfa$=jbxbt4l?yMR28&;p1C}~eC+U$3(5HQ^ruAoDuB%;XrH0SpiZn&|CRB-Rz2! zAvA}DAtcVx{#1_TItPR7U#B}Nx@EF|lRsn@?(`VNx+NvX92E`+&RS}GWj}?8o!R15 z^J9FXui|+IQbFGP)B4+Mvol~4(-P|=6-WGuA^+hK=B{Yf3_3}?bcVSQkg=Sv;+7|! zuR>Qw#mJP#Y4%rKY8$YQW;d6ZZM80*clpvMlrMVulXmqjftO!tUmFjeudut=F=n)A z);nf4ojkIeLgPqtS6}T^cHRfnoVN9h$eCyiZ6A!lD2SEj!4HnL;h^}X z%hJ*F_vWDJl7r%7`PLAB1qjUABScrTqrP1%7_fSe1p`*^DRqfqcd=l=>OKDE93Ash zY|b%nEG92D)F0SSfB1eJzoPk(1ZL!+`*HmD*R>x<*BSTYAmFXKd2m0De*6a7k3;-+ zh;M@ZIPPq~H*p`KY>n`4j27zBA=`2w-J3j*Wt104_i`WHb2dJSAL5f3*nr~?az(7s zByidW99=*Gi_Ko}L)(C(Ht|4<{1BuPEh4W({>7C7V+o%sjLpqtod0OgL);P3ABk|j z9Y`>Djhd+h+!3v3bVm^4M#u*L)wU`AH&-zxZKrs749wTit(5hWH%e6(0}r7+d~NSC z)gwVNtdXUSEy7p5sYK{N>-5ER0)N4VT)obI(YZ+*4Hh6JQ3Ff>8=N>7AagD`NGIooK;c4e}b~x3x1+>sxs^_2<0S+(2+3 z*)4`jZQY`)9EKl4Thq0z%`!g(U+Qyp(JX-|X=-wAzw+v_?O>LoVboAU5HCAh z|Mh~kUhI2v?guhk%lnsdxX|Vm1D5u3r772k%4HUGrT9UJZHxp7YD~jsrZ<_A_S(`x zR5L3^8&`x~)FZD@@lDHbF2U)rJVXy-t4usx z4m!o@=~uSAMf6A{sgw)l+cfCSkW0s``cCgK8-OX98NkHLGV~j z@-m4UhtTg<%aD=+)nL4l3%TKZ=KA0$J!SUm(4gbH9QPfk|FNTPixcqzQO;5S})JGrEyU2s)Y$u=cDQEE!Qt zuBpUYslCxHDxL0|t!%h|;2;3Td5=kbtl`l-WeK#@z#R5&m)|{5`0y+u&6KczpVu3oP0IEGnsVOTfex0iC;9flpUDB~)d?cVdv0r&p#$xl@ z(AD}P=w16n%4NxUe~$G8w-{60OOVNzGiwu@l97DTIdGva*rwqF->KUc8x!?ilA;X| z?AWA53UFEqnP6hf^oktNuWZI_OMnQ>D29~cl3|G9mmmirI{(?#!msl$D}k!TXOi-( z;<74Lr%$nfKsFw$UvOi6W?!IS*YmoQF@VnE<4w`R)d_fX+4k z4Swqju=6=v33%p-!eSk`I#6bMs?l%DdgB#kQ$9Gtri=jK~MYX6g%ESM-`W?X#@%Y6SXL> zYk(+B+0_7B~xmpB3WU!R$*9wUNh{=nR~MwG|Vu;06}dCJ3%@*IZ}R2pqSB{MeE z*3Mt}lrXJ60Mi0mfC?UB;i7M=`Z=Qw$+D-?3$N)Nenj&%eBC#aS~s263rnmQYETyJ zyoqE)CQsi8x~!L%GG3A*Dg)&j&$t;flq6z!M6y+9*y*YWA zjsrPF@s1VX5S;YSOqqY}Iy_(qgABrXbA+lab0}mL2&EH@SxjKk3}ZmX8>S2kK`?mM z1vDc5Mvp76+&(1B38dg)dPU*q=Bl+|fO*c9qYKH$`0RbD%k!MUd0rt(zG*z_REu-( zL4#3ahVTGDlM-Yh?B>e!eV;Z7;M*6;o?R;?d zKntH+{0X5au~*f^E+(l`u`n=YP&t#~vM%6ZAU1$Ui=g0M;{mueT5&Asm`SG8!XGg_QY1I>meI9{@gzNa z@o>L_`oLr>+{@IkL3q+EZ~WR?EdQ_*Fr8ze-Z>UfW>{nZvl`}C2nk!X*$#(@m|x*K z^)3jE$gdEDR!O!DlzVgLppk})-ueziNw+M^0A^uryUGf79|(WFTxkl2i+x?QdHz%Yx)^5Zma#H>!riyJWyAcdZK7Rq{Q` zGgF01B0Dcr{!6E&@ZLW}m-h(F;Th9W9NrOb*+l{u9$zeRUyw>c@2ViZw-b9cd*Z6V zpf9UE2l&G0kLsIVVMJw95XD;2E+)-CtoIO!I#U==0HvB2VZAcTtBB%Cv1X7^;2fKA zT9~i%+Q9*EJp#8D+{D%xhX5Lx>jv<0O5e)_GRqd&pbtAm{U-bfXG1GxrZnx$?qpx^ zX>PmmE^wdUbj5GwMlgdA^MR~Mk&08pcRE<*+z9?o2h(XMhEX$U$tniP$THQ_L#|qa zYg-R;X7IKD<;T7Y;R-60t@^4m-QZl%wdYAlPWB|$I}pMh%|7(7{0I16CXehurHk!O z?6m`f(fl4}z6#BI51y3FpdO0I!n#=~{~^)b6JSP@b9>FYvz|2$%&3#k-)}QrbJin% z*bH*@BT)0G$EXE7O}=E4h)xJRwOP-F8!#t4xF6&8BZStBcERw;dRDsX6}i-E-u@+byK(>3wG?o0mnfKk z7t$N1<4*nALz>$x(j}@zoM`hbeb_2c3nz-^w}Fk(>x~6ZG;yv~30uVmrJ{=m)lZ}u zc~U=z)l-$Z-aofp83VmYAkrKshj&;@%B>~kE(j#3geFGHiJ#i;K+!4-V2VuSc=xPx zq@yActp};(Ao>LsE{?d^R0GHCu_~m56dCbgwGxJ@0s)YAk*uR2X*Ta5!S?=4WymTw z0TB>*Cz&*Ad-g0WHzAPI+n{IT#d2{&2C&0$FBD)uzF}i=FqRfMc_N||f3~bg2G6(_ zk150)h~%z>8^{c3E+B5=T5x9T4m266!-1=abq{ey3}xELndM=Q&U(KVZ^ELr8d=jS z-p_wH3nv0YY=SzqhQTJe06zyy8VVL^{6_6+Cj7}G`!rOi=C~Kf@exTyn$gP_@ZH#` zKW!8}L`TiE>*QQ)u%!05RB>iJ2zW^;bPE0h*yH4puTx?i%f{8$-cG5om+eFR2eh?q zk76<1=Bp`r#|W3M&VMkaH4dws3t?T)|27wbHuw&96ZjaO<<|c*fTQ(W@ucw&K>X`G z-ukVj_8+$Ya{C|HL_p{9&MU1==55t(-ow{V4Bt=Sep(wzFhd9X30ywm zpDpGXwng~Rfi23{6uE?CuC{EStF7bbY76_h+Dd<}w)CItnqKm=pFj?L`P1$vu--oT z$IBX-2D}BBmU!lBdMekIk^{M}$Z%JSZ{gYEnYbo3FLO2V)axs=Gn}}tmi*(L2vy~H zCt}-4n9<>#7+v7)FWlhu#5Ryn*TtKvQtoPc-8KR&bQ`{nK$o|FW~bLT6Q7N?5ztJP z|L~)EuHZ3VU%0NrJ34DG1y2j{G^5MwnYB&Hy(A-MEq#J7BF#^>aH(y6=I8cg`v;)a zR03e6`I&zqkzE8Z1U^9m)(6QitOsea%kl;;#HDxGlj%hOY+)f>&HkeXWXwN%;`W}J zvn9!$@_O1go<(Y12*5V!rj(nF+*mXd_bKkpy65%XmA&vmEd#+9e;zhj>KEY-r7y4V z+ZNqKUc06bEXc^7$w%q^eGjJ6Yx{~*nd#OA;YrVX8)7x8bKE}OdVR4?++V&+_eY|e z(+3wY8cDh88{bw+Y>U?yZ}p}TBH}$w*1;@#^>lm#rI{E7tgBbie7ddE+aG;V1$E|n z`(rP8J@HPyD|{aIDQxn#WMjH&K{Rg@t4 zCiJR|H|=KCjf)g*6`6Sx%<$tl8`zr*6?-`q%e zTV3L%nq)}^$^E?UtWB&Z28d(ClJ@}%8vM_&D>iUTv}ZwWqE&fh@eJnjbnE>b^rinf z7v|`)@WK#*1fTQs5jpK5BGaw+6=z-O9d|n^r(2_V!gKT}ML6ABDaW`T#ZUQmJMr8< zq$y(bHzfP^T@@={NZYEa1Y^4O&iIaW)!3Pb(ye3L+D3R! zw;jmz`s&l^wRe8y07?o<4`NU`*4uX}PxJm>RFua+`Om1BHPO3N4GS$RWUMk&qTL5o zF1EM_YltD0ioFHJB^P;yjeDDr?)Bg$4K$sb$zRq)L4UME?j3UjbCx>0=!Z2WAR6G*4mgI&YJMv4T#l zx}U?bXnpZz7kW?AW_7zz9V@2yz5Vf*lrr}MuyhO22`EK2^3zol zW41Gf*B5PP2-pU-cCP_VcQC%P9|v~t>1eN)8+$%oRS~)&90n~5clh{G; z?&yoaBXa#jc!0{#Xt?78k>tm~(|UI?4G_YGcd%>?5SyjuLvWi7;z8bXu~(`5Ep+BE z{7z(DAvGf+rpr)}o)?D#6L24(CiOsv0 z?D@f75N-zzQPd9!s_FtBD#RQ2T|hYmNQXeEPCvBMCA|~$f_J1iZ;SzaLIEw_HU*GS z(P46LAqOpkw#CQoRfvN0>aftfQLu?hnihykV!ht|qem^${WH+W)3IYb7cH=NJRgZY z58Hi*-!bpbSSnpr5pAWk?U2`Y0N73k9_4q;yKV4gCoemB$;8R&T>>Hz@1>+;MyF54 z-?#f#O5Z@bN`{tU&GDo5;wfHii@(MF7EyzDBrXt`4eWkBwJ~`EMc*uQ-ZJx)Wvvh9 zCZg3wczrvwYwu$!tJs*|bbX&xO*_>uQn7LVXR8zIZ6$plp=w`2O7Z%1APaJ_=622Y zJxS9kx7D`phiWUB`jg{6Xa+s^hc5p!O67W$f4x@Mo;#3BhRI{z!CAY!H={*ayiFgx zbL941Z%1r40=&zcu^e~~r+g7M~Z7)-eO_FPKy}sAF{D~MIbpC-%RMLlF zGOU}&|8cI^4f+K0@R+MUr%Kk|a>*OCJq%CrNg(clj_?!$D*Bh8!y`!=H{0@0PiS>AoM-Ov< z*lXt6$=KTz`z^o2ygS9ae(w8u*AEWg7QUujB<4d%oAIEFV3amw7F*D`*!#sfURiPB z#U2dj;!yhFZKF11dYhu-9=sQ2(q~QnRB>>@sJ2Y+?)Y(UQM73Hd=S~1*}je`DYlin z*au##cw^zq-cu0yktk%?{?1w62|$1d1VL*R91co$I}eqp6Cvw zWF%bfFN}4htFDah^8O)~qDxZVL9QF(``{)ikS0-V6C&9j?%Ti}X~=gcM(QN^i?2!6bpf`QteLXqc z(4)sc!cJIEO)EKXN&nRy(2Oq3^15i!;8N-?VRtEa7e8KX+hDv{$3zz|ruT#KV%~{a z*&Efq6QNP=3U53SE1IRWzN@!=YrJ=4CLHr>TgKVbQ-38XB* zLy$JPB$8N%nj;a55+pw2MJ=@_>qEELy_bF4jPL$yVMd0wIr)%+Y*OwcPDdnTs!St< zgtB$KtZs=Tx3ZcRZfbf-s#zt)jq-yP?oUvOG@HM^Tbd2oMy>yru;CUvA)wDzbNaqr zgBn5*nRjGo_Kz&Xx0`IzJkn{aqNLxfk0hsjCw;K5cztFhdD(ZQ=ah_0S1kz79cdb0 zBb0=S6+Vg#v0YQ=%hbtm-N)|$&*OIM`6(vEr=o{Md!4!ZN36Vm^yRm!(kB@>Pimr% zzscZ9OEc5lK$*8L@fL)iXTsY};Rh-31%B`Pg&i9#>{zZ}$T137>+OplBl!mJ%>MDhK?=&u`uQRI!M z9R>l>VNk4VB4DDSloqLuSHy`aNZkRkRC& z`zN5DS|nDNrB1Bh=(S=q_mAg#Go2m!aryv(FxFkCc4-uKvPLE>p`qHcPy%F!~N>pcV_f-r0zj;6K zS-Kx`HFZAL(DZnwhROP$GdU+czO1;UB0YX=@tler2|$h1>GgFQ4X0cC9&D3tzBV)c z6f*ndQ2MC_#nly|KS>t;@Q;&ue+84bQLrI8@4OH+)33(9mJ2_BH+a>D3j}x(X`{Gl z(p9&_JCvzM7NQoD?L7W5~ZxuoStWHnEY_l+S3)s&)5I*hvYi zxV-_55K)HO*TOKS^I3Irl7tyPEqS6Y(S}U&i`v9C^e-KF&y-))`dYSdk@qG2$G?=5 zUWBav<@BQ4isN6-QQDj@h0=?V+P|!!y;3yo-4bu(ewmRp`YZdKS(KEv1FT6-NkcPODh1_5J8ZJzWT2w!j6%x_P){JAQNxwMPa z8;F_31?minFZ$=tb*3FM;Nv{=A<4q$j_W<2?tq``~OrFT4J2 zEr=fU_6>H1`-l$LM-KSqB(@WD6H-3v{AC!w zwKC$UHUho2Rxqjf$`G({tP=6UXwj4X8R zs}VP&2biZ!M-_tSUI~KrCX~*+KNR6<{KVqO)#{1Ebf2d|A_ODIwFiSlNCp&*glehH z+-nS2pb4~Rg9mb61m1;+fzdLF(PF1X(RYqk^Au&cVOnB0hMQuMEhRE{XK8HG*Fzxle;A5$kY7J?E76Na6$5Yd74SjDHC$UpwqzHgzZGeP)0*-Kgne|CHaG zzIuGx8rEv4uHRc^6m?iIRf7n6vl==Zk4EUFU(AG`V~eq+E%Z=zO%Ug$`CC^M?tlLo6SD0*=n*WV`Vjn z?iQIFv|9|y8qB~+0dZAwo~=<8L%1=yuzUa!;sN~nnVAxiMBmNWx@qtdvT=C!D%zU? z2APL{&3Fsx)}MZZ#oQmoUe?vd=squC^Lz{#(^I>yhPBhK&uhS0yIgC*gIb$oQ|yzx zITGEjH!u;+4Ux|a55BR8Q_&0$LXkQNCD(KjqktYaC5sl}C_d*9p9q55LUtdqL7XCs zo0YQ~H71w|6M<6iY;>xqFf8B-!}Of2OXU=htS^8RlHwHLeYz`B8)YiPeLNx_sX$~u zBF{}>8Y%)x>dU&q=%I{WKP3974kHCt6m!v}rySK+xRf%e1G&C+EV}!?=s|%L4`YLH zPJ^eI^TNW86#Pzct288a59*}5*=*{o`(Nk~vx=A&$b<(LKJb2gx zr7NH&6dOADezVd2r%j zV$6*m!5n@JvxGA_>bvJK@yJ9;JzVQ5QNcQ(2T>TmunBs4QI4!j18JpI%Nv`Q5RZ*> z-W~5n@vu62^;xAW&tzv=m-Hp7Q1rN$i}FM>j{!C9&D3`Vv17=M*z(bKXL}3c@42E9 zNRgyWNBfE&RRQE0AAMPA4Or6l0%j*TGNCx#8jJ7u@KONxCge{<%J>#giUoQB>flnq z-i9ITDKK^DKv8|I#b7>I~T2>J}2}Dt6DmV(2EN^#i6=??lL~22gYu8t?fl-uTOZy$v&u)h0*% z6w4eIsiJppx9d3ndbzGW-}J4kL;T&iRySd;8TGfkm&FFB;=x#7!e9I=V$OSC|0P(a zarPd;7IgoF?b2LYWm)Y&e?7k=vlcyw(S~t!(nB{Tvm^r$5K{7xtg7R-spz%0UDAp2 zv*5uFZ~v+7d{wwD3o%^nh3`E|2&z6}{7-q5BO=d+y%%SSF*cX{0@+R*mtsL>bd&k} zHzx}}b8~XxSu7Bqb2fi-&&s@6)1FH0K$xDQWu>jEA2GQdSwzpMFt*%u4|}10pXZ&x zs_QPTh~;{WNakPoJwVrX`65f}S&W6nj3{ni@csrVaWTa56RvM!`s<9n$gdL{-dntr z$rV#8*MqoIPlP6nq$jFJ=J3x3VLp%aZL4@0@^d_+tX|B9o`&xAB zg}2GX>YSE?CRPlpcjLcW8l8I2Y_RHFuzgbU3u4_ynVE$j%HF!4uG)+W{`y%0F_t3go5cMu=m2p_&k&9?53douo+pi z&cO;0ikyQ{lvd@sLPyIsQXI)uVhrA<4CuZ}qIl!V1Ki&HGK?R<%8cO`0gY8MAVYx^ z%_ljo6k$`fOf1fmo2H+fG7(p+VA_+zC6mEusa(Ir@5vm)Ue|isWtuoU3w$}E5|1ZMU+B>kOR|C0@Ggls-EIVuF z{i7!x^_pV}ZHQ&(|2GCDMpj)JLcWnTSE)x~p^2Dmbg^;KStRU_j+@O@BhCEt6_SO7{d$D|?c8NV=V_k06%RyT!t9D}z z*jw+5G$$JQgs757xHyZe#0Ip9CGaFP9GM0p2^p&-pird5jz~g_;Cl6CVp#?m@8Kkh z&YquRcMZ&~5_L4S(3QTqH3gkHb8DlcxUw4AU7fkLWw^PuW|eHMUyKK~*7&XJbRCRgSEh{<)rci_O`5{45d*K?SW!ZVn)%;b84sm)BT z-=J#dzX6l$9eVy4lk3}hH)wJ_>iH(u4l1YcTN{~Gw<2clq3|~;R3_I~&sbPch+lF^Erx%csy7jGIGyWB{26-jBa99cZSyu1BTa$vCW$6v1nGs`_fgn zMVX1KV$nDZqO~G^Abq2zF#ThG#S)9xlU2FGTI=}uIv#9Qn1w9eCd2D(ZM%jVUT+y{ zcx{m&^75eN^{^!bme*Tm^~myi!`FmZv%DVGsCJgu!@1@(ni$*VJ=OMRmbVRm$i%iy z>~i}e;?;zawTTaKW=+c17pL&aOxOxne8sGTYoWwi%*#{nFh^Qjvif3itYbKB`NM%@-(J~A2|kd)s*{L zR(4TIBvDi87Q@wKYI(|fEFj-yQ??}Ym~-+wi9V7nI`&OCTPB>{6lucUWv-Zj(V~pK zam@TG`jz>0wqV8l+UlEM7s2T@5VDG{QWh^{3vn`0K(c9;y=)B;>t?t#z|+f6Z*)YN)|STGswTCQ`(Dqyx@~ zSSDqb$E`OuPh#Qk>I|SmT)6`4YYGg|=zWMstw=1NVcSJe*Fmbp7`hX=<0o`SB_%`> z?_i;N7Wrp7PwGFdMpjcL>yZCtN>TR;UhIyaq2Nw5z@H-1{1`J!VA}FSnw|oY=SfJ5 zpXbc5=cQ$b9b%e$MRXUS~82n&5@7Sh?qI$Xxd^6RL_Q^+@I(x@eE;v!#6qoIe5NA2{mvqmIpk?)nMyi#h_r zgle-2bf0&=nI+Ei7Wwv8Mu61F0y(?~?XBm#)bn!SneR#02+(ewtzn>VLZ-h+GN2Pm z^{V=OH$v!htS5e#)F9ruMaV>jF(;?2m4xJAN21*{)?v`h(AF?;xC%T43tW#_mMM*O z>=N0`*+zAVH=Qu+i^^&v?V^q6F)5@3NJK5TyEp5Gehwz%oKnGRZY5yu5tw2$iNtPKoj#=YgN8~qX#F5TR%a4F{_G1ZcW+WS zMHqsvEkQp1awm0%UQb`$gw{as+Px6LK& zQ{Wqa&5q$np(^vJV@O%wWmMtr*x?Wys4D^|uhcvWmzsv`qgdXcOLK;y_Y#L*VcDT~ z5$L^i7TI{{%1V&~}r>{Je7$5t9mgH0#nUr^ye zdttyo`Gm#}p(nqwJws?|u(60NZz2E4#Qeb6H{R*2mE{&3w#EZfi*%e5y=7~c7oZEUM{6CkLj6{$|R4~Ut)8TrO@XP zLOu8~b7(FC{f6jK0Tdrk^Gq|bIq4Ff%-0D1Si*Zl^Jj!$Cly9H6XjNdIpabUqD=HXTHWS{5qbVH7Z%`W>bpI6)m)Su;8&6ZOWpURmV~#X3++{tqRPd7&Wa-XB90omVVpq?%QdcM3p$kMeu?fXhK4H zS~gJ`a|4s;fW=iaD!}dqSOuT16s=-3c@>T>zO@NMiR9+3bjlFpsG|G=<0yWrG*;6# zkmih|PlJ>rtZOvrXEiNhy$hJlnUI4k*$8VTMwl{idt_-`Gq2 zDS^>ZkoCf}{$-}4`43+im`q<7FqytMjd6*|R9EzM-(tAzie(Inja894Ud zH}i-OUW=n5MGWs=oD$hz6he+;27p|-y5)WMF`wx9KI|jz>XrlE7k0}7?+d%-g7<~p z^1=JUZaLw7VOw6v3dLIZRO|99*B+aSjzPC|jL!*OjZ>bw!I^ zzrB2kdtTP)o|6skxoNhpecxU)-L7S;D|H>RK5^NqTqUqX@yJ}ls1eIf79?kDj__A0 zHl@h$=@u&HPip51wz{jvOx7cN=gk07y zO2B3PqO?Km&dhzw8wJ=&0)lh6A}LjJWuwUco?EFpv1jq?AEpmHFm|KbkRq!dHtC^D z-}TPikUn@abK?x9H1efhr+8>;;h{EJ;s^5{?D9_DH7b6yHitZ>JlPH0dmk1wrLy^` zweK%dq(UpFzTK4ai^-nhXUR|&eG~B$N`r(O{e;ROVX~jl6(mdy60Fb#W3t0a&>A}s zB@bctg?>UQ36bQ~y)nkRDm&Z%veGh=aOx%pamHtSaNhG1N`r*Alt9~OQ@iZtv?kf1 z4mo+xk#oG0oz__Ift|~&iBCNcF8m_dmxV+>hht4buXi%EJcG}@lUT-2hE|m7{ynX^ zd_Ze1Pli@jT1sq-cM?C*;n-^Hhm!@X8&uI2z8cK^9=W!#{PJ*Ynfiunr}wIsT|OWl z^(ubC!>r?5G(VbmjrwniTFN`jk|zsRbg5N~z8TH?g5J&66YmHaJz20aW$zw{W=-U& zsnLH&*uFZRC-3BIDapS=viD^DdJuZ;2JgLlj*ReD?%d|>zn37&w>P8@e0^zN5tOL> zJG~>PUhtmm>{MRByodPwl{-7VefP4UYbG>v>(WW%{Je;Pr(Qfm-fj0D_FCWWPal}E zbc)Nn|6A{m@D|*)Ql0B9?dT~zMfixYsA zL+RVS$Fs(P!VK?$f~HbFIqg-*e^sf{s>a{jiR}5oT0l&x>=XphtLEUUzn29FZ_~({ zr6qJE1?@l8_K|!<8dThy(h`L9fvHQgrdr0nQy-F%);(}-zvtaU!&dGTc=x85q`~Ix zQWCVVd5B!!h-Fml!pJIl= zzg6(3eE5x?1ph!TAI4Du{9PY~e^dZ}qejvGGlA~)+$*<^3j3xHkoU})KvzcBm)~4k z;+lBmTTt!NPDY&ns_pRiOSVF0%e5+h07VA1%QF*FIQ)2tgPDAd68EvMvVXoifY^P{X7AZtfZLJ03i?3>CFE0?OPt7;vli4@eE&* zN(=-P^Hc3-uG{AvbxX6XayY`bU;=*aZphxC>QAc{4uyq)rPa6Zdi*k1ttC~!3`olI zTOMi>yJiwt*(HVxUYHz~*(C6umSi7pdoR;FS>K*MIO8jADU%<2|(-N{+)-pOcF zm+%i;hjnjZ_TAEUB+J`U55Ec-$M@#Pmx2 z!dw|!U8(!|(PMnGrb|^Vi|)}pHQVJ~-l*#q?D6Dn(fzA@M-Le2%VNt@mM^;1dyl!z zy?4zrmQsPH_D1iUw*IvHI@8`p?3ylmFrG-0*4oS2&xTvua5S&m?L7>mOi!rtDK z0jY@Tb{UBegpwvSkg$!1kfWD74wHOJ`idW-bi4<&Q#?YMGFAHFmNpkZm$KP z)zM?=);g>>hcOPVQP&`uzej(+s^-RD;eHcMhoj<&J}h~am1n2!@?{rF-X+=~@5xzB z5?0brJy4KU?!C7&IUA%+Tijk6sW_V7H0u5S{@RK?^FLRW*oHoO_F^4!{g*-?>(y!| zVhm17oV7J#+eqdhCV#)DJFuVO;G7M)VE%96UC+A~|`rA#czumWJ zzdIZv?-BgWAuU?DKQsx~;N0J07wW`)`Jue42erE8P)%~~WeVOp#NL5V)h2o)2{vqO zV?pf{5)a?hJmKj`;)S}zCd`&oG55}yLO9pLsoDnPFS@lHw}&(A_DxId(EVE@SW);6 zQF}Y}^!8b{LPp+F2ZzSuyl>n2S*)J-{}4sEDP!HNz5I-KWpoc=Xv!dX&nB9Ic(-3FQvvIr2-Av1 zo+m2c(pX8m_l4+IS&A37%k=&>VYj0fwzGny1IJ8lqCH)8|L9H}b}wOM?#JlZ@7+V# z%BB2Hgf8Dana;R}u)Mx-uXkRw+k2cbMJJ_|ev~N2zv1}~gz-!H-9f(dqQ50ir)kQ6 z!}U#u_M5R~>PYCnL#xjD-VOEE`{P}_JVZaR{l>`n+m?GV9AgJ@1X_RHsTjE{c~CKm z2yAVCpN{%U{0J#5yoK|;$zBvk;XXSU^emF~4gyf8?%e>#tgk^xKV==S?O*3_@q`V< zMrg$c`45kkVdBK-tW)T;D;t$}M{LyJR#^M&>f0xe#zwuYR8`a$b4q4UY}|IGVKRAv ztH!E}GJ8>}3S(})Cz?lCS($MgYFJ$fXHbc5O)r*)YnKR@{<7XT*s{snre(cM_g+<- zH^O_)miBHt*2>!6)q33*?&8RIUc?hmOjtNmoO`P?R5an^4`bQ&=W9O zmiPWIrJZ4U?-dt-R9oIVZxZ~lnZJ)&-g}^$^}Thh@5PQ&u&f*wOOP20dt;RxUCqMY z1=CsF8?AyJ6Mu6ZukQ&>+|cuDV%3Zl|KnKP>@2Utxb$JDX*PX{L^5_g%DI{U_26s{ zB*_&Dw@<@LQC54wzi>`HuM_WBqUsd;U-rDngBr&qXvu$lJGsx zTzj5e2x{N=`^WhIF@DA%>sf2B%UsvJ_I&1A2aiRcAs_PyfK7~i^rt_11TTz*gT8hn zKl=IEa_N9yv#vScFJOfR zT2mE+NOF+jBHsE#%Ko#C%0801a{Uj2<;sqpY zz)K9khp-<3J?#yt58%2-eL#F&$4)Ufz>8MQAE6(pscUb9qJ`gz2=qg?x&=F%7`q?9 zUc7cwr{Kt3cIu4D$tc0@=(trwbK)JoK{gcGiSnCn*lcXW|7L6kgo>*x#I!Hux!Y{9 z{>tXb<;$WDDgb`|54PW9Cm*pZ zPCoW8t6=$G3YEn~6rbPZ31d|jKG|{}sZ#Y9zW0C|*HM5f+aJsEWwQNE9C^cw93M7g zWJ?bv;Hn&MakK62y0&^M)>PEa&kWgMu|`7k;`7If5S+-~%g;e1Z|@D?if%G+m90B^ zmJBN)1HPVA2K~5tq_~P(SM(X@Bl?Wn){Q6{syR^_<#R`uRV}suEK5%AQSzK_C5xV9 zd*{Zww#LoU{IQmo+Oy#gW-J@yeWtUv#gR4?g){8g195si9~;c}tSz;ri^J1D%Uasc zbt?yaO}4By)NTv(GH>)MXXVZ|Q4EPv1DX#rK{N;3??(?mw6K_yu(VC#d#ZE2-C1rVq=30MaaRmPfr<(w#CRxT04guOk~vUk@rox zHiwVesDpSG-|{SsUtm-jJ`b>Bt^O#YE6%}PPfyWr`L#`636ff#XaQAH06vEI zox-q%uHdZZmXAjn&2Hv0$ByQQtZtN4rJXLP>U$;REP&F8O0!xh%)E3b7lPA$L^QHY z>*whD`#JmAOKba>Bb?tdNVnD-m0SBLli&1#fgBuzx=p<%4$FrY=F6b^duKzhgfg86 zzR8blsM*{B%f{PkE`wI)p+_*)dJMr)J5O?GNY#EdE2im+-2V=H3H!&SH?hwi1R16k zJ~akQSkfvs5(mZMx&sfq&3%&oZd#eCX8M)F^D}Re5QuIP7hxn}S-( z)2Kc4$XXveCvXa<)jJE5*|9xajRRuen};PG6hkTAKw5~c=_4zf*gn;9eU1dKGu}w> z*3xBXb>PaK&9#B-g~#larPA}@sAXo9OxdGU`8?Z*VF7iCHrTM8Xt3oJlijkTuJr-j z!qc#mk#IR9TNMp9Y>3w@%l(Yl^O4tJ%je)xtW)at%{)xCQ7yD>DV(7zSDe)+u{cmG zrRWS@*(B}mrIsh&3|(2pC(+KrDk-SPNJM9FCV!Q`@0PB zs$QMNjM-Vx5mP@L>#T;l+_T!g*iHv56I|C+^INy?{S=XPjmKipa~43C zBoC?9)=41igI#v0_LfIpa|)&&4-n?VZ1S$(RbuDa6_D?Q^EW9LK#x_o;doUqIp zB?-D_GItW*Og5a!%3t-E$rPV9_bk+m9GK8~Qs0?U z%Yo_Rz5|_6#a#u*epj4>?W*oWyg6(SO)XD!U?%e&h!(P|@>zQn>H)oZ>$MPCr-`}s z-8$GS^=cS-(R>8%#7bn!Ib>2q56<-0Z zxPY{-bq2Jr5&H$#KD!1I*ld=2-S!?H5@r$dhKflTI^P>yTi*r~7 z#GgZVfv2Y*4imanxIT>wz{*JR9PT?iaGO8_?s|j%o;z8HVfEwSjj?~XI`t0dm+_$> zd94LA&3Nrp04I60L!vd)SQS1F;=pc4S0X=_L*;QXZU{LLvE)okWNi-LrtQ$_=R@~S zOx-#^dMGRM_uJ=^ztjUVCv&Q<48I2D^IAV~2b65Z5v!PL3D?&xj#wLnHIQpSq;L-7 ztU(;?(3@<&YvnEOl~u*CLL4GTek*K#8-xp>%*e}6m+6sLmCZ}!k#>iS$$UEl+)2~H8WFuVU0!M|;3cla(|h zvb>3TkZ)*-WH!~!ip*LeJi>$sPPeTjeUaqRC6R25E1Sm(D_e0BWkX$M9pba%NOm$R zI%|&Ph&-6}4i?+QBps1Ve_dMmMf7KJ4(H|pj0?KEBFk6MZN&wG?dVfZxC2nmKF=I zAwMyLany)1--p|P>cgUQI4))a@j~#D~vj+FhSYcV_|PQ7H1Pt6rWWwL3ZLwKc5ZH zYGT5}b;H9=hzNOL^Sg?=@8OaP*SSr_*g>*nM$g9b)kl^-`)uWkv$D)iT-Ag?EOpQV z*Fp7zoNSL}gBMx@iHk8VR`OQI1yBgJ@Bn%??z209C#&#eIqK62u|Qw(K@~;_=^-#- zc+fsuXd@^alFUsck*vb2i#Q72luYKR3Bx7*(GC8LNu3G#hhB$`EtI5Jb-#IFF&72P z96@L5=`}@vd2}5frAN1MDjt;dGrSucHPeV)*YMu z0{NuDms9I3eA1!F!C>?)xzIJEFeQgf(qP;smk8GW==>L;jIIK|14YV11Gf#VTMAA2 zNuGB;f(zLmx)^tg2YAgK?oNlHI}R+78^e*GJe*e?XieYq$hHi5%a%8C8~CBlZJ}r< z?o&htxGw-D4gb+d=nL1;Og;4kLw7iz!)0=M;=MNa=E$&{YI7fm@S?cf{*n1NCE|IW z{g1#*WBu2C+)l?cp+*Nb+YNXQtgNlk-I12AlGEPCvkIWElHxC_$5bN?aZHujK13|;rAmc6Parb zQ_et2kViP1N-hY875FQ2(p$Y!jgwTDhhbUI?3rjs?TFhVSrhSN>(1!2#FJ90ui_15 z-;z>F!IVu*1nB~bCA_=|dR&yyKOZ#Nyro5E%K? z1cL_R93weYA39T=p1Yn)KkS7$0n;bdgFNEoOUS#%azwbD@r8)Bi98k=kn<^26*jeW zF|9nC+J7+g7?`o`5n&*B6Tu%C29D@2Hm1iB{f(hCt344;;j!se6bS_^uYy~PI|-iW zodj)QPKGaFt73!49|5@-vcO)c=15!}_vG7aUh7x4UG64m;`OfwNfA9X`uNd(0QPDE z;fQ)IJfhC;c7H%A_@WwNfod#YRAc#~8p{{eSiY#n@C4#k0v_A@a{CxZZ9B46K(QR%EpHhS9t-++zBz@uMFT`5&<6n_&?%@-cx?4 zsWvw|b^LX;aoMR^*CpenuDsBCY$>@5`%icw`B7Hv1$N*+zL4&Xl4hUjbfzQcA6 zTahID1r?(^YID=VhQ^^WPAF>}M1mb0Hah&!u{NgMC(*aLJ12A)vwGzU?tD9r>;qoz z<4zWbfc)Gf?VHh1*F2*|zCHN|3Kr61`OuwFZ)S}w(o_rG{s8o0qE~}*6(6Xz&>VTT zdM6(dO{DC1TCr8j1vf-f*AL@{o)uoEj%8O_ZYZDHhO>>{9w}Zy!J+Zw62&d1*iHMn zAM42tamz(+2qFZpXES{mH`IKV45IOX3}RclSZzyD$10H_#tp4U(cQVBH5eJn4c)+! zlesfQmsnj_Saf38}SIbj%EtYnw;k30S{peASAR9T5+HoOw>Edwk z2B~FLkFuJytY(z)(89f(V9D(Aw#M$|EvMF*)}>Geiso|^ml0?_#nQJ>IHJ{!7(_Fz zjv%72IePqcw~D9<}lDAQM4MyszOLm8#N8Kxf?h=*;KLV+#kb%kvwIV3*%g? zYeGaWO1&P*fM6c-u|moaWyJJRF+0~}JnmUUEKUz?Rt~f2Q=8>y4%V_f{SKs0^b zSE0UFfQJ^ofKFMS%oQjQIEBm0JFRBrWMlWl#oiLubNP5^;Rz=8aS}e;S&Ol_A8Nzy z>AXU*?1BMwy9|C8S|i6O6ZJ@df`lowvv-iEkYx9OFOu#NMsO_qXVp{V**|NN`{*Bz z0&4-c>gn;?LDo!2Mo8B)uZPymk@4mAkh~{E;W$l9k+Wfnd{!5AMRH>rM?`Fro*3;Z zEaj0`MsXFpFZWzsvSz|^!U`aE*&MH%Ko)UIfIvf&6o`PrL$({d~fcB-N+hCk3?P?52#hP7iDJeWy;~ z{D|eWdbVcqPF&Rn9@8$2qjX_xvttuX9=S|iI(QgiISXYqJZc0BJtB2mI+QZ9uVK}q zi-z2*$8uTkL=WEg%E2v??loj_>e(D#LuKal4Axef!&XA;*j2g5nCQE8&@xSDwM>T(8nXJGHBSDc)?jfTsWrIJ_SqWAAJ7{9kI))cqubX!%A6yoCwf+}@1gN5PCE3A z$6>)KC>sH=M%KKbYglw+jpP)}3e4({(hyag{e3mW@+Lkx=g1Jw=c67MU-_eUT+Weuc(Wnn(jpSqMqp?P!^7y~P8rf4FsLC7;y#*k}=&pof zXpP4jfgO@TjjAvyKv%zZ&RKs$;Yqb z5qIErUGAnWe%L`pL8;)w9$k_YnhF^qXbPWgDEoD6HQVSWxL$hkt~ zM1FLcU!lhYWk$f3Q=kE$zJ^5Xc9WSGpvmNr^BhnX&iju9_O8KYnmWi>s3V>8UO z0gbtfzlXFcvbYL9P$7}UE5PDNk}4V25iEzh5D$5*u!0rK(pZOzu;Q)wiKxQJ0zXv! z@~e@E{06g0)@+)*Z01NdV2oC9ey&1tPNO=HAvz!|4vt44Y!`TfVvkvB3BS%0I5OqH z9Mx9KVHMF;$f<7z?5E-)nO{R>a@LSB8U&iBkc`nFTp!J*8ie1r%T8S5XRJag>jaWw z;0R?j3g6vOfYes=@D4Ls8EGVGk^|YvNgaf|$ZTxnG@hP;=Zu$0Biya%{GOzdjg?RR z>{Q;qGL?GRsS&7PM?pglv<}J6pvCahV^hPOk{?Q&!}iR^cQ{U*U-){MPK>?1?1kjmLL(JCGzl z7$paiUJn$ekvVDo+|n`6g`&r&N4J1X8RfG~#N*SW`!b^IaE*2H=L+YKB3F@&qZ z`)GQ!Efn3&b4Rb@uIW2QwsxIGI)pIqEE0Z0r|#spo6b6kbR8t_brNYBP9iV6E#&e2KlCFBhhTIc&0Ij49I>5tsj;~dhk0a8J6yUrnf ziJ{&(q&Zw5a1QBKhJjZzq!|p=b4c$~_bHn*oI|SOPQJ%Eql_lM?+^9_-q`?}>7R_ilSqF8c(e%@V3TF~pf!q&Q|vwyGEGjs@J;oWZ>qO^Q@!Pz z>Mh??Z~3Nr%Qw}7Z>q-mq>bd8HZ8jJ*22N{IE=~rDI~v|^fOh{Csi-z{nrDoM9o9d z)8v-yg)T>Nc==oxx(r}5;X)U={{$Dh+|Fed7rM-`uDvhi!Bl&H3NCbc(uUjnRdAup zAFbcszk&;0w!x1JU0#LL{X&pqvg_*U$GpNk)U-sf_ybyp=@cV(h= zm;1zhq*#8a5Z5|4@m7_G@GEz8ai5FavT_nXQSRNgf}8l}Z*tq(FNq99+dy49y7+cH za@#sWNg1EygW2i*tZxb;1n`lDi{=pHqh0Ot6U0o&TSKArY8PgYGk7q?!w*7Oj9;{g z+M8UKac?z!Y3^^u>3Ne2t}Py2T!qgsxXXPX^BB8Yv?OnEdCxuG5xR6hcK-no{^3w! z#uNq&t)?d_ZG=3nk&2{^6p#1ha`CLQPg1(w=2FF=g@fiJL)_=$?&t_Tj3k|*N76<~ zk^xV7MJ1(u+6F{p0aw_7oLE4r4ft*>V6+YRb}ZmZ8!#^xaFy$3r-p}QF@R|Jmw-Uf zU4}C;?legAicN8IEX5cba8oQ`tPL0!3%J?_q)GsKIPIDzrAissv9h?pg-#>tQ`!+q zH8-I9{gI3j3{0GN1$?2%PVp>Ab=z7);a%>Dju1{-xy^`>Hrid?8j5UVw5)pfJ|-RM zwylvg-6MOk)`YTlNCC4hcgLZ)Hn*)M6glj+9SDV6B;&&a-PH#|ktROK+V3VI6X#m@ z1r)h$AjL?^yun@85(*EyUVO8zmsE}R*{lifvi1;eeUWDw0!&ZfU!3<|g-GcDkolblVmuchI?|tVHguP>zuUIl z57Soa5*XunN4g#`ptY+8IYTfDvB#j=xq9)bdUx64)W&+rhzkIE-&S~&t?(@0n}?;s z)(tLxN`;cKV{#63Y$G5 zC&o)_2W>gqI9)a^4wswK12g`{HH!v#|2ZngSKp&(?wcT9w*Czf&m4Agk-sG-s z_p{2S=wq+S8NN5)f%pNBcX3NIDEQtjn8uzUaaS)+W)+xdvs+r{!!ZI%QqvK5 zYRcB`CHHqrQHf1%DRYoG2Rl#=QKq_aYqh&w1X{+-%|#DN$afLqU@F@%f$9uwHgno!H zrQWEH3BxW{VO)7Cxz<3OF}+%79J+X)Te=v%B5{6&scD8obAfpXHc7&+L)hTqoSSYg z2Hd;++jv}t$~*4r2LA)^*g-0?i+bL@A1yO%0#f+g?HS3qk4MtrmzSu!XASR!EW_UI zo`)T7;FFSI8N?{}vt=ew6ngO$F!Qj4rA)A*VBqvA%4*D)HzbF+0Y0p7E?2m*B1_xa zP}YJQ+~&eiWO0gn2iA0a8i2%dw-<(LHeq#HlU~yh=T6&|K5vsdVvT?H7I*F%e>mex zw?EeAO<0>7{54yAZc>9kvPs@4i?4T^8~j;|C%AL*hQT5pSmV#y>fW&{l(osduOmIR z!7W|mk6^*ZYlbViKa9Ufju5;Ust_lXxcDl!8pW*VW8}-IeDQ?B^%50tG{JYQ357e| z)yQs-yE;D_GLF0`R{U9AI4HSFBufSn>#PL5m68LV7Sk|;?PQY znYD-*k^&dnKsRcGT4pgprR-UT~Xtg;-ns zMWyif6*u>QKiuSEC`Dd(m+cCLkD>+rkyf{~fi15=%llZWtYG$iN_Os8jHlkOg zM>|4cY|RdE+u?;^s~|5m#z85-Hcq=gYqQ2df8Z_9;m_J7As7>AD|y9aZI@KBQDVzN zxv_jYLkqEI+TIbGU4<+{xG9Db?aa6a7sXhOP1!qrToi+Y|DMi3=P#YCkozL|}Dk~nq({Rgw-!xMfDG`(=b>4DfD z^3l{Q`DiZCfK)|;KJB54hMa!SL7ByV?4zL&%7KH66RO0?_Zj-c;+ns~fkoozzn~?O z?~?%T(a&w7CddsS?=h#u8UH@l*ZHj&k?|M`ZNSD>ZNk6}1Mz)$WG_@l zx%^Pv4-4R-d#NC*f@$sJ_|=nv#M7gDVcGk*yDq~wa-Gj@%l36P(pGq{!@n>3piFuu z@MsHu8M$Qu%y60R0-J{e42$2i!WkF9T-GEb6qj(DCq7r0bvcWn@$WCyVi>oaBx1*y z3*m#;$GLHE@4mhtY>P)ACVr6-zxXgA-p%ds4Gm5}nS-BkZHxy$5lHyUX8aZl-3I;!2X4yWjUu11atwX~?(>vKMUXJ90ALG;8^5o^#o_q9 zu4`*~efYKT;KuOlaBr^kVQt)qp;^}^>h-yNm@(F4!Na}?64bmpiA)P4qsLs7kX55 zHL8VQn1fNX{n4~K=xnO+6IXk2aT1O!oUq2qZ06bPV1CRQOQliIyK7`RPr}dE${gm; z&0+E;yCJs&e0R8sjdEEc+7i}=^7tIa_`y|e__i&ciEo!the0} z9r0J6pfNHvM>P~;S&I)>*KmQT*%DWGIW4wMD z@3Vp0<1PC||JZS?^`iFpBm-3KF-V@~Ta6C&ARm(eOjCLi>{BXXY(QeSdu?*~%3L^L z0W!wTHWG8tC8)N<%t*2U39*2SL_vagD-)09(EWR$D?oL!6^<0>F5q(e^;MI@MO2&ZFpczlH zKkm-0^Y7fqy+LZqWXyw5>Y~Y5*g8UqnS-$12w^hKMIclNz`JEvHE%GZV{fw>L5C%1 z!4TO<9-tn?Q;I;nL8S|HBhAQc@lZzKo)n6bBg{Vws;pM3OQfuiN&c_8aZ<`^Y=sWH zrK`9Bv^jErSEm{N8u?;g+pM@v#FiO5ZAp<;CFNx%$ZmF{y9~m=#VRDFeM*9Mi)V2% zce1DhcYa8SjUje^*zL7o)d=BugE$bLyEyFh4*2;#kzE;5WbG8OQYKRPq1fSqP(ms{e40B`7cRJqzD9i6F;i(M>b#~r zFQXv)#_lZxO%P|>rEVKq4&Rh$pqIO)JBUwfdsU51JJB{fwKC91@R@w;Hlxe4Z=#+G zN7ilO-6D1`d~(mi+g8RJxLcn5rnco>+bx^kzBwIhQW#>VKA}#Cnb=!iz zQ^eX<6iSjpRZ{cLJvy*A3N1V;Vs`tSx4n*%K5&${aZ`P7R`uw`0UtI_Y%|5iiQ6`{ z?-a4xs74ElR~?HoQe=qO=9RJ6y_cZu7Y(1xL1;%mq?bPZ0ljpEj0&g;yOk$(=w2Sw z?0uG3%bRtfRMA2B0p&oSo`HUKn=zBC-RkV*#%lS*=hvm5ZmP%5s2=Tl0vgL?N` zc^_uPbr-w8bTzgam{*?g;6{$4vFu&eHi@Vec|nQyL{?h9Tr+Tc0acb#8YD_xC}TpR zb}zGk&oZHU`+(9MD|R2H;iWPw`P7@3z{#C;XSWMSZ96_kdSo8!2c%6sb;e!2AQhL1 zoZY5tyi{)KtQcF;tv$&Ot?RQr2mB{>JMWxzJD*LRm^PkjPHJAoH$i-}rdVcysx92> zfo-6_W=B7F`z}$z9xqOxS0~YDC30U9*+S3kYIpmpX*HYU+^1Hi&)dN*5x$|Zo1DFi z@3X{N7juIeUMJfjZWL!F<0>Zd5{7={l8hzX z2!x_+yKS=+S;wv3)#FqM*6hH5hxYCTDTLG__N)id0()tIfU$HN?nQ}>rA!EccTOGT z!Z70Y;gJPyA05?z0=Yd~xP=>rM;04qXm$5k-EQ-)I&@d#ZPZ6UbBt_v=km+uLfO7; z6L~(D@kBOYcndo-Y1Req>XjjUPjBzIHT8tsobQj!n+(l3^z7Hl?)n{R zrg=A!aT9JLGr94Y3>#2kn8{EdzfO^o7zNqlBZ)H+yeYm5GA7BZrUiR%KMsvyO8R%c z&BWfY{uNZiDZ&&`UryZ()5@SroOg*ESnFqgP!WSErsxYkEoC&g+w=Wl?1WkDwQe&$ zP!^1ZMm!XGS2WR9<^@-HktNd!w%A?1nXN919b?G|krJ=SuF`K{vAE8ngFCj^JcG^{ z9FI_><7+XNDcBttfA3h4?e5K6e7lArUPrO_w4AbW1BL$ZJ1&l`M&5SsXrP*Z?#fVP zUMF-wAsp(LH(2cTWbdkPfe}65!e=iM?cO4xFK)a`WsPuGkL`O^X^kZSMPy&sHUU1y zB6!=iK{}~KQrZ-EE;?}W7+g`ohDhRqgVi~8;b!T4j?kz0mc?i0a(`I950mEIC?9Xl zEJT8$?cYB-VC&9>#4(bIyVW8rfN0|Ez1# zILHWFakNpUBabYz1??M1B_>X>Q}#Yq;EcOn=mK{5tVwP&DliLggI1w#7&g&uM&D)* zkij(TJo%Ob4TIjm49D*i!AYqf0gsHJB6Fe2l|a-wt;*M(uu?1TRn!GR&n|+r3(>@?qg|F&aINnIn1D zO=K+41lu9dR<{%_@Dg-n{;-tcz4ii68%sW8U0Rsoy3Ke6W!~ymFGz0OE~TMlmWEdp z`aF}166Tx?VPT<%$VXvw8d8PMnd(BD%KHEdESq9eVZR6#sua~b=x#@QKtr$~8OH-9 zpPe?1Ofxpb7;s=x8!kXq-9@wSdJtBz=CItuK{SWG#E@U5i5ZiHBLZKE_XE_U=&LF> z7vteLR$V_Vzq{28DaU)9JY>WdKaxO&0vW-^Kt_B9vV}Yuz0Ez?b6TEo^(jU3*Y%VU z3IwmSN~KPeZj3buV9%WKs40rF4t*tTRGlCC%`SqTNv2q_*mS!^ZqRZNYlAFT+uU4f zoLtoHO}A7q>j_OV8{iZ2xJ^8nm$|tOe(0zf>m5*S{>Vu;7X^Y52Zkn=FVkTX=>!Xc zqJ@uvclq(gtu78h-;DW@IPU^kH$mMY=pCv08K|~Hk!pNG3o{3dS&AmnkY&J4ZZ5PT zus>)ZWrdLNA$}oKskDn(bIw5)VB`smJb`0509%bwECg}32~h`?F!F?|R$kG}(_;ih zE0PGJ7G-8(fk&l~H!Agx)E0Y`X4EJ9238bA0xODcK^<&sz*>uqk~WF;%0jtFU**QF z;iTq+%p1kYn8+~Dd~ry>$&FjZ0Wkt8*1K`-GUm|kZ;@Nh+90ihQ3x5{bE^;dvzkOI zD5ch75yv7F%7U63`QSro6{%TF-z;njQ1UuXNW7IPCFUpwD89q++N>we!ahdIY6C3@ zg(un@ufzkgCS$r`5F?Ig&ZyK!99SEn{SXJT$+|roDl;@4-=yuK*%`@9k>p@S=?pDQ zyH0%5ZxT(^&a~m_kG`2B_Xj<2jQ0oa>3M$;ep5mTMd+xDtsYtQ#YdmUqx5Je?+?PG zAyW=eeSOjJ!~4Y@<+)>15ROVy6V-esK5+=cu_ua$y(pf*-t70S2;iep`}l}x>>foE zc6sQcslNvTX?QQR$>0?q6em9i&h%*h67em663)>*J?+i)W+I^xQ@|u3|1o&e7W?Ld zD_P@1qvPZ;ScfFAiR)lVJaFScY?tDXY{FeekGg^E_#ZU5cV)*9-3T_WDh)Ri;T9W0 z{NzR2(qVb-&GURCU+}qcV|}omK}7t@f;by-jgel$leTnl0f@tX-km|A=m`*gap3r7 z^ozTt(2NG5x1N^}fl$0k3B}kIL_*`4$>Pj_k~a=D$eK(>k+&7?z-aUhoes_7h*iF! zgD3I=A$P<|-_Wdy{UGU;(+U@jMnSma2c=yZ2MTdh+R`xva5wY%B2#i(>%B}l&J@)2wgLapYMsWXfjB}7nC%$MCMeq1?a{3Qz0q&#i{))>!zYX!2d#l@h>)TLOaClfS+I^VNajSe>HXe`1t3R;H zHy`#M?LI6-2*+Z;F}E4&`}p-QgHi+mw%;8A;;z}F7ZHN}Afq1PQ()KzuHS?gCGy@X zB-<-)V7V`HkRjNBq6AooSj=2s0j-F~huuJ3JXTWl1y&5y8tqF@j{1IjnZ+T{j?cf~QY9MGBI$da2CnguHu{Vszej z=lpMUUR9|O)ZJRWNU@?9l#RsTZYg)c#c|Aa5=1h%X3SHH?{NwSRm{R?bx zeKoaEwJ-0+GIqMlHVjNW?Z$10pAD86<&Ju555-yTyAXuLvS)me(?`FJ)V;1o>b9nK z*^>+3_`kRhiKZVP^**G3KuWm}3D@4n?nC8*w4h-*XT*v^2T|PT_)5o@43cX3x_qJJYJ5J!_CgZ0ddHfrq&=ZFCA3wm6D`fg;2JQ@qQq!wJ`*k<^JYupde#Wha-=@H~at`To` zir{gL|A-~`xW?77fXi&1ej5uIW&`#}0N(x?!|Q&qu$Ww?WQkn>TI8eijSIjx?uzk^ zO+LvLw2gVdxDfb9NE@Thx zkwVVD))ImXEcdwIQi!u|5ZPhnAW@-d>+pvi=xr!hs(i}MztKxLQp$$1OdgJwgfweF z)CHFd>8RH0SBil<&4KPhiyRv$hVHv<)J)hbfn021E=P8tJkM_ZhUm2a)V!FiHliHJL32%sF^YCIM9>(^C(5#H`9%ISvn{|= zf==VI@bbCxmSt5kl75$ru3@A0Sp=9wOcz`daMpT=mXc+mYn_&P0y1!wpJX73#J<+i z22I!NKuyQO7m?Q9ny{~? zuqB=Uq>h$A?5mCgIAeIO0&4xNG)o|6OyE8u3no?8k_0MhIlKX98}#(;ff{@={;4B? zGaO6|0+BqSoT8XbBe+}f+XC+K1L^aid&2|KnSoivi!pH?2*h@>Pu%n26L2+-JAetU z#&L%i*$=L@L74`X^b~9^TP=Zz4eNFx5TR`22B&M_Y~!U~*h$~2Z(nRRg+PSHu2u@+ z#tYley~rIG_d&-+0?|89#j%t|OKZ8Vb8ev{zQfXlY`YtU=bS zw9BOtGp~d-pC4w!_Z1=x1R{2@_1Ffdr{b)`S`a7Lh_0~&B9yed-4O>uS;r9T)>M!s zYe8LrIV{F=Fo!MvFjxz;4|gS&IjjbA_?q-ZX)Ae;8uZ2BH)S)FiAa(o8xA{CIFU@t z9m4K^mmfb$Mk2(%OO(f;wtuW$5>eU?CQ8b*+~ML&-0HPFAJc}7eC7z*$sZKiXMsT! z1#|UU)&R=b$gHbjjLrRiF0y&ioeQBe@$N({FHfNkSMKwUc7EOsnzI+}g)dq6Zn}_XF)a*(Si} zR3rnuVVxX`K?gwF&d$pKZf!6A>lz#mBOsM1c zyMcCpxRZ-?WWfOUKBRAa;C+oP3K{b;uLwJc^=l2u zJsujx*Otc)qS{t?kFFC^%T^qm2w3)UsPel)3xFdmmZMZ?L~cs7+TDpXsdjT`Dou); z@rL(U!d=~IE{`ST%7anleaXc5K1qCuO>D`+z7kuq5E@Qw+T@g|B@3(3I4^i);cB!B z?2WsKIIo^vWZ@ou>Vq%gVzZ~agm(IS@$I%!yP-6!Vt8j>@|;N z?IGP+G@*1(DTITy57L9-gIG9_hVH^&wzde@ohB5qK%!weqF7iYG0Asf1o}arVdcVK zHW88oe*~Hkv0^kKa4(t=QlXi;(}ZNI-co)LIK~eGS1UgV47JIDZtNa#6Q;6Ip$&Z6 zogXZ1_ea`fvVo%mMxGtQ4+6*X1jefx;Tp0G7_U7{v>4}z8MaP1M{1ct{PpGsQJeo2 zKZx|G*BUZv99bWMAC#6LFMBS25X>NQ#oy)15AIWbaENIIS$+@~8LY-q7{qZfk0-=K z;RoNykt?FsfgjAk4JbDRqZ=P>Iv;vmToUyyj~}%6uTYf@F3@U%VVVb$hI-zke?+Y5 z(RKWxdORMIRBfrkzvGfPo%%Jbsh0sOoXLMSx0In zaSXWL-gF^U*g4du_Q4uX`Ju3e&-Y*rANN>8P==46O&M-<+?&B1*7rDr2-1+LKpK*w z1MJ2aCVPzG<9!}M6yc3M|2M-s|Cc3a5P5VF1Qi*>32|hZhR)DKh@=cBfHE9E@|ME+ z$vEdYohK1-2oY3abpW!!U58VMkNZJZQXpj20K)JIVHD+%;`5+Kdmcheu|mxEgjjTa zzv)Y*?e!ocjy|T~AR-Av9z>J_iDw-|w3MNqM0DeDTye_TM-kb<-cdvhJob)Kj^qq` zpGfS+9O679nZs*^IlT7&XAb}0&m3}QgCa|Ec=1vwcO7p^#1T`@^o&W>G3rpJcS7E6 zr0g(}FLJ7TQ-?QHVjTff7#=j9=zl;Rj`$6zL%byZ{o&MM>3={SuK6d$=}sNqCDb8) zU>={DAGqB1`n~k`aorWp6$b0E_Tq1%{Jli}Cd=Pc`HSDZ$J2=++;f#aY1873_+6Dn z$)Q41Z+}oFc7GP4Iom^LYC@B?Ek42PuD%aRr$=i^9op-zZbiVkuDkjYp7FY?Kcc7L z+Uufl-PJbwA568^N#VMyx2)e@H}znuy?zSUUCDJ+52o7dsc_vDudBj!S7A84uei0&&~rec2h^KDru@!g!SM+>T(h zDvLI3an;fK?WZ1X?hK6H7>wqnWHE>BCr>0?S#=`e4*ZV{xc(oG??gg) zz%}B2b--0{k@dTKrz6{Y89yOZ9eT9t?GV!Ez#rZA?%q(eDCO(G385MW^CEy={TVF0 z_3dEcm)i@cy%P8r>9K3Wl4d<-+Q@bNr`h3?$YTg61Ut5!o3ClS>jeY-x0UWc|zQB=eOb(In{8}qPv3887a31m(EC;e#?>~ z=S2h-e&GztyOw#MfOp!GO2^<@x+TGsvD231IirYYq+C4>(LY0+m2%BA$TSm}GB!iD zwCFy$vI<<95eQb68&d<(LFNZc~#DRIl4--}!1 z)WW4m2jB*xb(H8G3957wq+G8jQ{lm$XL`!j?5u!DQQ~~rljbnn3N=Do6*(QANJAx` z3g;Se%bgJ1V02MR7Uh~No`+My6z+TCxjSXXElbLrr|H2!KogcY<(>ykSmM;d!*+cM zE(Lv8+)5`=q{wqd!=-f7Jn6pTN!KC*6*--7(FK?ZdCtX>w!*nq+;S%!F6DB?t#rOG zZkbaAH`bx6B(TI;FM-}Pco`l_^PaesP7+FGL!l!JoGU$%ZuUgVf{S*-Kr3>NNTWT> zMyqtrz!QkBrwErwMitIDam$@s;Rcu9&2}qu?t%w(2(u0kTRelf7?^GI2XRZB4dNC# z+*@PjA_+!Yo^ujjj<$u|k&uq7Oq{ zMXn>BpAwvga&H4piyoo;cQf`TY=0_1K4$IB!T# z<~ip}ZUs&J_;;(SNC2pmYCvKUuT%H#>e}Nm_MR~RYrA2pBq*noBeHe%0 zIEOyLVq??tdh7G1<#dTt;>?4qWB*N0vUp_1-ed!ekc?-r;fkD_JWr6GFN)`3F2j}1 zeDU1P*H@X7??uDwtH}8UJX{d}3YU$&4Q^WWIE6kcLFG`bRHMC%vmAN%bmZAzsT7E7sCa6qY~#BaI9w?DYViVE<%(zH^4;? zdwu!^_~^lyD{#IpQ7fF|kO`gXXH#9sd>64}E1VQyHrZ5$1-Y!{IbQ;%d<(^`be4!) z=9I!^JN-laMa~Q2FL7QMf4MV2%Bple36~vsqqqgm?Gj$#d;@NDGfKh2pXZ!_MA7Y( z(I9;wdV)zt0YXglB6h~WoU9mT1y&_w`GJCID;gQ66e!!Db#1hU*Lqr zt#IxWx6=79am$=v!=*fx;xBS`iNC^m11@SVyN&auj7ldNZYWxrvIL($U2;uAs7zPL zRpQ(UU-SS+z_)<7co4UH(tV7%dTZMiQc{uQhszd=h+E;@FK(r?RNOMB3~p?}T`QjH zT-XYn-SDv8-V`^_iARZCKt{l28{H&siStFc(W9*8e2G!wyaRc-viT`;0+QqXjp0@4 zq)JvrPJm(F@cJ@vkW;_Nc>s{pu|V81XQjAB&cEPddq$!508*&8;PUOnVh5tw=}U{G z;W%MO!jC3f#Ez|SCaVt(SmDeN5eu9J;+8n?LR3nbDEU@8qv29?tZXIDGzrXez9Mds zvj{HbD-^fP`EPN{oqvm4;OvD<;o8J4cP>C76zFm*f;ZBpz?a7HUEyQ`M)9gDaJ~(s zEAD%c3E0o@B+07Mxe_kIFcS)#nZw3EWu-Ag1lzK_3oP7z8Guu8#t1urXjPeBrLkjPgmxLHA#g2f7c zq~JFSsub+AAn4rnE+u&wfAMXDQUv@#!3G6Q3Qj7x2sudPs}uwk%u?_j1y3mWt%7O= zFIo_E?tYJwWaDq1^JA1E;5h{w6*Mb2rQqYpK_ZV)kfva^g6}GLQo*wdY7{hD5On69 zq$EGU-#q6Tlp^4d3N|S?sNj7CgOP(o9;@IM1z%P0pn_!zey3oag8dc*oqJAEl1K12 z&*8=H0#+-iS8zzdX$3=&gG9bs!RHjrQSgw0rxg5NL9K!V76hHIy-yi(kiUSRD)^Iv z%?b`H=uj{enMkT@6oeGaRq#CpPb(-_P^aJ}3xdwqPg9s&lM6gLD2a|2Zi||@)z)P1r-V!6dX}-M!_Y>L{g1YaI1p5 z6g;e;Siv6@Y*5f-LD2bTCxv+w`3opfutvca1+OUZA+1EdRKax$Zc}i#f@}ppR`8sH zjS8AA2s+<7Lt*kzkbvb1{-R*3f>#y9A+1FIgo5i8Ojj^Z!4DKXqu`GUHYqr0LC~3x zmrsNfC--&F9Z zf)WL56l_uOiUmREz5x{Gzfh2XUn{6ouuH)k3eHDbiJYupl7c%Gd`m%|g5?VSqF}3n zS1kxS_n${$mZBg5WeV0R*sb7A1@TBLkw+-FNx>Ht%vX@F;1>%1s$iRfqZS062hOK3 z1t>_sN(KK?ut&jL3I-yrL>{SNvVs{37ASa3!7mm3O~G~ruUQau7R6JTLKGz6zZLvj z!CnPz3NAoeiF~<&DGD+bd|Sce3RWoiyMi4Gj#&_N77wH_KSDtQexsmD!9E4Y6)Cq_^pC!1urT%p}-)mL>{H!GYV!Z zxK}|`!LJnjQ^8IJtri5G?_Nk@ofqzpi#lw3I-vqMEkxUq!mvXN|F4+3P3FoD}g=C?>Io>~^WDx}+ z1I{}?e&7WcGK3fUtj|kXf(H)Vcz8`h>Y(p2&!mEssN|WHmog9!LVw3EMae%B%s*vb z$l+3(Ln*uiki!Tshme=UuQZ2|lEZo4Kj!c!FNf=F4&ah~=OKriyc~vlIe@N5y&%J6 z$>DtOA9L93<#3zL;RSflM-E@~aya~RTZ^5V!wktG-uuTK4tqJ=ZF6`X-gx9N-^<}o zUJk9A!ve`+p!bhCba**r+Z^y)TE2nEA>Yd($IAgfFJ^1;nB;JQ_m4RY#Ur-lk8KX0 zg!cmE@Cz@8&wDw1N^|(7gST!Ng zr!OrR%Yo@jE5x4R*md^z!;U9}_v8QG;|Y=BOOWiIgsV>^Jd-#6Nmk*^`cQ7?eGnue zuMqx(f8~v*89*?uDdAt!$InSin7Ik%Ub!(Cca-kQd(sk8tB0tu;A z#{&uB>Q_g6pV_CrClMt4)sNYrJ&<4#k%R(-;ctM5se z*~pZK09wB@5PuKTdu++Z;|XK-K$TUGg2z+^&Qu5Es;r^iW5KwMM1`R^TDJ8!Kf*m3 zk69x)=P{Ib5>-FRT-Kw!4G1`e0Q@Judhd55yg=jT{(~Qbr5()Xew-fNHa+3F4e8ad zULL4!!nGZaKRCJuNvin2`rrU`z?4r&p6HU`$Ky{V{NP1oA3+m`-@hkeNF&^r@qDi> zJFA0LJOyi%)EWq%RmgIlMrfeog_KDLs$Dar!{Gf@#G((8f5aux4R}i6vLOjnu4g9l{ ze|B|m918Jfx5fd!x-Q}IwH^4`ifA>8-x{hudO3(~;g zSOgUdZ$#OsNw^wM>yCaGx#&Pmzw2!stLb-jjB7-jBo0~sj5h=7JTR)(17p_#1pXD9 z0+n9)Aco^Qnqj}eRKVD$4bQQ0_dJO*W8?iNh=PW>dUL`vRZH@Q*&)9!l>5FN@;}2+ z4`Iv?J@>shPTC)So^xg9JI52Qd<$}8 zLZDaIcTI?Qf2$;D0$CmIm`NdAm)AVE^fb(3a-I z*yu0Se2N}!LzlF1xN$zLN5F9e;6E|u!Cd9Oa z#PK(Ixh6h*v7HeK5ln*c32#EYgvXo^C_Jm3gYUQ|{Xk zTl$&|D9LbAx1!9%MH_TFpklqILm2s}{qndzMrQs!h;Rb)0vsEhSreWqa`3K5NS*K! z03Qr!@xY)af`r7W`@Mi6dpt1wFHhUJqgH!h?Cp4jsX}A5AYK!+Z9DcUF+c43z0L0L z1gc$fD&@n=>Y>i^ZZj?Ta@&TtGTq*^<^gj8$ThY4v-{Q#`>AAmIo1W*&fR5cn* zE%(d_kv-SG2ihlMIq(b=iEP$HwlpFu z0Fjl6NW~m&1@F^Dkw$^%Fj3^n_Yq7K?`94?O%(t56vs|@K)JYI9q?PYF%w1F9(FfT zBpIhBirjE`CW<+LxE3a6kDpd}``c-S;db1aDF%vx!tlF+!mq!VR(S8pw895Zr4`P3 zKdtcVVxpKDD7?3W)aOMOciUaRkX$-clOQxrM8kCb6MI1~S1xm@#L@@}@6}P}M6CTQDZ4_T5v^I+K zY4=fXJ}%{zm@(p(n>4tTZ??FV=DXsSnJ3|rcS~gjpY=Q>;YJW9-1am&pua4FSS#jP|Cid$xu z!6nTOsmjgo#AB&Jvko4QuQU6JEt_fDf!TO8R18I7-i6&;Fy+$}>vR|mY7<&Y?=dbqZ=V6 z4W`UH5?X0!u}FDR;fi#l=nGii40dag`7(TLK$ySDNpM=Wg2bl$odD!JtcJF6F@N-a3i!5}V;Az(8~zWqlVg zx-kUN!}QX2kv0r67DvNFiKmHMWWM6bN&`m9Ndrdq*Tu+#LS8Fwxk-mh`CM@;&G*GE zGevM?oxBPjinm@KmYA2tpJ(2K8=|=$OQ7*$w4Q=p>B&ONMYdZOTnLT9S75B=B3@=J zl$MJ@T3OI@iDX)0#)(^QZiP!*Kr~;axl25Ev-!%*!{S-Q=BqHp@UUWkfJ+%Sh`+=% zi9gSr6t~h`1R>aYS9!t(J>jg)Vu7K}qGxyBYwcys z-2!PZW3mBhFC)GBWAS*C zLAdA+OpFTizC5cmgFUgvdScN?k)0~`i4U_=D-G=vbwc!$bOq)zxX}joE-bz)%y

6e~@C$)ns1gKKRo%mhM+D6RE*csSlzqptzu=j=G6asVmbPbIFm z82gZjreWF68#}+3gmiT>fb zxNHUtv~n|1JYJXHA)X-DkUaBEc-RJyid$w%;G&jTc=OB}U<$THo|Twa#9v{2C^)vl z(QuK{T?aSz&bkdAj<~z!VS%wWi$&&q$)VgNOOEAc65L?)DCfeR5|n4YB|%k`DNlmR z%yI_l^!y7vz?cZ-W~)RfF|WeqM2M5(D$OUv#R34gt0SkwN1^616iX7;jxu9y7K=>0 zl;N$qBP729bCU#Gi*oZt3CuI|;Rd1`&`7tlk@DdMrU{Mt1u&cYuM)M~Y!iQpIV%1N z(@*58G?$56V#bR^nE&Dy8CogIv5+R_W^EOV%s`~}rshb=sK88SkWSGV@UXcSNW7D= zarT%*@|NCTif0D*Cq?FO@UZ>1!{vl{P5c$cvsE<1Jo&7xVxFO`Vp_C@Rr{Je$}==o zM71&G^2`O2yvSS*7rRkz&M@jq%oNGK!eq+x0`qP0SDMGgEi)_N#`^W|5~+m6^Z0BgTNYO4t4jkqwaD&mk44NuI z73NEld4X97mlpe#W{Es0Go|8|n}0}*a`OV*K=gIiix#-%bqTL914NukL$gJSb)yvr z`!*Iy<3$Sj4Tj>Z4@IM&=8L+v8RP^k@6=I}D>lt=IU#0>zsTGx{z?;ts{`#<5>{^h zDQ=nBDQ<~rh0BTH87>+cE~ZCMQ#e{JQY>q)SZ==QiAQ@y?Z`or92;f0foKC?Q_StN zl6!>-%i|(*pZF`yf5F9~hM`|+eoZ``>qdd8v=~#g!0eK^CFTtqCfm#NrRXw~440zO zaFL?jDgJWvE%6r_Ypj@OK8CabJN&Ky3`Scim0xnMFcG*MEcc7Q(kz7=h@R%;E(3&s z5USFw1?FJGmqNMOEpbcCn{X**JW7mhAxDT9CFUj(BhP#hF5eBF~X8v2;O7m}V%gtW6s4`|Gradql{{oa5!idHu7xP{ODK}H#QZU*ovaM*T zNTFz@$aYGS#Fges$+*Pa%#6LaWtN0hn8h{>OKXAok%X0*--ugjs>Cff``}WTE7Z_F51$Vx(xqNYSW~qFDt2Xn<(cNYSW~qERD7lY|^3&Xo#oR*F=p1^X-r8ZlBdVx(x)NYSW~qERD7(*)UQl4#UO(OiTaB;+auK?SoEd`H0( z3Vy4gTEUAJ1dSLe8ZlBdYNTk?NYQMB0JKCj%?i{=(R>^^$fGd|(iF^A@LdH@DtJ~w zjeX!0Nf?GDXy1!|;d)JW01 zs*mE3Rw92w!SxEJE10L?2MV51@J9uk6dbf5Xv9d-Y^8nEakC`dr50yR=JYNTk?NYM;HT8aEg1ve_VUBNdL{7}Kq z6;vo_P;kV8pb;ZQ6NL=4J2byiphk*DjTB9*J~|I+CGw{fOjK}(f^RB#R6&V?H43&U zc*TOC5hF#TMvCUwD3Jz;rc!|#DH=6WH0L9R~#Nx_{8zNH{f!EyzEQLt6Ps}=-} z7%7^iD4KSMrcA+F1!|;d)JW09BdtUpq2MM3UsNz(LB4`tDEO;_Z3>QB5Hw<>Xw*p2 ztb`0SKs0KkXw*p2yrqu@BCW(3sbI2#844CCcuc`B75q)Xb_K6l5Hw<>XbMpf?GB9^ zDH=6WG-{-1)JV}>fV2|%as^WqWGeW!g2xrCQ1EvJI}{wVAZWx$(WsH4Q6oiD1@UN! zXw*p2sF9+%5NYL6ih`*MzNBEGf+Y${75qcN3kqJhAZWx$(WsH4`7LCi0ivl^phk*D zjTDVRT8T4C!DkfARB*3?sDfW9_@{!M3R*1)n*YPz+dxNkTn^mG`qZhjYuB#5 zt9I==XYVTJq?oW1Rg>Rg!p~%)L?)z@VuEy1Oh`mo2K;}?#J6O^EfX)u#6QZ!Ph{c~ znW&SAYdqmhU`~n&(n&Gl=b(Wc5EDve;&YjhPKpU%LRkj$7c!A86CRmZBNIh3@wQAH zmWdNG(M}21j@sPfMetCpkRFQZ@JP(Wr*a#<1WOM^y8lx>6#LfxsUC`D0>fLe!YA&Q zJrs8f3?EAjx9p*)2@FAr;g&rVXA2B%62mQfC=S2KIr+zXC>|9U{zwl+I4J(B!0;#d zC(=iI>7Pj7_2KO_#y|1C82Bf`g)1p>2`a=jSzJ@ZHI1%^m}3@P5}h1qA-j@?%7e&o z6(?`nN8!FxKCCKIU2RDvi;TM zws`gw1$qGcSCc#A;f0Z2N0}$6NDW~iN5i~hlt;MoPEabCGpB+GtaRA^xdaLLDx%MXVBjHCm;S5BL|lWqs_6y8_3?T5!#$T3n%xfcoNMM>;c%DO~Zmxx%pWlfxQ zfl_TRugZC~CdcAO`HICw2}L=UdYOFKTM6H}nk8^qbiilvAvi7OaIZxNxh*b+rxyG` z;VK2+F>G~4)VG6op36jYH?SwSQR*5|-$q1M(GN+~eVl%RS=3r{M?h>v{Q7RSukhHL5KqS$Q@d;K7=R(T^s6_VhB~Krb-F(T@N9WYi0E&Y$rvW6cDdL(& zSI0+U1Z72YsDAa~cmsqNDy(i$r)|Kl$hs&VL((Bqf!W|s<0urKSqv|wC8};4W zga)faYBpXs68}5FZSwH@tI0j%4k(QXGqr*vW02?_arm2} zyB@-*s%N*W9>lP!kAth+=4;e%#|$f!X{T}Yg~O^F1F3@qZ49dp;_gM;57)6_RRX$d zABgb6N$8U z@L`5vv-XL^yl>$VMjpA&k;5c;+O>X#5oGMIsQOFNOP8DbOMY`val&5+_eY0qdAZ^+ zS%G^Wc}&6!l6y>kH~05?Ou{YlW*(CP=`pF3$7I=ca+rJ+4wL`?@R!s#U=v32u|$G< zT_W)=^TR}f`${6gT_us=o{~s#M@fCXaFo>hfQL47M8e$6@&7YVNqyj6pFZ#i{yo81ZGTDdfq7XTk%fw)ovfJ%#63MOkPBJ7vmPp8bl2LMN8$2>3 zD;L9PrJf`DhFQLG zoGI@|qsKZ^1{||jR0JIN-&+oE%76n$oCh2+_tq1_QhlSRCg8u%?GH3PrSJQZ+k%q#Q~ z-1t0()-5_WrlN?^%PWm89{N+dc!+gzcrV_(hYx0UP!B&bz97qA{<}ZeSCPa9KSh#Z zf=$ZsI{J@i?fgHmqjR^!KcJuAq8B3XUro^g3;KDzq^&n#0d4hvT0d{OHD^Ok$$3zM zo?as<3E`-5PhQRVJ^hEML7YE)6aT_pfY1k$xWkt|@Z>xvwrZNw6VPCW979t7ep2G= z{F+=$*MX#kUVfjlgJ09W>tt|QPx0$K3-U*M7bdCxgcMQwd;AGmC)R%ePqmMhL+e3l zjYJMcbSY{Do3%k{V1KE4&B%&OMAQ09uKR_}6G>Rpdi z;NGYIE7Y?8``^*=^JJM`DfV9k+A{oe-#a9PH{N=&10xQImL7ZQ!iR6Y*nts`grDDf zu>&J=gfHNF;JMijjH`Bz+JDiVfcbkp`~c~QaU?#l&*q7D?WsKRwmpd_-n2*Y#BO`X zWCr}2y@4mNbu%UUU-`uD@VWC2xD({F6P=$eRo<$UOD+{?`uF9TL=zW4yL^`ZR4mkG~Me)5m_HHG0F z%1`?p?r9c2UpU=*oSxl4T?O!%WH}WwXPL|)cm^|D<{XqcNiv7gPIx*{Im5GG!c@zg ze~~%w%ba4Fvs315lQ{)4XM@a1iWivkWlo2z{kt-!TIM_HWZ-O~NdO$8+%pY<>AcAF?`FmZRUho$UvnQ|uU^W5cTv)sj=ZVbJOQ#J&t{$SHp6 zMo#gwLyqD{hS3*rXt)2>?e=8^<*jsVsf^C?RxQP$-LB%;E!jBl?R|0-!&zn(xZqhi5gS~;q{>>du?8Z*AAySE3L{N92sef9B1wlz9DPoxNq;Nj$L@Mch)82s=qan&u zDhFx%SUJvrhEcyD>cioCBx|r?R4HP=l4Vo_`luI-L*56hOGK2@+hnu6Z`)_17zt0o zO5~C3lq{nnXJk7$D2_1(Q*yke)EnOiLf)EgS&bn2VQ;U(I5_)*Hdg!iZOXwWSz|eX zV!K7}Qn;fJ=YPUfl71#AM5gefhl@&;5weUnDZ()z&DQtm7X(T{jQ1q9LO?=NTOuVD zf!XXX0^WOFtxaz;s5K=ZAXN#dIGxz4KWUWEj3Q(RRf?p&yaB&Y>dbVdP_^$~So zbW7DA5aeNt6qseh^g~Ib*&T|?$G(OakgGUDMp;kK37`1;ICEsKtgp`1! zrPq*!Doas7=__gt6mn5Ls+-9YDrQ3z5b10NZ}u-qk}@bFs!+y{00olwfkdUr5Qr)W ziP`ocbT_N@6SK7npGe;tT#((_?4-R+9J5t%D6;4ivhaSUvJJV07#!YCRYJ0W!umdg z^^ho2PG;X0;K~#sNmc8>8J(&uMIndxEX_Se0cA*-{Fn4VX%;XhnJrgqQ1zH7q6nbR z5CFaHpb?=qPq0W)Z;_a{+jp{>O)-cX1Xw3*ZqS3no+4I@E3{iYW3JFyx_( z0f|}BW-3P8hi|emFt$XF_o&({2N?gWZ^r*YIn+$PBWql~8=z*l8yt_{0#4<(ic<1B z$6HT*%D~D82=o%mRk)7zALF_?f{ZdIx78X{ZVOO_+k*zT>5LK5JmbLIK75V!eq|)9 zH3+Udyk{bsV2+eQ#`X6jxbE#&u-y=R_nuU{HgXNtLoIfo-HL!^87MKXdrXaW!w>{n zBs3$a>Q@w0C8E}(b{AQPTCE`q9Ad8;fd;H8h6uEz*qP)^9Vdz?;<#amLluKWZTpa~ zmDCwGq7KCfFw2b9LN}N^cB=AFgfTRB3P6@23Be2AUj?i*8?g5^b%7z1|H^SR1*h(XE#`|h&3O%=*@kU5mq+T^%XiYQ_j8Y6}!-m3~I_R>{l zpdz+?_%2gLs{CX-yq~F^YlumqK$QaYK2699CBw2gd@Uy#O|;;VDHL;x5hau^J8X)B zh7lVMW+yaic#SO=R0`D^RJq6^%EU0N$ivi#5^$UeLB;JP)x*Zdn4%E<`*;#jyCEEP zRLiO&d1o+46^8&-Rn0Jy1-+Ir=4A!Zh!BD@QzOg3Eyj++dz8c?fFp*Qg&~09BM*lp%?+ zYXylIy>Sa0BxY|^E5JOba!r;}21Ar76d~>$?^)u!fK_{*idjq%N*6JZ^Dct7V+@TU z5>*reR)~SW3Q)1D0urz)_oEp1t0TCttn4gfxsI%Hxh_GW&G^i0b_|Qbenln*{8$YX zS%oPNQ3N&6#Gv#%i^+O#=XiB|AWTzp2&r{AyysLED=8Baae70>6K+{!Z2AHf!bTI z3P6-%!Xb4O@r+4gXM`j$LiddVi{TPc<&G>v6sjBqXpZ-Y+R0)JIgApJ09X01V$Sh? zsxnYOE3A((SdZ>PQ^P14*&9*_v0#)C)k?;yBuN#Xyw3`nl8G5tGNORP+oGx)wGxN7 zQt2E5e!LkXMn4=3i`U+73rU!rGggcZ{#sALhzH}!Lllt0$ng%*@Qy_#*Lfw1{*$m^ zC7&EVF$P5>dQ-88B8o%^br(aeio!rWcG;|=7D~u_ju9!k4BWJw8He%lvg5xiI=DVE$BPg1Q79@3=WJ5AF5JB6tC}*@=)emS(DO_ zBWe)3U%Vj|(1z|gL7XGRV`Ly8RpzUB)gdN8Rn8O2v79$>Mzq%8#yCA)qV^6;3vhZ>N=io6bCETe9a$DpCSbxXl#G~f zRlbP=D%T@$V?Ik_{l)BnvJP~m_eXUls%xwij5s_4 zR-2k>GM9nEeB>C+2R!xmao}Gw#@b|HmoqHaT25~%30dE;XmPz zRPqXPmG3HMweuM`P4*Gao9lN`K-D*qxXu0|ag)KblJzlDaZkn@w+>;`N51R{lrn|E z-+W@{Fvd&4c7OloFpe&=)5lqY>$X4$KA=WsCS_t#)e!(xFE$riz@Xq0(`^Bd*<(|B z*I#{YhJ4-w=aLWRvf^#Lc#r7JIL?UWQ$Oe>C00NJe@$SPJ)sK^s2{)j+Ej_M(?aWH z-h*FDV5qx;9VD9s*62k%$(qWELJoZVh<2P;;!dENRZzQaNZK*A4;HVoPx$!hdb0I#R3?{9OR-}$mIgKcm}J;IxOstJ-nm# z;o`r+cHUR&M@CqXfgcB_vG9~^7Bi7;?D)&n`Hn&j&;JsxcEtv zDTlV+FWYXXLwsX#rSSX&LYw$JLyM>sIxLsPWYiP7Lss!ePK$IX8zG@>`%(A>+*)z% zG>~LTB-kC9Bk5vDCWt>m@&kb+n*64=+y4=_6L6h`TTaPU)*et&B#}(UGuNSs;*XI0 zn?Ukqj^rn}od_iN7)X90k-ROD*cp;I@kdBL5J={7B%k0m4oDUlNd6y*A!l^Xlgl_q zOCCWOSbk!CBe1y zAHIhnioy51pL`yDlq#oG#heQAF>l%D0-+w41$0)Zg>8=yiEjcwCNa0Hv?!L+LeoU5VeS7p9*(%b zTux42ABg9MxZbj&R?jIJum+wkoIYaFxBmTzULR`IH{>5NtNc&>J@*9^;a|8>zfbQE z`SqF=ZGrQSotZz>`wL3D_5OUYpBU92_Unz|etkp15#$yOb?biIJrbr6rh*E%jLwN3 znFMeMgTNi3O5MNaWDI_Kqk()xuNi3953K2q)(bO+bbtFu;H|n{3(6VITc~?A&5!m? z(l>-kz-h3ZI6dd_I@}Be9EIuU7-4U{vn#V2MYyw+-oK_g20tCq2$aHHj(X9&#}T-q z!c!Ier7lDQNesYKKiP9e-7!v58WDOvfJ5}qCp}QY+ck0i1 zDj5PcRvm?T75XvHMSWi0(SYAWEIL?{{570(Y}M;qtI%&sc%MAHYX%sU4xY`2lG3_& z#Wv_ad;BP}&g=c8w$AK|E!TAnLK2c*=Xsl%E*NA-^?F38ok7MjqK7hulHO+a8FJ|f z{a5Ha5N1&fs3d|)_V7;y=zGlpN5+cUfIrQ1ML$D55e;G@sPnX;9{O+A_bzW{m=6O1 zYxXfu2+$!6b{|GRL^;n>uU|ms1!S}>_W>Gw^gyGq(OtsT4}D3Q?ziaufA7aXI{MSp z*{u!9P*MGxw+1N;<^>k<2ycZ?nY@SeOfDps2_hsBuD4*9GX zL(QE=P-1&CMN!Ud58?hiA{8^cd61doeosrl@hH@(<33LpFy|q_aKkM9<-eyeGjm6L zN{zE2zO)xI+9d~p!lQ!{5qfsNC(}mZl|U`R<q?2@>G<=QZnJc7u}ppoi{}+u;+g zGejMlG}&{J8hJx#mJPF@hY%Est^1Ar?-wik-?Q|1``?4r2-S3a+0#rr&Ic@ihWIlg zu;(VRX9Rjdzu#Sj|D!q!c60|p@VIO{WBXyH8Gg1%?sdQT=6l_XCf?%-(T?{4%l+8# zp2(>USTa1zDFBVc?#L*sW$~y;-s7on`7Vnv&#|UgfUU5nQ=y3I^22?l1<`LXFbjbVDW^Gwaa@jw9r@l!qEW;W-M3 z6c-=@j;Dd?21L*)BR?AdXpw;bo1Q+sluklGu1U(c)5`)75&oGVTWSrjw%hd9}wXzIV9 zwUsU$5(1n!A|#^{G=tZk3P=_hrdFehGkT*ZuR0Ez@wncI_*s^??&*RKJc9o+u*TYM zKIJd?HSOs{ z2AB06ex3KX(6k$veh)(-|2kN!hUA&&<{Okp;n|6E%G6fyY3 zli&HaBhT@qfHXtr=8BZX(izhI?6ybo z1WZe^Yu00>(m)A1ub`SIKCt_cpmp5$AbxMP|MK0I>TM0fh&0supD$s7g?@+am*tik zCl+~h0!5&7!&iM4y>m_V-B?rjk!ZlYzhPRXMZa>d-;v>WdUNf|ba7(AcI+chp@v)N z+P@it;XBs#ED!6Ql~kv{x8CL|XU9N+3_81jA`C@jVF7Nn&LLNR@d3TB%HB`DJR}?__G>VM-CD^FEfTg@-7$hU)l@OeG$aspMbF6l*VoOZz4A zM0~KL*b#<6eBcj~-Cn0XJtEd`t^EN+_w^$daDXEF)QCRsa{Fw!C?U|!BEtEc-ioCN z=gWoQ+^*u-EwEV#F}*WvfF*(5&(kU0k2Kz`-T`}wURBb`Ts*wbi4=KSK1o+^nG@8w z==;k$cE8p>m)>ChhylGF`%`v(H`b3U-o>8AWlKfOtFPIkV7ZFGzUvI;wb%rhKJ;BV zja_%g{x!ccBN^W&R^YbG8M~4K`ZiF29y@Cs*I$Uhy@0r$5m$E3IG)=ugAshXxH`o3 z@5J>*aeYl(-x1f}it9R=6%8#$+I6U zoPW(i#X8Wk2xtWLt+p?aY-iBgqlK>d*P@lWp+{|t%72C%%_z#H{%-P$7Z7a#ujWQz6YWq0(W5~Sg8UqB!?oCxxBi=MH&>En@Poos{BRU*izuZ~-oGB7)i;3+&KJ`kcXJ7AAC2cozv zL}7ODUMU9?w0Eo&sAUWipCEJX3lyIAszNBrvp=8=9#KLS0S6Z%P}wi&kmJDV-DF?R zYSrdI29j<*#++WAbXm4R)n#;9v)8Hgt0EBOsE{i0D)%Mjwhx!v{~`i6D`C)Y^4_3d z;Ui;t%NUmTjb(YC!g6S)TsbVLg$x{n*zCuBD%lFZ1*FPv4l0K&6cu#7si*`clIg5A z#&1rvDjxv~>1V2xD0Hi=mNc6}iJ)0!wL$b~SV^?(=@C@Q!D@25k8%6T7;g8E<#xY9 zwaRG*iXJ{n+4tXs-2z-?HwTrJE1VWoK;bS|6>dM{wjdhP$yTK!$|&qs30K)ID1il` z_SFcwALdT2QoR_on_M47JMY?XAfsL(NXhYr$$l`9FqXG+mg7SL87J!V0U;%tYgOqD z+=TxOz5`zw%^7UqCcGD*FqG65b7;YSbk(^E?*uU__XimF1^tS!1Sq{MQoDyrHZ^5D$ee>>NcXgK}t?x@l11 zw-Xahu8N1jM>2g-WjYxZux?C|hyp6#c>%$9r9FZcmFI(u=aOtw6&ax8ldbSt){n8| zRC`+l)ruq}yh;~qpfOC}G1DTv%a6%Sp+Yquzk$d}mI)D^g^^&BFN5#RAD`uQkA6? z#`6NA+en5xyd$Ju1nmL=3w~J%;=%EDq1^eBRu$whZvy&Xx?*OsiAji~w!`c*Zf3(@_C+HE*c!lXBH(@%1j9C7S+>GM_R1OR$+tFnu z2RHb=iN7(B(h!8G{2tM!7}h#z8b#O!w8`@bx{2v3izP*))qF$~a$z%}4p|an?*w5(} z{l(!uW*{c(&77uHl1HfJS!$D2xWhU^KUf;h05+yjmcZ^LvzG6MGYuGw4W`$KHhH0#xNT2Nm4T z@t#y$BI(Z3@F6<^yB#6G6AiaRb&=FcE~45-0s0Q7yWc z@qU!X5@-k4xiPG=8(vxr(j?7#6IL_GNLE`_R*zY!iUKObc>&qIm4PeBP`Pbo+!p4$ z(l!A)W;%_a8{-P*j5)3hT1$gZyeHYRvW4kbn(vju_hmvD`?i&EX_yQMDAv2E!w}yn}yNisL+Y zcrU9%GA@GUqh5=#B4D|zsVNdCXQvt$%9R;JVcA>Sz%JsqBx_9s&F1iq-5M@tpT6 z3n{V^j;O#$BzVDPX_+*krPQl3>5L zL?L+6O$5MzNzaUWM>Z)!3YgmPLf9OOvXsIoUO)%}y%}FxKQWpiNK$+ABqoS4Yi$G4 z6oE?msD+b3hpB`j2v^u|fSN-jf@IYW6GX`F?_{CbD?u^%%~yNk(}&ui$(-#9&nJ(= z^BnIb(sB%#2CUS5S2(QTx5{fl2d&W_0psnA$zxey%v<3I`q96~zAq3|smPcF_`*-JKJXy(?O1D1UnH0* zxbEoR+_U)APv+79C!k)-;#c2ZKzEYMfC!`CKY)UE35B>gyn#ecrUA)$j7i}WmPcZ4 zr8f*S7z8nuQPlqGZibznU$cPwu*pT`(#@m`ac-<4fFRBd=XBE@35NpH`0renUci3s z>^E~I!qnK$3`*edFMs6oj#@OrZC+9V+h=h?EWs`=TwnC4+B^VU@A>yi(OG z-If)*jAHx+nfgSl!3g+Vt3jKC5AO&)T)Y(FZG)$Icw6$l zzxC6XQXZ2?#OX`B?YDmVQp!|`B*?gg@P=DIeJSNQ<1Bd6CXqY`ez^{*;cY|2V()f) zA!n==%QXsb+hH(vmqa2?U)pWI_0yM99+61I=}Wuqw|x52`jjL>GVscq^7N(M_FFrB z32t)a5b3>4UXz#MHHlEGG(Uy3k=vqcTM5G3xJM)WcPl8w&1I=%UV!E7H~B}GV&Ky_ z?0Us%pQd%Sx&E@+wRM08ziEi8m{Ut&)2-_n*e2H$r~NLLllDAv^qTfIa(SF?{T@B1 zRN^X`N`8i=3e(*wnn>N1mM8PF9;Z~}PDGx4>qCTxK{jS7y$a57Ze(M?@ zk@uqYIRfGo-^qYeo+c1Py_5f_;=ihO6}rCaoSLU~cDsT;O~c(YQxh}`#&9zsxaiX? z_`RxGhFtCo+EaV8J{OI12@Il?n_!H;BIIZ-IaHd!8T~G zBYlKnY13ZclU)+DYOfEuE|yW&#Su;0oc=(UX0fIY^k_2^Q$yFut6RUZxLo^H;mn;2 zeyz@A&pw2D_#(W9QV5wN9WJl+O@?W;~1i zSUa#cJFic3pmYbX2tg78;_Ij>_Se)LPgt`Y&d%%6rtHb~9Mc@d*?FB>XK{APF{?In zM=Di`^Qi)jeLL&5uNU64GvQ-2=5Nw(Xiw=bH$F}7$#yqV^U{a31A7)B6z$-!PdiYU z9-?D^5aWh|HQJiOdv_*m(>hrZkmdGka}K755${k~(9#Vg>ESl*Zy!$Gd`6p-mL6&k z_|w)LN0e0Bn(K&?N*+c`#JqtjL~pI>jAj^Fc!&1&p}*NRqeYtoc~xo$-b)X|lfST_ z3kZwT!`)gf5S9`0$FcPj$Ot$r?IWzvf=+D?NbJ>zD2{B1f~1NOA@<&Y4{S((yhecI zJT3;FS))zak?lFDJpxYEYA@(6Pp|gKd#=0_TIYMNk~%9xL4^>062GDcuCYsZ%|#pc zy4>fPhj6EM;Bb2QnD!L>h7tKxSWw-q9cW7r*J)iK62uRsKOSTVC^!opd+DwjHQIrf z(nBZp?lm>o9}6wl+lDLj!vhsyclzUg4&BmDBXa4kseY~Ipv&XeIzggOdt|T66VPgg zTu65gxk}Dh83roT#6o32V(zZk3e5qjw`&gcgC^0rpW5T{w23r&xaXQ?0St2X_&v>< z<2{$>ipX(*Cp{!oPl$Sq8^3KXPo36z!R4;iI`@=#@C#wrY6rU0AOD;xuL9zz70no@ zOLp1oi8K!*5WhtJDLi(eme+ydbM1AMVjU==R0SJx$5<^$4^>g4!PZZycDIlEVm|5x zW~cp5x+|%&Xq~Tk^Q})bK}GeS*giEIzM9a5aZqhM=&KPyUvMr*usKu)JjYYvQjgie z0`2)#pWYwb&ff7sNyJ^mZCN=5OOzwpA+g{*P!8Tvp?4xKscA!eeLNx^S}l5$`2@oRub$kHx=^?H+P zbdqmpZ+ucEexvd#2$j1*J1`QLR{}kj<4fB9wx|`g+WzjS=PJ>wi z;K7e(Uy5_rQ+8Q}h2NkY&XV&XKqLSne4sSSQ%>|!%Rog9Ex(r0Nph!iER_grsa;$& z^`CMqB{KQ2_js=Dm#3HbTkoNeriY4WJdu-e8eCz`qx0m>BgEwioNlEZ7>?WA5ZLeW zJX1p|3!Yp*1@|=SooF`_6{xTU zJpx?A`Gbgg`M7~PO;RGeikO%8!LgP%jTzRH@+AOaWV@!PCCF6PBuecsjB5Asxiy;Z zX_ifnlBty8bCic!Hthyd`muFA>@KTI=FhuIDVk{)4p0iSs@s}GC31Cigxrf+9I})#T>8@{7X|E$|Si6pi7+HXFAJJYo z?DFW^l*6vP&os+Hm%EK}Xzm3lJeV=vo}?MrGo*DM%+AACZRoQytp@XRpLRfBQc`Wz z=De343NT&DCOXV<_t8`}1!bWZ=cNx1X)~>|BD!KRp# zm>yyi9%B92gohT9RRQShn9{S|@aEc+9=fV04

F!up6W~7iGkr>WHo@~9_e=5c*l!|fk6L666Yq=R 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%0AHY6g~m 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 VnkwoZkMxSctk{8U{QWOl;9qz3T897t literal 0 HcmV?d00001

0It=Q^FEM9FdKhtMg^2s62`XHs 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