From ed359f8be79400b1ad56065118369adc7705748a Mon Sep 17 00:00:00 2001 From: etil2jz Date: Mon, 13 Jun 2022 16:14:55 +0200 Subject: [PATCH] [ci skip] Initial files --- .editorconfig | 6 + .gitattributes | 7 + .github/workflows/build.yml | 47 + .gitignore | 53 + LICENSE.md | 674 +++++++ README.md | 47 + build-data/dev-imports.txt | 10 + build-data/reobf-mappings-patch.tiny | 22 + build.gradle.kts | 79 + gradle.properties | 13 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 +++ gradlew.bat | 89 + logo.webp | Bin 0 -> 85340 bytes patches/api/0001-Mirai-Configuration.patch | 29 + .../api/0002-Add-GameProfileLookupEvent.patch | 78 + patches/api/0003-Build-System-Changes.patch | 21 + patches/api/0004-Disable-reload-command.patch | 30 + patches/api/0005-Add-last-tick-time-API.patch | 60 + .../api/0006-Optimize-Spigot-event-bus.patch | 47 + .../api/0007-Default-Purpur-permissions.patch | 119 ++ .../api/0008-Skip-event-if-no-listeners.patch | 37 + patches/api/0009-disable-AnnotationTest.patch | 20 + ...e-Team-settings-with-a-single-packet.patch | 37 + .../server/0001-Mirai-Branding-Changes.patch | 241 +++ patches/server/0002-Mirai-Configuration.patch | 1692 +++++++++++++++++ patches/server/0003-Time-Utilities.patch | 42 + ...aps-the-predicate-order-of-collision.patch | 76 + patches/server/0005-reduce-allocs.patch | 624 ++++++ patches/server/0006-lithium-fast-util.patch | 94 + patches/server/0007-lithium-HashedList.patch | 309 +++ .../server/0008-lithium-CompactSineLUT.patch | 143 ++ .../server/0009-lithium-fast-retrieval.patch | 99 + ...dd-config-for-logging-login-location.patch | 45 + patches/server/0011-Global-Eula-file.patch | 42 + ...op-wasting-resources-on-JsonList-get.patch | 74 + .../0013-Configurable-max-bees-in-hive.patch | 35 + patches/server/0014-Remove-TickTask.patch | 97 + patches/server/0015-Make-a-field-final.patch | 21 + ...016-Don-t-create-new-random-instance.patch | 61 + ...-Completely-remove-bootstrapExecutor.patch | 53 + ...edBlockingDeque-in-IAsyncTaskHandler.patch | 99 + .../0019-Better-handling-of-async-tasks.patch | 190 ++ ...Executor-for-MAIN_WORKER_EXECUTOR-in.patch | 100 + ...nc-chunk-writes-in-server.properties.patch | 34 + .../0022-Remove-Spigot-tick-limiter.patch | 81 + .../server/0023-Add-last-tick-time-API.patch | 73 + .../0024-Configurable-flight-checks.patch | 49 + .../server/0025-Don-t-save-Fireworks.patch | 24 + ...-Do-not-drop-items-from-Give-command.patch | 20 + .../0027-Don-t-load-chunks-for-physics.patch | 36 + ...hat-commands-before-player-has-joine.patch | 20 + ...ore-Name-of-an-item-to-ensure-consis.patch | 100 + ...0-Bound-villager-trade-maxUses-by-16.patch | 21 + ...entModificationException-by-copying-.patch | 41 + ...cow-rotation-when-shearing-mooshroom.patch | 26 + ...hould-check-if-entity-can-use-portal.patch | 20 + ...ows-should-not-reset-despawn-counter.patch | 21 + ...ptimise-player-list-ticksSpread-out-.patch | 67 + ...x-vanilla-command-permission-handler.patch | 32 + ...on-black-cats-spawning-in-swamp-huts.patch | 23 + ...0038-Fix-rotating-UP-DOWN-CW-and-CCW.patch | 38 + .../0039-Disable-outdated-build-check.patch | 21 + ...ver-showing-in-ping-before-server-fu.patch | 21 + ...041-Dont-send-useless-entity-packets.patch | 60 + ...-Skip-events-if-there-s-no-listeners.patch | 28 + ...0043-Fix-the-dead-lagging-the-server.patch | 33 + .../0044-Alternative-Keepalive-Handling.patch | 76 + ...-settings-suppressing-pointless-logs.patch | 65 + ...0046-Add-5-second-tps-average-in-tps.patch | 97 + .../server/0047-Fix-stuck-in-portals.patch | 57 + ...tities-can-use-portals-configuration.patch | 46 + .../0049-PaperPR-Optimize-VarInts.patch | 64 + ...Converter-for-itemstack-entity-deser.patch | 32 + ...AI-goal-set-with-optimized-collectio.patch | 29 + ...skip-entity-move-if-movement-is-zero.patch | 43 + ...Optimize-inventory-API-item-handling.patch | 65 + ...ment-updates-if-only-durability-chan.patch | 21 + ...packets-if-player-has-Fire-Resistanc.patch | 25 + ...056-Packet-obfuscation-and-reduction.patch | 198 ++ .../server/0057-lithium-cached-hashcode.patch | 44 + ...-spawning-outside-hard-despawn-range.patch | 22 + ...E-in-CraftPersistentDataTypeRegistry.patch | 19 + .../0060-lithium-ai-task-goat-jump.patch | 147 ++ ...-store-gamerules-in-fastutil-hashmap.patch | 40 + ...2-carpetfixes-BiomeAccess-prediction.patch | 119 ++ ...0063-Skip-enchanting-table-book-tick.patch | 29 + .../0064-Skip-cloning-loot-parameters.patch | 19 + ...0065-lithium-precompute-shape-arrays.patch | 77 + .../server/0066-vmp-ingredient_matching.patch | 87 + patches/server/0067-Fix-MC-121706.patch | 23 + .../server/0068-vmp-entity.iteration.patch | 112 ++ .../server/0069-vmp-spawn_density_cap.patch | 45 + .../0070-Configurable-server-metrics.patch | 34 + .../0071-Configurable-FastMath.round.patch | 374 ++++ ...072-lithium-entity.fast_elytra_check.patch | 21 + patches/server/0073-lithium-profiler.patch | 26 + .../0074-lithium-entity.fast_hand_swing.patch | 21 + .../server/0075-c2me-optimization.math.patch | 267 +++ ...ithium-entity.fast_powder_snow_check.patch | 35 + .../0077-lithium-collections.attributes.patch | 28 + ...8-lithium-collections.entity_by_type.patch | 38 + ...lithium-collections.entity_filtering.patch | 52 + .../0080-lithium-chunk.serialization.patch | 460 +++++ .../0081-lithium-world.inline_height.patch | 67 + ...0082-Configurable-criterion-triggers.patch | 62 + ...083-Set-item-stuck-sleep-to-15-ticks.patch | 21 + .../0084-Smarter-statistics-ticking.patch | 49 + ...y_entity_tracking-and-ai.nearby_enti.patch | 1061 +++++++++++ .../server/0086-some-entity-micro-opts.patch | 82 + .../0087-lithium-cache-iterate-outwards.patch | 175 ++ patches/server/0088-lithium-ai.raid.patch | 79 + ...ont-eat-blocks-in-non-ticking-chunks.patch | 38 + patches/server/0090-Fast-speed-check.patch | 50 + ...91-lithium-block.moving_block_shapes.patch | 164 ++ ...0092-lithium-shapes.blockstate_cache.patch | 112 ++ patches/server/0093-lithium-gen.patch | 243 +++ patches/server/0094-c2me-reduce_allocs.patch | 52 + ...ge-look-changes-from-crashing-the-se.patch | 55 + ...d-more-collision-code-skipping-logic.patch | 22 + ...ap-for-entity-trackers-for-faster-it.patch | 32 + ...0098-lithium-ai.sensor.secondary_poi.patch | 25 + ...099-Use-faster-random-implementation.patch | 372 ++++ ...ick-function-tag-running-before-load.patch | 26 + patches/server/0101-lithium-suffocation.patch | 100 + ...Optimized-default-values-for-configs.patch | 410 ++++ ...e-Team-settings-with-a-single-packet.patch | 151 ++ patches/server/0104-c2me-aquifer.patch | 668 +++++++ ...105-c2me-worldgen-vanilla-bugs-fixes.patch | 134 ++ ...106-Configurable-map-update-interval.patch | 43 + ...ger-saturation-depleting-on-peaceful.patch | 21 + .../0108-Fix-mobs-attacking-themselves.patch | 21 + ...tone-implementation-to-Alternate-Cur.patch | 33 + ...ds-resetting-their-brewTime-when-bei.patch | 27 + .../0111-Optimize-Util.combineSafe.patch | 64 + settings.gradle.kts | 10 + 137 files changed, 14216 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 build-data/dev-imports.txt create mode 100644 build-data/reobf-mappings-patch.tiny create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 logo.webp create mode 100644 patches/api/0001-Mirai-Configuration.patch create mode 100644 patches/api/0002-Add-GameProfileLookupEvent.patch create mode 100644 patches/api/0003-Build-System-Changes.patch create mode 100644 patches/api/0004-Disable-reload-command.patch create mode 100644 patches/api/0005-Add-last-tick-time-API.patch create mode 100644 patches/api/0006-Optimize-Spigot-event-bus.patch create mode 100644 patches/api/0007-Default-Purpur-permissions.patch create mode 100644 patches/api/0008-Skip-event-if-no-listeners.patch create mode 100644 patches/api/0009-disable-AnnotationTest.patch create mode 100644 patches/api/0010-Set-multiple-Team-settings-with-a-single-packet.patch create mode 100644 patches/server/0001-Mirai-Branding-Changes.patch create mode 100644 patches/server/0002-Mirai-Configuration.patch create mode 100644 patches/server/0003-Time-Utilities.patch create mode 100644 patches/server/0004-Swaps-the-predicate-order-of-collision.patch create mode 100644 patches/server/0005-reduce-allocs.patch create mode 100644 patches/server/0006-lithium-fast-util.patch create mode 100644 patches/server/0007-lithium-HashedList.patch create mode 100644 patches/server/0008-lithium-CompactSineLUT.patch create mode 100644 patches/server/0009-lithium-fast-retrieval.patch create mode 100644 patches/server/0010-Add-config-for-logging-login-location.patch create mode 100644 patches/server/0011-Global-Eula-file.patch create mode 100644 patches/server/0012-Stop-wasting-resources-on-JsonList-get.patch create mode 100644 patches/server/0013-Configurable-max-bees-in-hive.patch create mode 100644 patches/server/0014-Remove-TickTask.patch create mode 100644 patches/server/0015-Make-a-field-final.patch create mode 100644 patches/server/0016-Don-t-create-new-random-instance.patch create mode 100644 patches/server/0017-Completely-remove-bootstrapExecutor.patch create mode 100644 patches/server/0018-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch create mode 100644 patches/server/0019-Better-handling-of-async-tasks.patch create mode 100644 patches/server/0020-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch create mode 100644 patches/server/0021-Remove-sync-chunk-writes-in-server.properties.patch create mode 100644 patches/server/0022-Remove-Spigot-tick-limiter.patch create mode 100644 patches/server/0023-Add-last-tick-time-API.patch create mode 100644 patches/server/0024-Configurable-flight-checks.patch create mode 100644 patches/server/0025-Don-t-save-Fireworks.patch create mode 100644 patches/server/0026-Do-not-drop-items-from-Give-command.patch create mode 100644 patches/server/0027-Don-t-load-chunks-for-physics.patch create mode 100644 patches/server/0028-Do-not-process-chat-commands-before-player-has-joine.patch create mode 100644 patches/server/0029-Always-convert-Lore-Name-of-an-item-to-ensure-consis.patch create mode 100644 patches/server/0030-Bound-villager-trade-maxUses-by-16.patch create mode 100644 patches/server/0031-Mitigate-ConcurrentModificationException-by-copying-.patch create mode 100644 patches/server/0032-Fix-cow-rotation-when-shearing-mooshroom.patch create mode 100644 patches/server/0033-End-gateway-should-check-if-entity-can-use-portal.patch create mode 100644 patches/server/0034-Arrows-should-not-reset-despawn-counter.patch create mode 100644 patches/server/0035-Spread-out-and-optimise-player-list-ticksSpread-out-.patch create mode 100644 patches/server/0036-Fix-vanilla-command-permission-handler.patch create mode 100644 patches/server/0037-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch create mode 100644 patches/server/0038-Fix-rotating-UP-DOWN-CW-and-CCW.patch create mode 100644 patches/server/0039-Disable-outdated-build-check.patch create mode 100644 patches/server/0040-Fix-outdated-server-showing-in-ping-before-server-fu.patch create mode 100644 patches/server/0041-Dont-send-useless-entity-packets.patch create mode 100644 patches/server/0042-Skip-events-if-there-s-no-listeners.patch create mode 100644 patches/server/0043-Fix-the-dead-lagging-the-server.patch create mode 100644 patches/server/0044-Alternative-Keepalive-Handling.patch create mode 100644 patches/server/0045-Logger-settings-suppressing-pointless-logs.patch create mode 100644 patches/server/0046-Add-5-second-tps-average-in-tps.patch create mode 100644 patches/server/0047-Fix-stuck-in-portals.patch create mode 100644 patches/server/0048-Entities-can-use-portals-configuration.patch create mode 100644 patches/server/0049-PaperPR-Optimize-VarInts.patch create mode 100644 patches/server/0050-PaperPR-Use-DataConverter-for-itemstack-entity-deser.patch create mode 100644 patches/server/0051-lithium-replace-AI-goal-set-with-optimized-collectio.patch create mode 100644 patches/server/0052-vmp-skip-entity-move-if-movement-is-zero.patch create mode 100644 patches/server/0053-Optimize-inventory-API-item-handling.patch create mode 100644 patches/server/0054-Don-t-send-equipment-updates-if-only-durability-chan.patch create mode 100644 patches/server/0055-Don-t-send-fire-packets-if-player-has-Fire-Resistanc.patch create mode 100644 patches/server/0056-Packet-obfuscation-and-reduction.patch create mode 100644 patches/server/0057-lithium-cached-hashcode.patch create mode 100644 patches/server/0058-PaperPR-Fix-mobs-spawning-outside-hard-despawn-range.patch create mode 100644 patches/server/0059-PaperPR-Fix-CME-in-CraftPersistentDataTypeRegistry.patch create mode 100644 patches/server/0060-lithium-ai-task-goat-jump.patch create mode 100644 patches/server/0061-lithium-store-gamerules-in-fastutil-hashmap.patch create mode 100644 patches/server/0062-carpetfixes-BiomeAccess-prediction.patch create mode 100644 patches/server/0063-Skip-enchanting-table-book-tick.patch create mode 100644 patches/server/0064-Skip-cloning-loot-parameters.patch create mode 100644 patches/server/0065-lithium-precompute-shape-arrays.patch create mode 100644 patches/server/0066-vmp-ingredient_matching.patch create mode 100644 patches/server/0067-Fix-MC-121706.patch create mode 100644 patches/server/0068-vmp-entity.iteration.patch create mode 100644 patches/server/0069-vmp-spawn_density_cap.patch create mode 100644 patches/server/0070-Configurable-server-metrics.patch create mode 100644 patches/server/0071-Configurable-FastMath.round.patch create mode 100644 patches/server/0072-lithium-entity.fast_elytra_check.patch create mode 100644 patches/server/0073-lithium-profiler.patch create mode 100644 patches/server/0074-lithium-entity.fast_hand_swing.patch create mode 100644 patches/server/0075-c2me-optimization.math.patch create mode 100644 patches/server/0076-lithium-entity.fast_powder_snow_check.patch create mode 100644 patches/server/0077-lithium-collections.attributes.patch create mode 100644 patches/server/0078-lithium-collections.entity_by_type.patch create mode 100644 patches/server/0079-lithium-collections.entity_filtering.patch create mode 100644 patches/server/0080-lithium-chunk.serialization.patch create mode 100644 patches/server/0081-lithium-world.inline_height.patch create mode 100644 patches/server/0082-Configurable-criterion-triggers.patch create mode 100644 patches/server/0083-Set-item-stuck-sleep-to-15-ticks.patch create mode 100644 patches/server/0084-Smarter-statistics-ticking.patch create mode 100644 patches/server/0085-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch create mode 100644 patches/server/0086-some-entity-micro-opts.patch create mode 100644 patches/server/0087-lithium-cache-iterate-outwards.patch create mode 100644 patches/server/0088-lithium-ai.raid.patch create mode 100644 patches/server/0089-Dont-eat-blocks-in-non-ticking-chunks.patch create mode 100644 patches/server/0090-Fast-speed-check.patch create mode 100644 patches/server/0091-lithium-block.moving_block_shapes.patch create mode 100644 patches/server/0092-lithium-shapes.blockstate_cache.patch create mode 100644 patches/server/0093-lithium-gen.patch create mode 100644 patches/server/0094-c2me-reduce_allocs.patch create mode 100644 patches/server/0095-PaperPR-Stop-large-look-changes-from-crashing-the-se.patch create mode 100644 patches/server/0096-PaperPR-Add-more-collision-code-skipping-logic.patch create mode 100644 patches/server/0097-vmp-use-linked-map-for-entity-trackers-for-faster-it.patch create mode 100644 patches/server/0098-lithium-ai.sensor.secondary_poi.patch create mode 100644 patches/server/0099-Use-faster-random-implementation.patch create mode 100644 patches/server/0100-Fix-tick-function-tag-running-before-load.patch create mode 100644 patches/server/0101-lithium-suffocation.patch create mode 100644 patches/server/0102-Optimized-default-values-for-configs.patch create mode 100644 patches/server/0103-Set-multiple-Team-settings-with-a-single-packet.patch create mode 100644 patches/server/0104-c2me-aquifer.patch create mode 100644 patches/server/0105-c2me-worldgen-vanilla-bugs-fixes.patch create mode 100644 patches/server/0106-Configurable-map-update-interval.patch create mode 100644 patches/server/0107-Fix-hunger-saturation-depleting-on-peaceful.patch create mode 100644 patches/server/0108-Fix-mobs-attacking-themselves.patch create mode 100644 patches/server/0109-Set-default-redstone-implementation-to-Alternate-Cur.patch create mode 100644 patches/server/0110-Fix-brewing-stands-resetting-their-brewTime-when-bei.patch create mode 100644 patches/server/0111-Optimize-Util.combineSafe.patch create mode 100644 settings.gradle.kts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..210d3ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*.java] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2fb638f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto + +*.sh text eol=lf +gradlew text eol=lf +*.bat text eol=crlf + +*.jar binary diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..62b7046 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: Build +on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Git Repository + uses: actions/checkout@v2.4.0 + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1.0.4 + - name: Set up JDK (GraalVM) + uses: graalvm/setup-graalvm@v1 + with: + version: 'latest' + java-version: '17' + github-token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: 1.19" + - name: Pull Gradle Cache + uses: actions/cache@v3.0.1 + id: gradle-cache + with: + path: ~/.gradle + key: ${{ runner.os }}-maven-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-java-${{ matrix.java }} + - name: Configure Git + run: | + git config --global user.email "no-reply@github.com" + git config --global user.name "Github Actions" + - name: Patch + run: | + ./gradlew -Dorg.gradle.jvmargs="-Dfile.encoding=UTF-8 -Djdk.nio.maxCachedBufferSize=262144 -Dgraal.CompilerConfiguration=community -Dgraal.UsePriorityInlining=true -Dgraal.Vectorization=true -Dgraal.OptDuplication=true -Dgraal.SpeculativeGuardMovement=true -Dgraal.WriteableCodeCache=true --add-modules jdk.incubator.vector" applyPatches --stacktrace --no-daemon + - name: Build + run: | + ./gradlew -Dorg.gradle.jvmargs="-Dfile.encoding=UTF-8 -Djdk.nio.maxCachedBufferSize=262144 -Dgraal.CompilerConfiguration=community -Dgraal.UsePriorityInlining=true -Dgraal.Vectorization=true -Dgraal.OptDuplication=true -Dgraal.SpeculativeGuardMovement=true -Dgraal.WriteableCodeCache=true --add-modules jdk.incubator.vector" createReobfPaperclipJar --stacktrace --no-daemon + - name: Archive Paperclip + uses: actions/upload-artifact@v3.0.0 + with: + name: Mirai-1.19 + path: build/libs/mirai-paperclip-1.19-R0.1-SNAPSHOT-reobf.jar + - name: Release + if: github.ref_name == 'ver/1.19' + uses: marvinpinto/action-automatic-releases@latest + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + automatic_release_tag: '1.19' + prerelease: false + title: 'Mirai 1.19' + files: build/libs/mirai-paperclip-1.19-R0.1-SNAPSHOT-reobf.jar diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57ab0a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +.gradle/ +build/ + +# Eclipse stuff +.classpath +.project +.settings/ + +# VSCode stuff +.vscode/ + +# netbeans +nbproject/ +nbactions.xml + +# we use maven! +build.xml + +# maven +target/ +dependency-reduced-pom.xml + +# vim +.*.sw[a-p] + +# various other potential build files +build/ +bin/ +dist/ +manifest.mf + +# Mac filesystem dust +.DS_Store/ +.DS_Store + +# intellij +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Linux temp files +*~ + +# other stuff +run/ + +mirai-server +mirai-api + + +!gradle/wrapper/gradle-wrapper.jar diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd7d74a --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +Mirai Face +
+

Mirai

+

A powerful Minecraft Server Software coming from the future

+

Mirai is ❗under heavy development❗ and contributions are welcome!

+
This project is experimental, its usage in production environment is discouraged if you are not ready to face possible bugs.
+ + ![Build status](https://img.shields.io/github/workflow/status/etil2jz/Mirai/Build/ver/1.19?style=for-the-badge) + [![Discord](https://img.shields.io/discord/928402257605701683?color=5865F2&label=discord&style=for-the-badge)](https://discord.gg/DdH6Yfu9gM) +
+ +## Features + +- **Fork of [Pufferfish](https://github.com/pufferfish-gg/Pufferfish)** for the best performance. +- **Contains [Lithium](https://github.com/CaffeineMC/lithium-fabric) patches** which respect Vanilla parity. +- **Efficient redstone** relying on [Alternate Current](https://github.com/SpaceWalkerRS/alternate-current), up to 4 times faster than Paper's algorithm. +- **(WIP) Implements [C2ME](https://github.com/RelativityMC/C2ME-fabric)** to improve chunk generation speed, I/O and loading. +- **Reduced bandwidth consumption and CPU usage** from avoiding sending useless packets in certain situations. +- **Pre-tweaked** configuration files to reach optimal performance with minimal impact on normal behaviors. +- **Login location** hiding feature, which adds another security layer in the logs for admins. +- **Toggleable metrics**, no one can collect data, not even us. +- **Bugfixes** for several Minecraft issues. +- **Faster process** for Vanilla methods. +- **Plugin compatibility** with Spigot & Paper plugins. + +## Downloads +The latest JAR file can be downloaded here: +[Mirai-1.19-R0.1-SNAPSHOT.jar](https://github.com/etil2jz/Mirai/releases/download/1.19/mirai-paperclip-1.19-R0.1-SNAPSHOT-reobf.jar) + +**Please note Java >=17 is required.** + +You can also clone this repository and build it yourself. +If you are interested in making a fork, check out this template [there](https://github.com/PaperMC/paperweight-examples)! + +## Building +In order to distribute and use this server software, you need a paperclip file: + +```bash +./gradlew applyPatches && ./gradlew createReobfPaperclipJar +``` + +## License +Patches are licensed under GPL-3.0. +All other files are licensed under MIT. + +## bStats +[![](https://bstats.org/signatures/server-implementation/mirai.svg)](https://bstats.org/plugin/server-implementation/Mirai/14774) diff --git a/build-data/dev-imports.txt b/build-data/dev-imports.txt new file mode 100644 index 0000000..b818b96 --- /dev/null +++ b/build-data/dev-imports.txt @@ -0,0 +1,10 @@ +# You can use this file to import files from minecraft libraries into the project +# format: +# +# both fully qualified and a file based syntax are accepted for : +# authlib com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java +# datafixerupper com.mojang.datafixers.DataFixerBuilder +# datafixerupper com/mojang/datafixers/util/Either.java +# To import classes from the vanilla Minecraft jar use `minecraft` as the artifactId: +# minecraft net.minecraft.world.level.entity.LevelEntityGetterAdapter +# minecraft net/minecraft/world/level/entity/LevelEntityGetter.java diff --git a/build-data/reobf-mappings-patch.tiny b/build-data/reobf-mappings-patch.tiny new file mode 100644 index 0000000..f5cadde --- /dev/null +++ b/build-data/reobf-mappings-patch.tiny @@ -0,0 +1,22 @@ +# We would like for paperweight to generate 100% perfect reobf mappings (and deobf mappings for that matter). +# But unfortunately it's not quite there yet - and it may be some time before that happens. Generating perfect mappings +# from Spigot's mappings is extremely difficult due to Spigot's bad tooling and bad mappings. To add insult to injury +# we remap Spigot's _source code_ which is a lot more complex and error-prone than bytecode remapping. So with all that +# said, this file exists to help fill in the gap. +# +# We will continue to improve paperweight and will work on fixing these issues so they don't come up in the first place, +# but these mappings exist to prevent these issues from holding everything else in Paper up while we work through all +# of these issues. Due to the complex nature of mappings generation and the debugging difficulty involved it may take +# a significant amount of time for us to track down every possible issue, so this file will likely be around and in +# use - at least in some capacity - for a long time. +# +# If you are adding mappings patches which are correcting for issues in paperweight's reobf mappings generation, +# unrelated to any changes in your patches, we ask that you PR the mapping to Paper so more users can benefit rather +# than keep the fix for your own fork. If the mappings patch is there to correct reobf for changes made in your patches, +# then obviously it doesn't make any sense to PR them upstream. + +tiny 2 0 mojang+yarn spigot + +# Workaround by MoritzR200 (see issue#87) +c net/minecraft/server/dedicated/DedicatedServerProperties net/minecraft/server/dedicated/DedicatedServerProperties + m (Ljava/lang/String;)Ljava/lang/String; lambda$new$3 d diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..607b029 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,79 @@ +import io.papermc.paperweight.util.constants.* + +plugins { + java + `maven-publish` + id("com.github.johnrengelman.shadow") version "7.1.2" apply false + id("io.papermc.paperweight.patcher") version "1.3.8-SNAPSHOT" +} + +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") { + content { onlyForConfigurations("paperclip") } + } +} + +dependencies { + remapper("net.fabricmc:tiny-remapper:0.8.4:fat") + decompiler("org.quiltmc:quiltflower:1.8.1") + paperclip("io.papermc:paperclip:3.0.3-SNAPSHOT") +} + +subprojects { + apply(plugin = "java") + apply(plugin = "maven-publish") + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } + } + + tasks.withType { + options.encoding = Charsets.UTF_8.name() + options.release.set(17) + } + tasks.withType { + options.encoding = Charsets.UTF_8.name() + } + tasks.withType { + filteringCharset = Charsets.UTF_8.name() + } + + repositories { + mavenLocal() + mavenCentral() + maven("https://oss.sonatype.org/content/groups/public/") + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://ci.emc.gs/nexus/content/groups/aikar/") + maven("https://repo.aikar.co/content/groups/aikar") + maven("https://repo.md-5.net/content/repositories/releases/") + maven("https://hub.spigotmc.org/nexus/content/groups/public/") + maven("https://jitpack.io") + maven("https://repo.codemc.io/repository/maven-public/") + } +} + +paperweight { + serverProject.set(project(":mirai-server")) + + remapRepo.set("https://maven.fabricmc.net/") + decompileRepo.set("https://maven.quiltmc.org/") + + useStandardUpstream("pufferfish") { + url.set(github("pufferfish-gg", "Pufferfish")) + ref.set(providers.gradleProperty("pufferfishRef")) + + withStandardPatcher { + apiSourceDirPath.set("pufferfish-api") + serverSourceDirPath.set("pufferfish-server") + + apiPatchDir.set(layout.projectDirectory.dir("patches/api")) + serverPatchDir.set(layout.projectDirectory.dir("patches/server")) + + apiOutputDir.set(layout.projectDirectory.dir("mirai-api")) + serverOutputDir.set(layout.projectDirectory.dir("mirai-server")) + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..2522d2e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,13 @@ +group=wtf.etil.mirai +version=1.19-R0.1-SNAPSHOT +mcVersion=1.19 +packageVersion=1_19_R1 +pufferfishRef=671d68b85d4e9367f6847e4475e106135169c0e3 + +org.gradle.caching=true +org.gradle.caching.debug=false +org.gradle.configureondemand=true +org.gradle.parallel=true +org.gradle.vfs.watch=false +org.gradle.daemon=false +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Djdk.nio.maxCachedBufferSize=262144 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..41d9927a4d4fb3f96a785543079b8df6723c946b GIT binary patch literal 59821 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..aa991fc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/logo.webp b/logo.webp new file mode 100644 index 0000000000000000000000000000000000000000..252138e3fe187edb6e5b0263c561c283eeddddbd GIT binary patch literal 85340 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 literal 0 HcmV?d00001 diff --git a/patches/api/0001-Mirai-Configuration.patch b/patches/api/0001-Mirai-Configuration.patch new file mode 100644 index 0000000..1eee08d --- /dev/null +++ b/patches/api/0001-Mirai-Configuration.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 2 Dec 2021 16:06:46 +0100 +Subject: [PATCH] Mirai Configuration + + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 79b26045a68ebb9b01e5bd06abbccaaef5489777..3dd9204c639737ad0c508d2d4a0ac8bc61c74910 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1897,6 +1897,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + } + // Paper end + ++ // Mirai start ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getMiraiConfig() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ @NotNull ++ public java.util.Properties getServerProperties() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Mirai end ++ + /** + * Sends the component to the player + * diff --git a/patches/api/0002-Add-GameProfileLookupEvent.patch b/patches/api/0002-Add-GameProfileLookupEvent.patch new file mode 100644 index 0000000..1b735e8 --- /dev/null +++ b/patches/api/0002-Add-GameProfileLookupEvent.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: tr7zw +Date: Sat, 1 Aug 2020 15:52:50 -0500 +Subject: [PATCH] Add GameProfileLookupEvent + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/build.gradle.kts b/build.gradle.kts +index 9d7bd0f965c7dc3a60246310688aa5f93a4594a4..3a1c5a4aa8d512d50565b22a383e44b73b276ee5 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -51,6 +51,7 @@ dependencies { + compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.2") + compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.2") + compileOnly("com.google.code.findbugs:jsr305:1.3.9") // Paper ++ compileOnly("com.mojang:authlib:3.2.38") // Yatopia + + val annotations = "org.jetbrains:annotations:23.0.0" // Paper - we don't want Java 5 annotations... + compileOnly(annotations) +diff --git a/src/main/java/org/yatopiamc/yatopia/api/events/GameProfileLookupEvent.java b/src/main/java/org/yatopiamc/yatopia/api/events/GameProfileLookupEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d7db9c87dc82c883004a906e024d2f6de3e40961 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/yatopia/api/events/GameProfileLookupEvent.java +@@ -0,0 +1,51 @@ ++package org.yatopiamc.yatopia.api.events; ++ ++import com.mojang.authlib.GameProfile; ++import java.util.UUID; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class GameProfileLookupEvent extends Event { ++ private static final HandlerList handlers = new HandlerList(); ++ private GameProfile gameProfile = null; ++ private final UUID uuid; ++ private final String name; ++ ++ public GameProfileLookupEvent(boolean async, @NotNull UUID uuid, @NotNull String name) { ++ super(async); ++ this.uuid = uuid; ++ this.name = name; ++ } ++ ++ @Nullable ++ public GameProfile getGameProfile() { ++ return gameProfile; ++ } ++ ++ public void setGameProfile(@Nullable GameProfile gameProfile) { ++ this.gameProfile = gameProfile; ++ } ++ ++ @NotNull ++ public UUID getUuid() { ++ return uuid; ++ } ++ ++ @NotNull ++ public String getName() { ++ return name; ++ } ++ ++ @NotNull ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +\ No newline at end of file diff --git a/patches/api/0003-Build-System-Changes.patch b/patches/api/0003-Build-System-Changes.patch new file mode 100644 index 0000000..50d57a2 --- /dev/null +++ b/patches/api/0003-Build-System-Changes.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ben Kerllenevich +Date: Sat, 12 Jun 2021 12:35:38 -0400 +Subject: [PATCH] Build System Changes + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/build.gradle.kts b/build.gradle.kts +index 3a1c5a4aa8d512d50565b22a383e44b73b276ee5..ea01ad75a874944a16f817f0a27e12b251111192 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -103,6 +103,8 @@ tasks.jar { + } + + tasks.withType { ++ (options as StandardJavadocDocletOptions).addStringOption("-add-modules", "jdk.incubator.vector") // Purpur - our javadocs need this for pufferfish's SIMD patch ++ (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") // Purpur - silence Paper's bajillion javadoc warnings + val options = options as StandardJavadocDocletOptions + options.overview = "src/main/javadoc/overview.html" + options.isDocFilesSubDirs = true diff --git a/patches/api/0004-Disable-reload-command.patch b/patches/api/0004-Disable-reload-command.patch new file mode 100644 index 0000000..16b89be --- /dev/null +++ b/patches/api/0004-Disable-reload-command.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ivan Pekov +Date: Mon, 19 Oct 2020 15:48:06 +0300 +Subject: [PATCH] Disable reload command + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java +index 0c7ba0718de2b93d013968ca0fec34ffd423990f..535800197c43f43c30854d2b193107808dc3b6c7 100644 +--- a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java +@@ -21,6 +21,17 @@ public class ReloadCommand extends BukkitCommand { + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { // Paper + if (!testPermission(sender)) return true; ++ // Yatopia start - disable reload command ++ if (Boolean.parseBoolean(System.getProperty("Mirai.DisableReloadCommand", "true"))) { ++ sender.sendMessage(ChatColor.RED + "Operation denied."); ++ sender.sendMessage(ChatColor.YELLOW + "Reload command SHOULD NEVER EVER EVER be used in whatever circumstances."); ++ sender.sendMessage(ChatColor.YELLOW + "Mirai has intentionally disabled it in order to stop you using it, instead of restarting your server."); ++ sender.sendMessage(ChatColor.GREEN + "---------------------------------------------"); ++ sender.sendMessage(ChatColor.YELLOW + "RESTART YOUR SERVER AND NEVER USE /reload"); ++ sender.sendMessage(ChatColor.YELLOW + "For plugin developers: learn what a HOTSWAP AGENT is and stop using /reload"); ++ return true; ++ } ++ // Yatopia end + + // Paper start - Reload permissions.yml & require confirm + boolean confirmed = System.getProperty("LetMeReload") != null; diff --git a/patches/api/0005-Add-last-tick-time-API.patch b/patches/api/0005-Add-last-tick-time-API.patch new file mode 100644 index 0000000..9f065a9 --- /dev/null +++ b/patches/api/0005-Add-last-tick-time-API.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ivan Pekov +Date: Sun, 27 Sep 2020 18:01:50 +0300 +Subject: [PATCH] Add last tick time API + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index d8666481f9a407403d0114ff02024fd3c50c27c4..cfa749cfa9fc8bade80018f0deaa6420c57d8272 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -2336,6 +2336,16 @@ public final class Bukkit { + return server.isStopping(); + } + ++ // Yatopia start ++ @Deprecated ++ public static long getLastTickMs() { ++ return server.getLastTickMs(); ++ } ++ @NotNull public static java.time.Duration getLastTickTime() { ++ return server.getLastTickTime(); ++ } ++ // Yatopia end ++ + /** + * Returns the {@link com.destroystokyo.paper.entity.ai.MobGoals} manager + * +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 3dd9204c639737ad0c508d2d4a0ac8bc61c74910..2e6bd4a4b333eea52339ebd99410ec13300bda67 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -2046,6 +2046,26 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + */ + boolean isStopping(); + ++ // Yatopia start ++ /** ++ * Returns the time the last tick took in milliseconds. ++ * ++ * @return long time value ++ * @deprecated newer method with java's Duration and a possibility to convert it from millis to something else ++ */ ++ @Deprecated ++ default long getLastTickMs() { ++ return getLastTickTime().toMillis(); ++ } ++ /** ++ * Returns the time in {@link java.time.Duration} the last tick took. ++ * ++ * @return duration ++ */ ++ @NotNull ++ java.time.Duration getLastTickTime(); ++ // Yatopia end ++ + /** + * Returns the {@link com.destroystokyo.paper.entity.ai.MobGoals} manager + * diff --git a/patches/api/0006-Optimize-Spigot-event-bus.patch b/patches/api/0006-Optimize-Spigot-event-bus.patch new file mode 100644 index 0000000..af25049 --- /dev/null +++ b/patches/api/0006-Optimize-Spigot-event-bus.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Thu, 9 Dec 2021 01:53:30 +0100 +Subject: [PATCH] Optimize Spigot event bus + +Original code by lynxplay, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/lynxplay/ktp + +diff --git a/src/main/java/org/bukkit/plugin/RegisteredListener.java b/src/main/java/org/bukkit/plugin/RegisteredListener.java +index 419aec56b0e3fa8bcec2ea7f340caa3456b57d00..8530d926931a54ed1300c40cd1e0908b2d9b594d 100644 +--- a/src/main/java/org/bukkit/plugin/RegisteredListener.java ++++ b/src/main/java/org/bukkit/plugin/RegisteredListener.java +@@ -62,8 +62,10 @@ public class RegisteredListener { + * @throws EventException If an event handler throws an exception. + */ + public void callEvent(@NotNull final Event event) throws EventException { +- if (event instanceof Cancellable) { +- if (((Cancellable) event).isCancelled() && isIgnoringCancelled()) { ++ // KTP start - optimize spigot event bus ++ if (isIgnoringCancelled()) { ++ if (event instanceof Cancellable cancellable && cancellable.isCancelled()) { ++ // KTP end - optimize spigot event bus + return; + } + } +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index dba9041784e7d3051b5248cbc24e4879e60103c1..20408d8668aa17bb6ab9ecee94db32a0cae5e95e 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -653,11 +653,15 @@ public final class SimplePluginManager implements PluginManager { + @Override + public void callEvent(@NotNull Event event) { + // Paper - replace callEvent by merging to below method +- if (event.isAsynchronous() && server.isPrimaryThread()) { ++ // KTP start - optimize spigot event bus ++ final boolean isAsync = event.isAsynchronous(); ++ final boolean isPrimary = server.isPrimaryThread(); // Cache to prevent multiple thread object comparisons. ++ if (isAsync && isPrimary) { + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); +- } else if (!event.isAsynchronous() && !server.isPrimaryThread() && !server.isStopping() ) { ++ } else if (!isAsync && !isPrimary && !server.isStopping() ) { + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } ++ // KTP end - optimize spigot event bus + + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); diff --git a/patches/api/0007-Default-Purpur-permissions.patch b/patches/api/0007-Default-Purpur-permissions.patch new file mode 100644 index 0000000..5b5a746 --- /dev/null +++ b/patches/api/0007-Default-Purpur-permissions.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 5 Jun 2020 23:32:38 -0500 +Subject: [PATCH] Default Purpur permissions + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java +index 7763d6101ac61900db1e2310966b99584539fd0e..d5a42707d365ffd72532bbb1a59a1ca7145f9918 100644 +--- a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java ++++ b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java +@@ -18,6 +18,7 @@ public final class CommandPermissions { + DefaultPermissions.registerPermission(PREFIX + "plugins", "Allows the user to view the list of plugins running on this server", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload the server settings", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "version", "Allows the user to view the version of the server", PermissionDefault.TRUE, commands); ++ DefaultPermissions.registerPermission(PREFIX + "purpur", "Allows the user to use the purpur command", PermissionDefault.OP, commands); // Purpur + + commands.recalculatePermissibles(); + return commands; +diff --git a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java +index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..8e481e3815f5645ee92f0d229e5ff25c8fc9a6c2 100644 +--- a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java ++++ b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java +@@ -89,6 +89,8 @@ public final class DefaultPermissions { + CommandPermissions.registerPermissions(parent); + BroadcastPermissions.registerPermissions(parent); + ++ PurpurPermissions.registerPermissions(); // Purpur ++ + parent.recalculatePermissibles(); + } + } +diff --git a/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java +new file mode 100644 +index 0000000000000000000000000000000000000000..efef6b099202717d2b53316028dc6dfb0e5f01ec +--- /dev/null ++++ b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java +@@ -0,0 +1,79 @@ ++package org.bukkit.util.permissions; ++ ++import org.bukkit.entity.Entity; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Mob; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class PurpurPermissions { ++ private static final String ROOT = "purpur"; ++ private static final String PREFIX = ROOT + "."; ++ private static final Set mobs = new HashSet<>(); ++ ++ static { ++ for (EntityType mob : EntityType.values()) { ++ Class clazz = mob.getEntityClass(); ++ if (clazz != null && Mob.class.isAssignableFrom(clazz)) { ++ mobs.add(mob.getName()); ++ } ++ } ++ } ++ ++ @NotNull ++ public static Permission registerPermissions() { ++ Permission purpur = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all Purpur utilities and commands", PermissionDefault.FALSE); ++ ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.six", "Gives the user six rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.five", "Gives the user five rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.four", "Gives the user four rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.three", "Gives the user three rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.two", "Gives the user two rows of enderchest space", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.one", "Gives the user one row of enderchest space", PermissionDefault.FALSE, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "debug.f3n", "Allows the user to use F3+N keybind to swap gamemodes", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "joinfullserver", "Allows the user to join a full server", PermissionDefault.OP, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "drop.spawner", "Allows the user to drop spawner cage when broken with diamond pickaxe with silk touch", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "place.spawner", "Allows the user to place spawner cage in the world", PermissionDefault.FALSE, purpur); ++ ++ DefaultPermissions.registerPermission(PREFIX + "anvil.color", "Allows the user to use color codes on anvils", PermissionDefault.FALSE, purpur); ++ ++ Permission book = DefaultPermissions.registerPermission(PREFIX + "book", "Allows the user to use color codes on books", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "book.color.edit", "Allows the user to use color codes on books when editing", PermissionDefault.FALSE, book); ++ DefaultPermissions.registerPermission(PREFIX + "book.color.sign", "Allows the user to use color codes on books when signing", PermissionDefault.FALSE, book); ++ book.recalculatePermissibles(); ++ ++ Permission sign = DefaultPermissions.registerPermission(PREFIX + "sign", "Allows the user to use all sign abilities", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission(PREFIX + "sign.edit", "Allows the user to click signs to open sign editor", PermissionDefault.FALSE, sign); ++ DefaultPermissions.registerPermission(PREFIX + "sign.color", "Allows the user to use color codes on signs", PermissionDefault.FALSE, sign); ++ DefaultPermissions.registerPermission(PREFIX + "sign.style", "Allows the user to use style codes on signs", PermissionDefault.FALSE, sign); ++ DefaultPermissions.registerPermission(PREFIX + "sign.magic", "Allows the user to use magic/obfuscate code on signs", PermissionDefault.FALSE, sign); ++ sign.recalculatePermissibles(); ++ ++ Permission ride = DefaultPermissions.registerPermission("allow.ride", "Allows the user to ride all mobs", PermissionDefault.FALSE, purpur); ++ for (String mob : mobs) { ++ DefaultPermissions.registerPermission("allow.ride." + mob, "Allows the user to ride " + mob, PermissionDefault.FALSE, ride); ++ } ++ ride.recalculatePermissibles(); ++ ++ Permission special = DefaultPermissions.registerPermission("allow.special", "Allows the user to use all mobs special abilities", PermissionDefault.FALSE, purpur); ++ for (String mob : mobs) { ++ DefaultPermissions.registerPermission("allow.special." + mob, "Allows the user to use " + mob + " special ability", PermissionDefault.FALSE, special); ++ } ++ special.recalculatePermissibles(); ++ ++ Permission powered = DefaultPermissions.registerPermission("allow.powered", "Allows the user to toggle all mobs powered state", PermissionDefault.FALSE, purpur); ++ DefaultPermissions.registerPermission("allow.powered.creeper", "Allows the user to toggle creeper powered state", PermissionDefault.FALSE, powered); ++ powered.recalculatePermissibles(); ++ ++ DefaultPermissions.registerPermission(PREFIX + "portal.instant", "Allows the user to bypass portal wait time", PermissionDefault.FALSE, purpur); ++ ++ purpur.recalculatePermissibles(); ++ return purpur; ++ } ++} +\ No newline at end of file diff --git a/patches/api/0008-Skip-event-if-no-listeners.patch b/patches/api/0008-Skip-event-if-no-listeners.patch new file mode 100644 index 0000000..203341f --- /dev/null +++ b/patches/api/0008-Skip-event-if-no-listeners.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Mon, 13 Dec 2021 11:28:08 -0500 +Subject: [PATCH] Skip event if no listeners + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 20408d8668aa17bb6ab9ecee94db32a0cae5e95e..3a93a1e4d0f0345a601428f2758653dc406be122 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -652,6 +652,13 @@ public final class SimplePluginManager implements PluginManager { + */ + @Override + public void callEvent(@NotNull Event event) { ++ // JettPack start ++ HandlerList handlers = event.getHandlers(); ++ RegisteredListener[] listeners = handlers.getRegisteredListeners(); ++ if (listeners.length == 0) { ++ return; ++ } ++ // JettPack end + // Paper - replace callEvent by merging to below method + // KTP start - optimize spigot event bus + final boolean isAsync = event.isAsynchronous(); +@@ -663,8 +670,8 @@ public final class SimplePluginManager implements PluginManager { + } + // KTP end - optimize spigot event bus + +- HandlerList handlers = event.getHandlers(); +- RegisteredListener[] listeners = handlers.getRegisteredListeners(); ++ //HandlerList handlers = event.getHandlers(); ++ //RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { diff --git a/patches/api/0009-disable-AnnotationTest.patch b/patches/api/0009-disable-AnnotationTest.patch new file mode 100644 index 0000000..d1d9137 --- /dev/null +++ b/patches/api/0009-disable-AnnotationTest.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: foss-mc <69294560+foss-mc@users.noreply.github.com> +Date: Sat, 22 Jan 2022 03:59:27 -0800 +Subject: [PATCH] disable AnnotationTest + +Original code by PatinaMC, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/PatinaMC/Patina + +diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java +index 57cf9fab2501da6f1abd12e14697630ca82b0a6d..cca7e9876c7609acb26a6c9116b191b0e467a0b0 100644 +--- a/src/test/java/org/bukkit/AnnotationTest.java ++++ b/src/test/java/org/bukkit/AnnotationTest.java +@@ -63,6 +63,7 @@ public class AnnotationTest { + + @Test + public void testAll() throws IOException, URISyntaxException { ++ if (true) return; // Patina + URL loc = Bukkit.class.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(loc.toURI()); + diff --git a/patches/api/0010-Set-multiple-Team-settings-with-a-single-packet.patch b/patches/api/0010-Set-multiple-Team-settings-with-a-single-packet.patch new file mode 100644 index 0000000..5865ca4 --- /dev/null +++ b/patches/api/0010-Set-multiple-Team-settings-with-a-single-packet.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Thu, 7 Apr 2022 16:35:36 -0500 +Subject: [PATCH] Set multiple Team settings with a single packet + +Original license: MIT +Original project: https://github.com/Cryptite/Slice + +diff --git a/src/main/java/org/bukkit/scoreboard/Team.java b/src/main/java/org/bukkit/scoreboard/Team.java +index 0f5c49d41c9460da0ba913b4ca8bef375f2c23df..46eee49bb149910c01db377c9cc8a96da9ead797 100644 +--- a/src/main/java/org/bukkit/scoreboard/Team.java ++++ b/src/main/java/org/bukkit/scoreboard/Team.java +@@ -534,6 +534,24 @@ public interface Team { + boolean hasEntity(@NotNull org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException; + // Paper end + ++ // Slice start ++ /** ++ * Fully set all team options, combining all 5 options into one packet send, rather than one packet sent ++ * for every single option change. ++ * @param displayName New display name ++ * @param prefix New prefix ++ * @param suffix New suffix ++ * @param color new color ++ * @param options A Paired list of options ++ * @throws IllegalStateException ++ */ ++ void teamOptions(net.kyori.adventure.text.Component displayName, ++ net.kyori.adventure.text.Component prefix, ++ net.kyori.adventure.text.Component suffix, ++ net.kyori.adventure.text.format.NamedTextColor color, ++ java.util.List> options) throws IllegalStateException; ++ // Slice end ++ + /** + * Represents an option which may be applied to this team. + */ diff --git a/patches/server/0001-Mirai-Branding-Changes.patch b/patches/server/0001-Mirai-Branding-Changes.patch new file mode 100644 index 0000000..7b2883a --- /dev/null +++ b/patches/server/0001-Mirai-Branding-Changes.patch @@ -0,0 +1,241 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sun, 27 Mar 2022 23:23:55 +0200 +Subject: [PATCH] Mirai Branding Changes + + +diff --git a/build.gradle.kts b/build.gradle.kts +index b04e9ab317fbce9c090b61076eb07c40f069dc59..0321eac313212c717cf02889589bbc5f73c7d6ca 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -9,7 +9,7 @@ plugins { + } + + dependencies { +- implementation(project(":pufferfish-api")) // Pufferfish // Paper ++ implementation(project(":mirai-api")) // Mirai // Pufferfish // Paper + // Pufferfish start + implementation("io.papermc.paper:paper-mojangapi:1.18.2-R0.1-SNAPSHOT") { + exclude("io.papermc.paper", "paper-api") +@@ -74,13 +74,13 @@ tasks.jar { + manifest { + val git = Git(rootProject.layout.projectDirectory.path) + val gitHash = git("rev-parse", "--short=7", "HEAD").getText().trim() +- val implementationVersion = System.getenv("BUILD_NUMBER") ?: "\"$gitHash\"" ++ val implementationVersion = System.getenv("BUILD_NUMBER") ?: gitHash // Mirai - removed quotation marks + val date = git("show", "-s", "--format=%ci", gitHash).getText().trim() // Paper + val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper + attributes( + "Main-Class" to "org.bukkit.craftbukkit.Main", + "Implementation-Title" to "CraftBukkit", +- "Implementation-Version" to "git-Pufferfish-$implementationVersion", // Pufferfish ++ "Implementation-Version" to "git-Mirai-$implementationVersion", // Mirai // Pufferfish + "Implementation-Vendor" to date, // Paper + "Specification-Title" to "Bukkit", + "Specification-Version" to project.version, +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +index f2fe6ea3719ff8b2913b7a3a939d7a5b75cb8b28..d7d614f679ac30360eee431044962614b498a72e 100644 +--- a/src/main/java/com/destroystokyo/paper/Metrics.java ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -593,7 +593,7 @@ public class Metrics { + boolean logFailedRequests = config.getBoolean("logFailedRequests", false); + // Only start Metrics, if it's enabled in the config + if (config.getBoolean("enabled", true)) { +- Metrics metrics = new Metrics("Pufferfish", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish ++ Metrics metrics = new Metrics("Mirai", serverUUID, logFailedRequests, Bukkit.getLogger()); // Mirai + + metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { + String minecraftVersion = Bukkit.getVersion(); +@@ -603,7 +603,7 @@ public class Metrics { + + metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); +- metrics.addCustomChart(new Metrics.SimplePie("pufferfish_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); ++ metrics.addCustomChart(new Metrics.SimplePie("mirai_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Mirai + + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index e0b1f0671d16ddddcb6725acd25a1d1d69e42701..c5dc798bb272a94520404e0e14c17500d3d045ae 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -17,7 +17,7 @@ public final class PaperConsole extends SimpleTerminalConsole { + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + builder +- .appName("Paper") ++ .appName("Mirai") // Mirai + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) + .option(LineReader.Option.COMPLETE_IN_WORD, true); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 371b0f95b23d67c9913875a3dd7888e722551f18..7ba86703433322e2b166cc263cedd6e7310d36a4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1695,7 +1695,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! ++ return "Mirai"; // Mirai - Mirai > // Pufferfish - Pufferfish > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + } + + public SystemReport fillSystemReport(SystemReport details) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index ce1ca033b351785fb67304b8933990d7f2f22d29..ccf826e4d9815af19e598a2320c880c9235eb15a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -249,7 +249,7 @@ import javax.annotation.Nullable; // Paper + import javax.annotation.Nonnull; // Paper + + public final class CraftServer implements Server { +- private final String serverName = "Pufferfish"; // Paper // Pufferfish ++ private final String serverName = "Mirai"; // Paper // Pufferfish // Mirai + private final String serverVersion; + private final String bukkitVersion = Versioning.getBukkitVersion(); + private final Logger logger = Logger.getLogger("Minecraft"); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index f71af7ecad4b6b0328da8cf8c4e5a8e163866e19..1e74799bb012ca45769a3fd103f0b24eba0a9a99 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -430,7 +430,7 @@ public final class CraftMagicNumbers implements UnsafeValues { + + @Override + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { +- return new gg.pufferfish.pufferfish.PufferfishVersionFetcher(); // Pufferfish ++ return new wtf.etil.mirai.MiraiVersionFetcher(); // Mirai + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 80553face9c70c2a3d897681e7761df85b22d464..4c7515fa391ee9b631cb9cd3da67b50a9378e7fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +@@ -11,7 +11,7 @@ public final class Versioning { + public static String getBukkitVersion() { + String result = "Unknown-Version"; + +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/gg.pufferfish.pufferfish/pufferfish-api/pom.properties"); // Pufferfish ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/wtf.etil.mirai/mirai-api/pom.properties"); // Mirai + Properties properties = new Properties(); + + if (stream != null) { +diff --git a/src/main/java/wtf/etil/mirai/MiraiVersionFetcher.java b/src/main/java/wtf/etil/mirai/MiraiVersionFetcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2a7ea9e238be3a253d560ebcacc433258ea636d9 +--- /dev/null ++++ b/src/main/java/wtf/etil/mirai/MiraiVersionFetcher.java +@@ -0,0 +1,112 @@ ++package wtf.etil.mirai; ++ ++import com.destroystokyo.paper.VersionHistoryManager; ++import com.destroystokyo.paper.util.VersionFetcher; ++import com.google.gson.Gson; ++import com.google.gson.JsonObject; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import org.bukkit.craftbukkit.CraftServer; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.IOException; ++import java.net.URI; ++import java.net.http.HttpClient; ++import java.net.http.HttpRequest; ++import java.net.http.HttpResponse; ++import java.nio.charset.StandardCharsets; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++public class MiraiVersionFetcher implements VersionFetcher { ++ ++ /* ++ * Huge shoutout to Airplane for providing basically this entire class. ++ * My brain and fingers thank you. ++ * ~ Code ++ */ ++ ++ private static final Logger LOGGER = Logger.getLogger("MiraiVersionFetcher"); ++ private static final HttpClient client = HttpClient.newHttpClient(); ++ ++ private static final String GITHUB_FORMAT = "https://api.github.com/repos/etil2jz/Mirai/compare/ver/1.18.2...%s"; ++ ++ private static final HttpResponse.BodyHandler JSON_OBJECT_BODY_HANDLER = responseInfo -> HttpResponse.BodySubscribers.mapping( ++ HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), ++ string -> new Gson().fromJson(string, JsonObject.class) ++ ); ++ ++ @Override ++ public long getCacheTime() { ++ return TimeUnit.MINUTES.toMillis(30); ++ } ++ ++ @Override ++ public @NotNull Component getVersionMessage(final @NotNull String serverVersion) { ++ final String[] parts = CraftServer.class.getPackage().getImplementationVersion().split("-"); ++ @NotNull Component component; ++ ++ if (parts.length != 3) { ++ component = text("Unknown server version.", RED); ++ } else { ++ component = this.fetchGithubVersion(parts[2]); ++ } ++ ++ final @Nullable Component history = this.getHistory(); ++ return history != null ? Component.join(JoinConfiguration.noSeparators(), component, Component.newline(), this.getHistory()) : component; ++ } ++ ++ // Based off code contributed by Techcable in Paper/GH-65 ++ private @NotNull Component fetchGithubVersion(final @NotNull String hash) { ++ final URI uri = URI.create(String.format(GITHUB_FORMAT, hash)); ++ final HttpRequest request = HttpRequest.newBuilder(uri).build(); ++ try { ++ final HttpResponse response = client.send(request, JSON_OBJECT_BODY_HANDLER); ++ if (response.statusCode() != 200) { ++ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED); ++ } ++ ++ final JsonObject obj = response.body(); ++ final int versionDiff = obj.get("behind_by").getAsInt(); ++ ++ return this.getResponseMessage(versionDiff); ++ } catch (IOException | InterruptedException e) { ++ LOGGER.log(Level.WARNING, "Failed to look up version from GitHub", e); ++ return text("Failed to retrieve version from server.", RED); ++ } ++ } ++ ++ private @NotNull Component getResponseMessage(final int versionDiff) { ++ return switch (Math.max(-1, Math.min(1, versionDiff))) { ++ case -1 -> ++ text("You are running an unsupported version of Mirai.", RED); ++ case 0 -> ++ text("You are on the latest version!", GREEN); ++ default -> ++ text("You are running " + versionDiff + " version" + (versionDiff == 1 ? "" : "s") + " beyond. " + ++ "Please update your server when possible to maintain stability, security, and receive the latest optimizations.", RED); ++ }; ++ } ++ ++ private @Nullable Component getHistory() { ++ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); ++ if (data == null) { ++ return null; ++ } ++ ++ final String oldVersion = data.getOldVersion(); ++ if (oldVersion == null) { ++ return null; ++ } ++ ++ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); ++ } ++} +\ No newline at end of file diff --git a/patches/server/0002-Mirai-Configuration.patch b/patches/server/0002-Mirai-Configuration.patch new file mode 100644 index 0000000..6f33a35 --- /dev/null +++ b/patches/server/0002-Mirai-Configuration.patch @@ -0,0 +1,1692 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 2 Dec 2021 16:04:04 +0100 +Subject: [PATCH] Mirai Configuration + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 27e90ca03275a42271bbe97ee1a63a355b098ede..201319ddb8a0f1817ec5328aa5757212ea6a4b49 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -237,6 +237,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + // Paper end + gg.pufferfish.pufferfish.PufferfishConfig.load(); // Pufferfish + gg.pufferfish.pufferfish.PufferfishCommand.init(); // Pufferfish ++ ++ // Mirai start ++ try { ++ wtf.etil.mirai.MiraiConfig.init((java.io.File) options.valueOf("mirai-settings")); ++ } catch (Exception e) { ++ DedicatedServer.LOGGER.error("Unable to load server configuration", e); ++ return false; ++ } ++ wtf.etil.mirai.MiraiConfig.registerCommands(); ++ // Mirai end + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index fd4d672eeb8bcedae0965d883fde52cfb8d1e9b4..a8e4cff4567d55db8da902d0a387e55e4a1192ac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -956,6 +956,7 @@ public final class CraftServer implements Server { + + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot + com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper ++ wtf.etil.mirai.MiraiConfig.init((File) console.options.valueOf("mirai-settings")); // Mirai + for (ServerLevel world : this.console.getAllLevels()) { + // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty + world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index fbe68bd4739d9a0e7d9bc4c3d5ba8ecfd2d13954..298c20e4abc04a8bdc93719c282d0b4ef4560819 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -153,6 +153,14 @@ public class Main { + .describedAs("Jar file"); + // Paper end + ++ // Mirai start ++ acceptsAll(asList("mirai", "mirai-settings"), "File for mirai settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File("mirai.yml")) ++ .describedAs("Yml file"); ++ // Mirai end ++ + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5b30b329959811f99f13f32166574f90e765bcb1 +--- /dev/null ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -0,0 +1,189 @@ ++package wtf.etil.mirai; ++ ++import com.destroystokyo.paper.PaperCommand; ++import com.google.common.base.Throwables; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.command.Command; ++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.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++import java.util.regex.Pattern; ++ ++public class MiraiConfig { ++ ++ private static File CONFIG_FILE; ++ private static final String HEADER = "This is the main configuration file for options provided by Mirai."; ++ ++ /*========================================================================*/ ++ public static YamlConfiguration config; ++ static int version; ++ static Map commands; ++ private static boolean verbose; ++ private static boolean fatalError; ++ /*========================================================================*/ ++ ++ public static void init(File configFile) { ++ CONFIG_FILE = configFile; ++ config = new YamlConfiguration(); ++ try { ++ config.load(CONFIG_FILE); ++ } catch (IOException ignored) { ++ } catch (InvalidConfigurationException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not load mirai.yml, please correct your syntax errors.", ex); ++ throw Throwables.propagate(ex); ++ } ++ config.options().header(HEADER); ++ config.options().copyDefaults(true); ++ verbose = getBoolean("verbose", false); ++ ++ commands = new HashMap<>(); ++ commands.put("mirai", new PaperCommand("mirai")); ++ ++ version = getInt("config-version", 1); ++ set("config-version", 1); ++ readConfig(MiraiConfig.class, null); ++ } ++ ++ protected static void logError(String s) { ++ Bukkit.getLogger().severe(s); ++ } ++ ++ protected static void fatal(String s) { ++ fatalError = true; ++ throw new RuntimeException("Fatal mirai.yml config error: " + s); ++ } ++ ++ protected static void log(String s) { ++ if (verbose) { ++ Bukkit.getLogger().info(s); ++ } ++ } ++ ++ public static void registerCommands() { ++ for (Map.Entry entry : commands.entrySet()) { ++ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); ++ } ++ } ++ ++ static void readConfig(Class clazz, Object instance) { ++ for (Method method : clazz.getDeclaredMethods()) { ++ if (Modifier.isPrivate(method.getModifiers())) { ++ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { ++ try { ++ method.setAccessible(true); ++ method.invoke(instance); ++ } catch (InvocationTargetException ex) { ++ throw Throwables.propagate(ex.getCause()); ++ } catch (Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); ++ } ++ } ++ } ++ } ++ ++ try { ++ config.save(CONFIG_FILE); ++ } catch (IOException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); ++ } ++ } ++ ++ private static final Pattern SPACE = Pattern.compile(" "); ++ private static final Pattern NOT_NUMERIC = Pattern.compile("[^-\\d.]"); ++ ++ public static int getSeconds(String str) { ++ str = SPACE.matcher(str).replaceAll(""); ++ final char unit = str.charAt(str.length() - 1); ++ str = NOT_NUMERIC.matcher(str).replaceAll(""); ++ double num; ++ try { ++ num = Double.parseDouble(str); ++ } catch (Exception e) { ++ num = 0D; ++ } ++ switch (unit) { ++ case 'd': ++ num *= (double) 60 * 60 * 24; ++ break; ++ case 'h': ++ num *= (double) 60 * 60; ++ break; ++ case 'm': ++ num *= (double) 60; ++ break; ++ default: ++ case 's': ++ break; ++ } ++ return (int) num; ++ } ++ ++ protected static String timeSummary(int seconds) { ++ String time = ""; ++ ++ if (seconds > 60 * 60 * 24) { ++ time += TimeUnit.SECONDS.toDays(seconds) + "d"; ++ seconds %= 60 * 60 * 24; ++ } ++ ++ if (seconds > 60 * 60) { ++ time += TimeUnit.SECONDS.toHours(seconds) + "h"; ++ seconds %= 60 * 60; ++ } ++ ++ if (seconds > 0) { ++ time += TimeUnit.SECONDS.toMinutes(seconds) + "m"; ++ } ++ return time; ++ } ++ ++ private static void set(String path, Object val) { ++ config.set(path, val); ++ } ++ ++ private static boolean getBoolean(String path, boolean def) { ++ config.addDefault(path, def); ++ return config.getBoolean(path, config.getBoolean(path)); ++ } ++ ++ private static double getDouble(String path, double def) { ++ config.addDefault(path, def); ++ return config.getDouble(path, config.getDouble(path)); ++ } ++ ++ private static float getFloat(String path, float def) { ++ // TODO: Figure out why getFloat() always returns the default value. ++ return (float) getDouble(path, (double) def); ++ } ++ ++ private static int getInt(String path, int def) { ++ config.addDefault(path, def); ++ return config.getInt(path, config.getInt(path)); ++ } ++ ++ private static List getList(String path, T def) { ++ config.addDefault(path, def); ++ return (List) config.getList(path, config.getList(path)); ++ } ++ ++ private static String getString(String path, String def) { ++ config.addDefault(path, def); ++ return config.getString(path, config.getString(path)); ++ } ++ ++ // Mirai - settings below ++ ++ ++ ++} +\ No newline at end of file +diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png +index a7d785f60c884ee4ee487cc364402d66c3dc2ecc..731191b7e805f07e465fae7fac49985456f8d8f6 100644 +GIT binary patch +literal 60066 +zcmV(|K+(U6P)P!gTuDShRCwC#oN2Tq#g*TG5xLa*mVUj_3p7o$FOpb}1T7&fiSaOKv<%0dL6&CZ +z@%TuV=hz>t(K$0-X0wlveI)yX<*{XKX~tqe*u#K?goJ=bAod0W0=n6uK}|z1@Acbi +z$t5COd9$*zs*CQ!$6ko;TalF+8JU%F|8ejC-pHq%bG%pHbWhz^IzP8>b-R6Vb!T~D_2SaOHE-?RJ*jFnU-_QzOzLaAFpWEel^|Zd +z{^WFV{$LtMX_{KIxbdWo`~KtC|LguOo7g`A`+(Ph*MR-NEHDQw9{#>XTwg5jm3LKq +z-YbLhE|kkK{`@x0!IuAg{r1zIc;NQ;EzRybV}9@M85_sbNt({AoLD&>1|iN_6i7Et +zfwhiiy}^8I0lz*0&f&BpE7q8bNs^wVBvA+7_g6mm!ZVw1yX7Xx%k!#>BM7RX6ejHw_#P|Qu48e2o^HGA!oa81?YfOUa9s1*-*WXvgEUQW`S@v2 +zD9YlNUahljhkVO{(-SrF13f=kbJE6L=f3Z>9cQlMUf@1pClFH>e_$=&D +znkZwb`c-kU#ErtD`slOH$>sMQ*nt^B=jteEkCbH*>z)tP+)= +zan_O~2~j8N)rPp+5jQHV)WIpVHPXh!U8$A!FwSAEJbBO=Ee|nCjKQJ3024t%K_+f{D#wx9?vql$u#${mHeCD&i{*jZv2mAooL0SA0iQJzE08xN9 +z1dIysYcI|6I}hEv{bSws@^loZtXGh|bs^aZGhPyw<1MN@q +z?_ZvvVpHaqTXefIu>ntKO6?0kY)_^ubt-(#)Hp_tfD@%k +zs3?>}DT}yb)+_i+y)iMr_3wP}XJ^jvUEn7`L|y_QJ>E(E@eGhd`Ndk`H-7ra+h2O+ +z#b+;6DB%Qt5TwiPNMCZ!TAf9WVgk0le*@DyZ>Rg{@AI<%N$!7kmcMwuLZwm_2I2V? +zaiy$Dk>{rrlVP3E^NEuPn;NX^iM6={Z&R!>1hqQ4UM1>AVmScmBW|~FY0Sjr6!obY +z{Av|v(w!k00B!;Hp9nnVcm#;*y8#J#Sr2^WZ|{2k +z^ZVX@b93H67jA{8aHC!zsb^E{Md);gKFFV_yG#5@N(m2Vj +zMNT66N(goBQLflY +zv17nnl-9;s+jqfW?YfPxUUTJHHvr!P_8iX^<#+-}p1&w9#woz>-Ei9z|73CD;2MMk +z`>8YeKr6H;m9HG_*mChjTZn%792<9ji$`8v&hl2Yk0 +zhs(*00%HIH-q{_qXl0$TKEUMEihZB|^hFs+z74!VUcBQepK`na#0SzLSL&aA@Ajwu +z(fr)(I)}@YD$PKV2?JL`(c6Jh0qrE=s@e{wTMzTipGRDmuIG})y(}(w=*UE`fD9ny +zGkn~on$7yv3H&}N$Uz~|s;p@3iMzGjS)!YCg~a##dFqo>0?h8xA~s3Lvh0;G6yVs@ +zO3ou!X1N&T|7AP361oR~MZ!i?uDNgbYi#}SCsdhuDdUU)H=Z~YjX +z&p1c=PLjCDzL&WeS6Y#LU%6e@Gf +zA3HoxVN!=NHqUY!Bl(TV#x!B2f%IxEwTBVM+ZK5N|aw)pc5lq1|F`W@_~vSL@CbZ*1(W!cMyim_kf`lR3~Rh<7M{0 +zu@k*wJxkVd*;!|E`4v|&J-w2sZ^5J##-sRf>ad|MmtQMyOW9t+##$L`jPR71m1}li +zd-Vn10ImZ%1rI!y!^6iiK!#_iYwmbt&mX_~(sS>JJOM`GGmkmn3J*u50%qJiA6oir +z{@<SX573#h3j!i{LOjE{RA_tWoa+<$IVY +zA?+lDVL%+m*x1r-A7uXRS2*e14`6!x?fe3N?>By%bsJ6*mrf0lcQs}u3dSzKi2Q#J +z2sdc!23#dSCATO!P~fyP&iTbvm#+Ua;MQZ^njDJ&@rGukvv=&_>-TND`_~l;<>}N} +zmucx3D=jp^?^}))Zh;FA{x!GljQDnIBkwnhymerS$Q4UKEY=pB0~%qSYEK;EMiojX +zwX?3z7Yy?~>*G~wR3=uS{oM76mzN|;Rh^j3!KFa^I=f%#i~O4X{tk^QX|O3aicr-m +zS}7EHqqx;&Y4)I4pXs$LfB5bH`_R9N +zqE1t5Z2%;GUkuwAKoF~t5HR6dY+byOn|HMNQTrrLju+T9+ZjZV(w>-}fD^wx)FHKM +zBmeFcPG_CF=S`#lW)1Fycs-?1AF&y&9D6?r=E5e +zaoinD8Uprll3wmA2t9`*@T0e1jEAw-t5&MZzxSIT{&V2#$D%bkRsb^0H7UxEILKWp$yX#jaSnnY +z#CM8nwOOjIr*Q%fVeh><*Qh9Jp(<4fLX^vuF70&jGZ)JDQO@QGK$SaWefx6=F?PdB +zD&TaTC3K~p1IsxHA4dllXwS{lSi73AS;rXz#i?51Qp>^D-eT(H4OkZuw-=b0Su2+2 +z?KgJuTRnH{hJUOqO3+jYT3TOYXmyjo^W9yyk{ +z$*};C07&`|^$)JQ>F0lLlDMM1Aj$F=BRu0UmP5H*zm^I3yYWxi_4-r%+3Tlrx>;n` +zfi_N-et>mOLb^^sQA5WOVRa(+_m#tXdH^BuqK&x?fWE4%e40HrM_+*VWTd&5sezSW%A)^;)NF7`DG?H +zt|MC5Pf%<2BW|$Jvm~GY+?SY|o{@ZsQfdGKH9*Or>T{71vr=Af=$JYbVA4(x_=z6` +z(U-3M=)VE}6JQ5pdlZ+W86d-Xjm?k0%5~cx+;(N==W3;FuXyG7s4)dAJ5l1%%u(Tr +z_)%&He!y3sKc6+G&F%w>#HqnKEqtRBr&2d3CNaxPLaZN9lL)`CeTTx+5;w~UKbZ`4YCwWVhM`Z+UEId6P2@Z!15BsO@}50Z*RCaKHUu15 +z&wBV7$f-`+xA*MDZ)Q1{khHx-(3p@UX1le-sz#H~^ehR^VQgCDNk)nIz|Q4b?~deR +zWle^SV##CVQ)^|iG0P5o@FQ2=b;)Ub8F=kz&zoqD&f*QdlknT$z4fujGH`gFm*x?j +zl4~f(VZlKk90wE4`SXvlIl7tuwd+hKZN%)tGO@8JQEW|=SW)1KsTJ5x8!xCprHWBG +zZzyMeB_K-4EKQ1EZ^-ESLdYG;L<4ZBP~mx6l6yHcTsceQ^7gnxm`Cm=*|>ur*7BoT +zOQyc{=37)(uO?_VvEo+co{Dt{h;!vGS$=KJDokfdEE%3JMBk(-6O+@txa%bzy6>lX +z=HCl&ZU9_4U5!$75hXJ2V!5I&r97tOQ^rgtH{VZtWBDuDm;zq_O#c**qp%!}0Pz9S +zAA)cEx9hikcX@enrSAK)7NSnydGSyQY+imBgm1VVEb~Qa%R^o)9Tl|{F50nt^BsCJcwJyQMU2%gTJLcmH +zIP~D%4>giYP{W!v08YM8F%i(>`~57qqWjif!WApet75ix%0=j$XYMIe=R9xKc&Sy6e#X+V{rKIPL|jkKO4)X +z-kAQy^><&NjWKyKN8>BtM;kzj2*;*Jo=@)Sf#a)6DVs?_#1#by$fell6y@mVNxn|v +zm=0sEzI;88?G5;!uQyol+U#A5(9&Mhj`KBClg$ZK#zm5tU}~Dw8d2inpG@Q-1a%Ls +z94ZWij8v*2xRkWjmKUC|psQIM2I1H|TuccLsa*+@Ljy!D +zc;u&-=qk6wlH}qP;(~d#CTZNs`Vp|3s4MqnjOBe7Udn^p@8JhOxRG|djqitrB^liN +zpd1Pmm5Wm4c&I`clDy5xv1}|eH(4ufvoU_`74GgY4N8wTkQ^->WO#w$$8Y%IQ#UTm +z&#uj05w#`_##TU#4VW6zE+_eb)Hs|;_`+w-qV@6v{Qe*PB@^eI&E7rx#A1M^n`FEr +zW+JT1a*w6OS*oknVyyI-53J2<@G43Ou5!6T+8jKR7+l^vOE|p}4~1X#!Oy{9LE2bj +zNA3?-Rxq4}*LeamQ|yZ5T*S3%zy3CrO&dUKiH4PAx;Ss7`+^_z7?TFmGpl;b +zRo?%3;Ae#w@h*7D31LB$NDhwQx$b-S{baER4!;i$cLZ>Zrw<$|pRr{z;WbyC$22W& +z`p#crdTWxBrA0ci6IUrtEZrob;a8}KRXU3Y2`8q8!mvs-T>0Qt5pXyGhsxVH1c>c@ +z2k;wHcuJ#ujq_E$;(U;Z%dDA0XX0lyy*igd2UTfp1k^g?KQ7O~MEmFPS52b=ADlfz +zGX)$a?hfGwd4E}KU?~Cs +zS?$u?YH2OsO}C4&27h`ImnIAhW!Yy*iuIu9yj{PI6QFsyko}$tCXNOx0>WxdB5_k; +zmFM^D=FtbY;d=qNL&$gTCJiz;4A*k3*c>0U;T;M!mSD`rxjc&Vzm#wn3CMVdw!mntw+@TvvpeeMNsOiRX>B65bz;}w*dyf#|I&J|{_?Y#bd%5MN>$iPT78n*w(s5&XH+Pv#3=iKnH<GnMm= +zD2`fTgB_PQrg>Jk5+pL7N{jkFF`~gnyxuUnlg$Nw)4QFzrZynNTZlmC&m~@Vp93T3V|15 +zZKm)YNv2_(Pl5tDNi~UXwN6W_+e7+htY0T3DW-GuF=nH22=M +z?Ng7x;%)<`-Z{c``~jo@$Lf2ZT=>c358v}~$#2*+VDwGT5)Q+Y;$bYfr28MwK}bIi7brzH|h{8Z{;+1Q>DLmG;iqd>M+TX)i9Ya%K(NAK%XI-LDY{u^*s( +zw0X-fsjTAsgoQWo|lJ?pXKg*VybRq>LTrA|VcT5+KoK?B>L +zmAjessMS$Uz#-QeR&zaYm8>Xm&}h(}KY$-LP~@ZO2OfEG|44N=9po&qi8UqlZQko5PDQsMGEuj}GOq#Dg47QMNF#*a$5uUw!h<6P>1>))<1Vo7#9v;DHW +zw(lPY$#Db_um8hV{OqZP+g^F`>5DwiPm3_F8u{Zh!x5l?jHnh)mjLN=kM%Rv-k?rm +z+RL<_{Sl8OXEB|0XvKzZ*GOWl=2i2-R>=Azqg`PYN@XmQZ#hQu*$ltdAWb59K`D^P +zPm0!gXcwC+U4B-X7?YYIK2X#;2Z!xPn4C#LYdJ2Kt<%nyun74lw-^ElujoUHJgdRe +zDOsBu*Om6f&GM@aj7do2m}aBNOE0|4o4a?(s^-#)lnkh> +z^P)!Bk$RUFL=B|ubATkVfa$(2dB+PtikD<29)2#m^|@yr`;g}a +zi77JUYIudnkykB@hf8w(iBrdx^>z6}2=1+y*z?OBJZ+oQk~Xb4;~kDbdsMs%#v1Xn +zlcbx!qoEkMNM%tYiw031It=N|?x)gRfy)z#;$Ds1w=2)LJz-!qvUY`d#mrtLSLAI& +zt%Iy13BiW-W}f-Z39_&T +zlUjLA_qk`E!rJr@w{yTbMtowC@!QH9j&qXn^9Fqma~g;3TawJ{=&i806=?Q#$~)Ep +zQmn?UKJgkiKlS(nS7^^qv&?C^?w}acafi0g*hR{sMz*YlYN$DRO&#kL>08h9!t4|a +zNrFwSBm|w!tVa!7&hWCd!YIG*$5YP4|DU>R>$T*#%3oD=_r9F7&zUnfkHJ>B%8(Z$ +zMY2Q!1Q7{E2nmTNc;GSI{(uFR5RVXeKu82cL=ZN4fbfvK@sKeJatx9gECMz$1WOi) +z9mX?f&c61osup{H)pcrGojp3p2{YcKHC5eRr+e@2wQ8+zU8<#*XAnSgR=}1ee6!OY +zhr|d<(Q}mqiH%BSs!9h&)p||-Jv?rl6gWN_bG4Dp?Kkkx)eRim5{(DzJ4U*IQHG|4X5yfufQaNU +zneKUA$jH!8o%ZmmfX+r}mUmI?w1-OzvngXDjz}g&4}2R43Mf?oTT*5W*-|i%f?Ky) +z&S8piU_R%<5~k2WIO1s&UolG{{!6zanQ!Ph{nYv2HdWmfM|Hw0G-;wX?9S(r7ZX@p +z1KskcP2=(Q*38=Whl-q(vIa=p4lRb1xPtuw9vO^}W-d1!6CK+xf9dAO-)`_3q>p+( +z3P=p{{LUBt=H@SGW2iZj>o76EE)t(oA!SyWCz +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 +zs7G%;Fo5 q_~6PLXE?MVeB$9Qr+yJmc-2<)NdC?3tb7mGvR@w4zmdgY-KnC6fNV +zouer4r`gcQgCSX~!PrF7EGZmuZ-`X2wGtO2fGy9b*`aX=a$rmCTQ=wka)_qN7&%X5 +z#33?GDpZn`TD69^;8P;00)?3O!MnfswQlPo*Zlvt=rf9pDZ2kJeCz6`fhv&y;0O}R +z9-p{)=B+zZlh~NZB$46&d5IC#YvdO*xvh)8PJE^OlIjw+mBBT##Kurd5^*%#H+B2$R9wO4)vG-J#wqG) +z%>B0mB>9G43rNeY;F+kj~B>MS)mwP +zEQY+668E~;VNcqSJMKAgfbK9u++Xl~K0$t?Naqi*AgmSsJ`t3%)2Nq`M-k(Cim;L{ +zlE5c7%J9{xl0?3x7UPt4iNr>bhHNwdp&Zt$rnO+G%_Z2XQK?8O(j=D9Wzsk;*9mEo +z>LSNeDzdJi+p>==v9$fixFkP#+c+`Rv}!M&efuup6AJS`c)(Ny@XgEo{KjwxV-jm^ +zVZs*g>5i^GK1$A&2KZx4{8R@V1W##}_aurK6qcj&RX+55L>QAf6(WaLwlKBY=~{bAsQ?y +zX-ZFO9X4UA&R1W5gJ}1XoFCh5DrOd++9wgu$Q+BeGK$7a?wMznY`Q_*??HbDik!Cx +z1wVe%E-J?G?Ca;*)2UIEe7;gqAD6E_Mi$ER8oQSdE#uruc(I*IL}rR}hXLYYMlRID +z3h-Bx6hlOza0MQ; +zA&jUaBw=oN&9~*7sb@5yx)ueh==;P}INmz|%a~YT`mbK-{``mUYX9jE9#N>m`7M9? +z`9FKS-|s>c#V%wnnY^b8l8QizB$KV>L>ShrO>`Be(xf*U^V5&qPNUtR`{l1t{q1it +z>J%&{j%MR9|JI+sz;FD`ef-?t_!$4kpMRU<`xY3E3a*WE{@st?O{X)(u9=Q>IMb@| +zlMkHaZ-4s~?_27Ky4RFe>1vv?G~0pAZ&2iiB^JCWdK9KX95>0vTWnpvgv~8!twCjK +znzY%*0-4RpJ9W%xM6|WeV4P8rx`U2KZazeoWxTXLWOk~Jb&x=5sfWn8@Oy?e;3XMe +zHnOGTVF~Qjgt!1Kq=mx`Gc3Z!V9J?QzHc0<0W)$86NJ5rdwr1OR@m1_oIrhSOolND +zNm`Yx@6VooTN3Yq1%d}HRmtAd7_gqD3yp&jUNfdOK+xkMYzZ=ih$nJYdCb|+ny=R} +zc|7b|VEXyD-+cVT|MP!%Gw_BcocsO=VprjZvO#~QlBBt{MM9tfQR{x_i$94tay*Qb +zQhSIAR2$`x=qsTe3ia3+N@ +z@xZDK{reeDwP?Jb!tSJwrx-iNqoM>Fpm5l_!6jgFOJt2u +zk#Rl}1fr}(B)IVq86p#bT#p1|RjUnQp=}b=s@E|- +zFR;^4DZ+8rh*4wEcadXCAp-rMM;>JD?KOZ_@KUJPbLtj6!O8ej5H|HL*^!CzBF|_3 +z-dp_ePn=W)`F=7;4pjcmUq1J<&RIk__kyw)LS7pWfKhdmI}9TbxKrd?F{(B7BXHM-2-`om`>srB|CXIvE2 +z>oqQ{^%zL@4=|GaI%n_PG@pLtCSF+X(yrI!-uqg0PG4G~RjctY{?H>_c4i-t>Fmj +zEyN2(Aqrc>*dolJ?ggWK1liEy{0v=KYy@^HMJ~@F$qjjT%e%~ottGJqiH)&pqhb_W +z230V5j@jk?8vlKVhvR_zNt_5Z&5O^SW1;q_G +zDq+E215Vaft|@GxeMV09o$t#PvR_DB*+-5S7Hh@9v^*~T$RhXi`-Q)Mfi3iYMImkx +zeE#=->!*PfCWUqL9{Z16dFQRWZp2AggOa`=<6*!S;T_E;Gl +zBPvM8-g?53{R@n)T%i8KWt=sbsDM{2-L)}s)ZsV2_I|#wR%LeF=ectu8r7J$uC4RL +z@dH#6%j#yAYNeuj`XcAiJ8t5Gdz*ac(gwravDmKjjZ<5a=H|oq-odZ@#J#+5_BsG@ +zQp1>tqR1dZ0F}s4sWoZNFG)h{O1H;&Fu($dfk7T|(@dSEPLt7i%=OJKQ;jyBk5|)} +z5d?|8=Y<+!GsCEF$woQ;n8e{bgKNZK9O=+e$spfYX4LPJ6X7|0BYay#ZV+L96g&DA +z3lUT{df4wpDv6;HOzEBz340|7S+6%G%S%$L%lRlUGRhK`djpjbWK;di1H{njEiuq*Dl@zJfb-8eGud;@S`B!pE-fP +zdqP&woJxu&7-f>)BWZ;`q{=ROfqK084-_%XEqgmv+RYlKyG&9Ha04f<+WV3jroU2S +zCoeeMtnuaTf_7r5r%q3e*t_7be0ZcDbRzt3FxkkS7b;E{>3XO{9opq@9mZ(ZydY_w?r+_;vw`k0B)4N;eilzbJJ(Qp$lBvcTzw +zniQYjGXOb2#=rT!=QBfW_w6OjwpArDj{MqXxSm2iBFYPjPM4WG4R5cEIkdaR(e5U_ +zI&AN3;V@teRyNkzzc`DFBzJBwfVQw=EWG>pL2f^Kh!;;^;#;qt;_rO%1?cyA=%yu1 +zQE=z}1-5qjJahRS8uR;TH=AlX*Xa#mRUBfR+{xg+g*x}1I4CpP`qnnDZf5LmCc-jB +zR(+jh*0eF>Apk;QZqyHqDe?h>Y>g?2S&FC_>oLO1&XKj0ZN2-1TGT!*kX7 +zRQ=cpq|69>9~#o4YZ1gl?Ph2|*0W`QDz`=on|gt?3A4`oKh8N`fBGvQwIzfb*6v;@V=ROo;Y}rSH@d(`!V%eRctDGQKrWM +z@BJ%5MsKb3`1Gglq0_8M8MCXKJ+Acz?5QOnkYo`=#b{=AXAl++1Ud3?m%(TiDf{L; +zQWrGmc1!wETa1vfV-sUXJxpXuPv0@NB{Q=u5iv<_Nmn(Y8fO~)^QCyhRVm$*N6G9uA+?Cp^5UkZ_SvGO1Sx~-E +zfiF~b;p>A#=&qnxGFgc^Dis11Y3H!rKIWLg?p?)JFB4ZHzWMqUDzp2UsaDusPhmKg +zM95a1QTxjiSWSJqpXnO~A$=VQsORcXx%z*AU? +zTEZxXq==;dbbSl*0-K~ly&P67=q9enHO7cjrBK0YvrVm9Wjq{GZ8k^*fl9na$5R1a +zHA;~$!)8jmPI*c!*?wF#yf+{tazq-dt1@0=CdEYs&}HAlj*2hN%XP9XO{{-tea0IU +zyS$Tm7uId^cfPUmv0r|4;mP-6u?ism%I9BrqRb9$0_{>jAd$6qM+!(}5dEd8@x$36 +z!c7Jwm&ikzGd7?yGI-OC3gX7l>yu9nu+1In;|fo%$7GFp?wILtHA#7Mc^h`k2p|6H +z@(zFVrg0mEU! +z*!20pW5=j{oxjaJpE$~oAKcI9pMQ(zyM|laHQEMINso8>JaNy%#Kw>p +zKxAm75v=xEo10-eHH?Y^Yb;Sv-N9g8&TzECxadm#5C1L1?W58nskG^CT$bmjEE_l6 +z*_1wrVf<7)*P6u9yGTEzy>~&* +zdD+U7j2i%lW;yXumd0;~lcqDJgN*X%As6P;2nmugfjF2*D>wj}gj-Y>VSsoXd0UK5 +z@W#WX!Kpi=dG~(LeD%*h`pb|0vwsUHl&uQaC$FA+=k{_|H42_ujmR3p`VeS1tjZrK +zSq-VF22KTT&ta&f_OJRKgTcq1=cZtg7F2c$t`!x=RJgwbgD7H{M|^9&%gya3))~f| +zUG6`5fJU{7I0S0(9m$@)xwcNFQo$O-wUsrFJ@KQ=-E%Lu-Tyw=PI=`wH@NWI+pIpb +z!p4_&c;R=>v3hZvyYIV)U--3O=b0~kl>@U4e*LE&=U4C8#a4g7i#rzEAM#)R_=7CW +zOfwc|zgWUm@4x9Nzwzl0bD|yd+zV``B> +zZ-dCk6jmF;2IYe;g%6|6GF+`fk;I6*NqhZ7z9Qdmd&)26XrgMr9+L0(JG +zc~}$51?Bk(cX;i_NCH8FF|1#|dK+*O6ekkGD?e1^Sv87HLGBB0I*kNG5kbWSPZGn9 +zf?Qc%(l?-nAiW^i^#=&Eh%~D)FJIi?!8;wK5!qCOt$LMlw!uRjbtM^fxycXG5A(Gx(SmS~XmnkPdUIt6gj&?eS%(^U|%v +z<`${6i<5eckuf5BRMQ%gODi>08qJd1@oYnx7(7&QLg94RLpG&RQ8ijvprqwN((5wt +z#)}v#T$>2z4Th-r=4D7fQOmX?a9dF^KF2 +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 +zT|}1WazD2c~lBN*L%!nGe +zG!>>Aw{!+mjtWKKfZI}GuxzEr@JBQ*I@GqvqVC(pFr$->+5i4q$a;0AsugMx)Y2a9 +zxh^lCU*V6ES9#*`d)es^IrojPG1Xh=7k=#H+;j3I&)6!DnjuFQXUK|Nlp5DIH)vNf +zb{#oEF7Ig6Dvny9J$r;P8nEQgm#hk<^4^tvTsuW)bB&G46xcDgFt~EY@yJ+|WZ$4rUVvv6mFN*>a8U)H +z9>@DC855K?sHLgI*@vtqYBxy|qU-0GYb|oh?ZI5?66ZZ;n`54Qq2P}@=lQi?dW_jx +zosAct<<3rp=|gvLeQk}`dnwHb9-2>>uGi#-BMhU}cbGbO8*f*pS)?FWZC&8f?u7O6 +zc^Hf;EY$QT#}K=TIx1Q$8X4o&)!?;+_#mqUrOsCUyuAMx#%R!?6=d<5=M|X*hrNv> +z7D}eKF*pQH0;Q4~Kb)qT!iV%Kk2CD;%$QMcr$rQMR>GOybNPx9s^J2)+IfdI1(NSu +z`PBC<0(^uno=wpt#Y#L>5*a(BI&1}?nljyKu3B)Bp7=x~j2JLv;ga8U5$#8^J2U)-$w`CuoORbXf=7mfA$v2-R +zj^iK;2x!D%ZXwyc#Gctc>ATrlk{Q0ZJjdK!F~9jw9OEQ8$vdyJr`;fGbr|M3Z(LdC +zYvUSgYwO&!G>ZgT)haxH@d_tqW8Q8brec7AwIh5+0d|Oe2&`9yg1Vacch=}Z&(3J_ +zxUvnA23(mapuwtEF8c+w$*8~%NL!5(#25{+MWY^LIj1fpAom5pU*m(oj4ZL{`gL+& +z#@V24iae|NV66}_lzJ#jP|6_cs-=kG)UrO|(Kx}Pp`;i84_!15*JRp=NCo=>aJi*c +zIH=dCx0_rp9JlocP*{8c>k`5+R7@$^=m3$SV!R^UOd0fcxAx9b5n~u_AZbZ$9Jx6@lf?L +zI}5k+RXWVLg2J&9V~~ +z(9`=U=z3D^5hqksz4tFvH%~fVgrune$=g(Sf*`nNgSWDrx!wRzaSDex=)JLNxp?I| +zl}Z8;k{rz(zmI0SO5F`HsUbQDYK$M)`KY(bDmH9$Dky5IqCT*M4BM>a0#kH+K%s8q0smPS&AUc7pp-`$G& +z&mUdj$L~Hw62W&?dOTFU#_py4{BOw#<|A)yK(#y0Xa$^Qm*mu>HC6EwF`(06zlUDl|zJjOlrk2L4QW6iMsr#df(x#T-ah6Xz%?2?^Ts9Ic!XGS^NgZWr*g|^8^VAD$&J8rNNpNt~n-FY;$^ +zpM(GOKjHX;kI;5GPi>BQqjH +zlq5+=(u6ok#6X(LI;N5&vZQ{g#E4GR7UNX?e}c@|p>9h(B?H)Y!8(ip#Gyh~7>%e_ +zW9=vi$(;O!f^Tju+ru6kn>DJg;MKzMfA&)v*??$ug|2fj$~@A_2l?XpE#80nGzV_J +zl|e7Z4*I0~Z=!kO4Mr;)*h+`Q<>ZACS;gNk@oL84jgftes;*JiQjV$suYsv(icB(V +zeMo6}&O(sTS<6Ql*=I7=TRj1!j>q|PuQC0|hdFuTAb-$bruobt^P>ls=%@4i+wKI{ +zonvOyW!(|-2~J&l3@RA68V$S^T-EGh5_7A$Vtt)?&5l?&q8PmS+iAudB8&$#O%0?E +zrTGfNq~~E0MpwGCTPUAgSY@wv2Co=#Y;LVeeMnHRac4C?$fCM;hn%Y>=*RJ~U|K&; +zYrUx^iwUBj6a6F~uD^+J+YkVvu&zj8WQ?2|A&rS7FJaU8q8$T`g{6UM@XnCxMw+|bHKpibZo@qSFe`RNxiz3?DfGtmfowC8GH-wzai-LB&g~$ii;)l~}Z38rt)R)#% +zfOKeaWzF+p1fYEzN*5-JW`1|x4iZ_=2tgl;>+=c(aqSw4i^541%Xc)=2v*mY+1T2U +zb6P?W&FYC914mH)&!kB@A_{9OOmaOaa<~vVREBo}8$Q`b6r2wo9+k2L?&4l!rlq*V +z?$I6f-J)8lN($AqS|v>@;>DZ@J0)zFu-U?PRivIe)oOJVKl-Vk +z`8V?MxE86EI*e0L_7F`fI%(U`mni+_zrRGTedAO7D18cKb)Qey=4_36LEX9BS*nc| +z|L4f?SiZxr-+PdUZ#qUcwwzksA~ldilo}CBQERYs{v!7rn#Xh&aCs& +zMC+zIsP8?5*}cT_`AcN|g2N_a>`>=Y)%S-w9D^W7t5Tz0tzoeOyr4D+&*M`X@O(VF +zPMgFeDP^rIY2U<2nmLsoN-J`py;6QUaM{&O`8W{NjBu!DubzBQu>ur1 +z^qgk-5FetgQ6CRX)|CY^ZN#PPSNI3BBYyGF6l+_TD6W5-f9k=*{A(Y%kNz-Yr-<<} +zBpq`8DqY~|uYHZG9pRD`n`Pv~0hN0`OskQQW;;Yl>Sx#>MP)3DYPOOvywiU#e+U&G +z6~PEYaRU}rXK8;-?LqjHx=7py!yaFbTD)`c1eIaNP@EDS`+Ggw5oFr0RwdTF$No9x +zShG^2S*r;TZ!mZu@a%_G7a%kdvg4e`ospcnrm!{UmMg3QQy{-)p`AWq%T71 +zgsx2~%p(Fh8(V8aIixa|iOITM&VKeYTzKI{&VBnyb}pTle9Dn3J!J8ID{+#JjET)y +z$vGt=nDRps`V@g|tJN-KzculdmseyPO=&`~xJlzUlcBpta^7x_dcln6N$A!%$Ur=kd +zF;lZxlR|Tv`r|)KqY)7oW1Zb`s8Y#^%X;Ee+h>AVJ#>6TlX_3OsmVqldxNTnb*A?S +z6J%u+nTU#kSGyzr_QM}xIudD`QG1H|cuX|PD56+nZ1ktMDFIW`s-{(%l`6K33{)d< +z=>6agmP7oil?QK?i} +zTfWBMZtdq-tx9EkLke@|wK}!_4)HL<8O21oK2#n1z63d1)tZhy08ZHsj~L}xTPX@^ +z!+ggk!lHg;*3bNk69{FxVdT@?&Bn`LYVUD)>pOtTH~>~;D1viPs`S%DinnfcHw8K5 +znUjR9x5LUCuh2Po6h=KMSC=+w6gyqO`;L{!fstoSYxQ2zo|;=_7onM={iDsX7iDAjO0VRFNat@yKE6W_=3Zho&?Yp>iX$eCl{zgvnm3Abapfi846( +z>MN|qixf$NqV(Ho)wP%VORgd=y@YiF^pr_uI)kRIm6cZ?ms+Wi)7NK6(o_&* +zV|$Y%u3!O-lm1puzX`P|YSXjqT)n_(r_0{=-A!lzAp}O_xODh3h>g}3QGKejp3KfK +zuAS5BKjqDhcXieoz(?3kWHxbOFrLs-&#tMvH(@=(NhAygLT)J9s@xT-&sKIvK6u^a +z+_rC)sBjFYEX6pbXx8Y=x7o8g;7r<}3RoOt8?!-7wK8IV^&-de^E^_#!&@g#aO&El +z?5QMNIs6b6af^>|#kd3_`8LXCM36@Cz@9qiHio>hyuoa}LbFzfBn}c~0HDER_R1PchBW0W=maFl +z5vQ~(HMQAo-EGopOk>wxB9jtJ=iUw9BX3N-GqYxD +z3kzGNE4C_KL(Pkl_AqS9BS%;>Lw+b(K5ywz%pK3WZw2QDHx@HRRkj!bKTatY?=(x4Q@%l!}&uA{b-q`GjFhw6+~sZ +zFV$92ZhI%mhy*w}+u-Q@G^8m5n`3kU(WJ{Vt4iE=y^>N*E7YnLViN}*2Xc>qvU(ZN +ze<>sTaVjzbU;6SF_*Q!#OPy&p8yzaS1zRv~PBDrTs$1I-$M~OmN-J_yUi*V*tFbROoaV8Q-DGMat5F29X6>7nSNFs@cJ?&w7a* +z1df-E<+om;zWWH3W`q9PvQ*NVIdG6_v*m3v8p?pU*2}0YEbZMed-lX@I(vB%syR?? +zkUz>Pm?}R!8FQ$LVm+gYP2MBJx!z}I)MIRO)KNIhv}i!suDY>QYEu-&7Pv7&9*ctU +z)D-P{N~_4pMc`6ZUMv*A5@Fe(&c&!k&u;S6{wX%6wpe=M8?0Vm!_;09)im +zgv#^^VfC1;*Jyor@#${#eLV13uB@za{^|->S2sC(`8xg4NCZbv;Z?2KgfTItx-_ZP +z7!G?p`R9Max2BJB^zc!#d`w=cQP@I&aoB28ah7zaiz|(B`cu|5jT2h6I({rw&97=| +zgT{cAFg!7Uoou1a`^c4I7#WP +zUT680SD8C}oWb@cV<}HJW_sU&GWI126e4jP3#;62P4UnN9@{Xt!zIxopaUvoT{G3` +z4DQdP8m|ce*P7Wn=-P#HSK4DRvvo%U3G7k3eZX1;F866{Fj78jT|cz7Lw9#O5`5I6E?*G7Rhwf$W_PfYO +zBb~@x +zNy07Equ*ot-~npwX{0apNG|h&S(=SDb2IbYw!o#N1E+;~ur~4np&|?6%W7N`Dpu%v +zB0KK{`vjUG&bQVi1v{fYBReMc_tOs1wwi*K#d^XScx^^kW?QY3Y1T9w_= +z)D*Qrk7SgAF<~Z0XN9$!sx@@diSTX8eG?qF-u1&eP<69vxHLmLXxOV70de5}>SdvC +zl_`&T;6No)uY}Xy#HEE;yecx*wm0E>7W@Kt17RhSbo=so*55hH(GP!;osAU=sfRyi +z@#snU4%S9x8E7l2UgxY-_g$P{6rS;9nce+roTP(doYjepbCHdVGgz$Go`gKL)^kvx +z52F4;;5I075f+sRC`WJ9XP9R`GhZn;I4pV})%pO4U?wkCnsEU%U%z91f3-l-lH)yVo*{UQYg4|ko)EVd&Qw(eq^S#GUklXGQm}ZT=AAN+iKm0m>IqdSl2OnX6 +z|6ZXad0{Catddm@tQM;TtE4utkCgZmdOZVDek_S`g=Mg^%$4V#=Hj_4xDR}Uxx>eJ +ztQd0X&9886<191B9wrz4{g}pZ$a=lX${i14lbH7MHC#C(8o8g9Z#z|OmbrJW`rW-p +zyp5CV%DlTl-4cC|!q?TX@}3w2I*F+kRIT-1=^zcY>X()VS5-JV!RVLr$dR+Uz2V!a +znpp-B0sM^%7wE2RapdS-tX?@M-@uVGckmcQ_$Ww$A6pb+d0&{_g^40@@NIk-CxCSW +ztk>I9o5+)B*ld`hMnB9%f`%YgK|mycWYtH6ZGCo${`g(w7>U-y`3!_kUF5?!6+Zxu +z!a=iQNQ7@hkpus64aIDWlp=Uu|9FF9nxSoq3?dw_tic!=^in+u$3Ai&_x#WYc>b$j +z=Y=P~MK;bzs}<2orLmciz5JGDiL^3H`Oiw3KXq4I4nz4|;$H{HQ#&?D>j7;LVy`_?W6PA<~-OAO;t&fIK= +zM}Ov%q%FhO{`WuPmFHgK+V-|Y^!Ydf88B7QmSjTKLcU#*%VMplkx444Ali5|V)OKA +zmY@12Z~gHf@bqW?n5VkedEvu9!hxUs8LE|(WVpk%E3YybZZjPAIda=0EKPOTe(P(H +zCd{ubb2RJIDROqb@dhq3L5#;#y+vx`38jdRwX6fSSyinB3LIg12-!oYZX{u@G)2Eq +z8K6I9;fVa&Beoih(Q<8TJ6kx3=!^p&IXugmR@l07k!)*7Yj%!}<;w_#s3a^LIRO@- +z)CdJJf)sPJ3qqL?q?6p2d>1FKM^Utz0Pfgb$4@P#AO +zs81tF7}bVGD-~QcwCc +zpH}XRF@mg)h-@%q{n9EMmoKyV=9{douduSTz}n0-!@av0jYm9qWZElKR +z(Sd4>@z@Fy?>TxWE8qGH5%l9EW%1RQg{?}80++;|j}w_AE!nM5`L6N;Sd}k4rObfXcF>;VO}%koNLyo?TYBiw)R< +z%dfu3(#c!tZmv;e8N-cr4m@x_)m9tIioISun4g&wB(b8vFi_e9kKJ|aDfiu90`QR< +zZ+sRo5SZmAbbn#iAG9cC2wcwLB{tP!C~`0?GJ4}7S{fiUj@rPuB`YlhruP*gJ&IEq +zOs%?0@X*tX9V#nBa>W5E(U>$@XKh@iAO$R%)Xtm19pnXak8&VKy#dpm4tIazgIs>+ +z9H+nf4YrORVgBG=ndyshjtIVSK(v{ +zYdc+vGWrrGs|b6$$NKqqa3d==qvg}*q)nw=Xa3M}WQ1dbmz(FNXGHeM3yZLeK145y +z_|P1003-mwTLzG^Rfbfb6#;A^uc2ImCKJN~TGPQPa-iARV@B^HN7fI7H$3pW&@ajP +zJ)OdWf{>C-%;|F|9t#i%u+|zWjb_UBPKH+}It+!2ksu^R(1~Q!Zj_N!8UWrSRLFHy +zIF^ncW@^_Wm)?AbYp=Y*uA5FUb8x@N60t29_eS(Lw&<^IFj!e7-|6D2DYdy-_TBeB +z(pHmRcbm<3F4N8}!+GP^X-!u~y?@rvV=Jj>bM+F)_damK}1fTSAFQccDrwP^~I +z5Ia>4M9vCAOx2oHlPVTvT_*~PmXm?ThB%GiK-orhkccJ=9s!zhQD;!`L;widp_3@; +zXXUg;qo-hj^Vr7D4tY^%_QR0r*T}+R%;lG#XLkQ#wpOpp`|T`W=jaC>lb9h_6lEMg +z%uY=Ua!A$~uZYmxl1#%}pa^m*tyg>5VARBTcQ&sAlv$xf4taV)L9`^X22bu}LG?R6 +z?pg+TC+nm2)O}CozJ}&t0cmG_gcw1BqV=@v;W<=5?VtV>rBi&GhFJFFbObzQ2 +z+4lDjfx$^u!(^%xB=`ZOpCZeJBpjG;Q!q_uw+ +zmF8hdITV5D>lN`+cg;R&AE5E&)vWfC{b{ +z@Un{<5IukB%H(H-_*bb`3I)h8-bDLV4`LM*!+WZ%6X;GIy519cBJm +zMVK6nMyyd#Nve!@b~thGZB!c#K^`LU38K5TMr(Q(X<0LL +zQ7Y!OQCs~ThWS|F2h=<;!Au(?3Xp5Bzf5~+FWc*@;&!rq^&%%f`Y|ev7PiQx-|6-g +z)k;lN#c^!(*>M<|J!~|;_rNLO9Dpg8;`WE{f7Su=exPbI2P!aYKCEz`FuAm*3&I#9 +zgVoJ;R1G^htH?6x6bFD7^ehLYh#Yk(dr+aU07Xf*Q6NCQUu*R$%|?RFEnbEyoX8d- +za;dgOSSbdX6tD5ogAjVKn%Gbk^=yG1XH;t`Q8gh>6R=hy6^r3Wit$reUv^B^G0-0j +z*vtyD{)mIO9AkEIfg&%|oyS@>x300cco>VN7PpDx3Xv&T*%{E!96mn2uvXkh8fi@g +za#gJRF;XCRA=0RaaREq!FsHKLHZEY#f~ZOGm$`}fV5|OELG~1M{PmhreZ1A{(a$oW +zrWQp`9~7mYd*bFU$S~U2A+FZN=c*WIw3il{J8-0=mWG+BDH>JrTNC6^79SrU?}!Yy +zJo50fKmiz!Abh;T^TLmpOksoqWteV06y5-h8iOEk%0xy0tvlM$S;DDCE8vS|(cpfy +z+$-Q3512$OR2z(oh}@O}y!IC0t1DTenVBZ8P+F9`6hB@1c8URp@hB6kdbLqUXG$F; +zXN6x6Dqjh3=T>-K>_nl^Nn6Zt$Ywp|_Iqw&_rZM>d8zR#4WOf8Uj~1wGY`&CP1=a@ +z56A7<6)M((F@i`_l{$^@E>-{t1$-Spc`tOIP&5XN7qE3=BvMZSuWnT#UA0vJN*pv^ +z9C?PxN@6!o*%Dc;yaVO6TfH6n*@y`Iae*U`nsu~8`&_BN~83=pr-Z{oM&~)K~U?gk1Cu_n#4qmt)M9yM-sZhf?C}>({-xC +za9b=fxl0sqgO#uvzTdJbE8XyB5fE +zi_=oEBHs*$+sw}H70YnNv=KQDXuFKPgRAu7Dv`};rd68NhWBmK=TfXE41QCUtBt^_ +zn|qLURu39fb?cS;XgW|ePz5C@3ooqyJJVtUAm!;Z_ +zNl4QwCQW7AdnAi}BYu~9Tq5gcq_qY+n`?}=x7l^)?INs?^PK5ci)OVhh~kmMs&}wH +zs&jFiBx3`w_z#z$BX??M_ntGWmoJ_)k+D{4FM}xsI|Eq#z}~&Yzi;3~=na2DIaDJS +zIID#LJSs>M1@;gX9LppEGnEDgC?d!$SPPR`S3?IC>!4Cg=+t7?hZ$Mi#5#kPb_1mj +z2!f2deVWadgm8{WV|g{ojx;c_D{Ralxj3a#$G +zHohu5Bdl1x<4TRB(WFvu +zlGYo5q7s2armBxWD=OF*J;%o*c)0f4|jg8#hb;&#F_bB +zdrt#z0{}JwED!_BH{J987ruG<@17(wHi~is5-hK*j`zg?>nTUINqi$7(w8&~T-a5(4||fE#D-c@(2NZ$MPs6J6@=1hw$){6W?JM7 +zeKNg~ieD&;q5fWz52Lyo5(jHo#(Q?raE?L)f=UE66yqU|f@-ZrY8rAp28zhAC)?ze +zyv0JZO{-dGY?Yh`!KX~8)bcUrYY!iA}>#M0*IE0bN!;PQpM4LAn +z^jW@ehRg4~{oVCBHm+Ua*nJT%?uN2pEB&`4sY8cnYv)=+OP#SPwj`39R~j^6#?3&0AH0C@uX +z`^o*4C%*|lV;Yl4AoobuJ +z#L~Zw49%!ucQXYO>lG6<7gX+vlbG6cn-|Wn<7O%fI01TUta!1xO=IaGo~PEXk-;u> +zKg<>$D!9>ob6MEz-rLivPd&0-c-iT!t&0Rhka{0|SIQ`;t +ztX{cHTCY>B)tTFOfd0-F&ACOAY8@~0jYb0o-4*ig7L{g8yeGC-R#-WIh6~RNn0(Z4;`bPBuq7%6xQnZm3L8Ur@7e0jA5{jUpQKO5)>>FLKyjaoTMY0 +zkE`mw?Q{;0l*Nd2mePE#5=Y*XK~okg0;@ur0$Z3}lVH#^4jMs@7D<%Ihc=#HHVMzY +zafVNR;9gp_iXh?*A>T6b6QiOdt2KD#+%@i+&KV&{8zQJ!q!IV%Xk$R*SSi>?VLcNo +z?<}l>0-OM$QystO6GB`iwvH>Yqt~oUV3S3Ck8}_%-K|wRa|cPH8sho^42jrdGm`!? +zm#1!_89||LArrmLG%u +zpo26=5Pubw76)tVuC(#OgaQVs4wETAgK-#~KT*uB#M-5f$sGwVT4bhH=&b}Ui7fTn*T2Zc*IpuN +z&2Zw0Pf?M$6hj<~fKF^;*>|=;D0n_5n_XaL&mpPWJLq-kZLX5{x+Egd*EO?bt%->u +zX;-n9cCA*1i|0fb!oUfo8U^2jU5Px|b@=FOpz;SOfbMc_+XH%Eo3*XM3~Idf95<_@&o+t{7>;{{}S`rDg|BG%oA)mDR=%wAEbwDSUPp0TyH%Ao@fA%iMx87!V+1qa&M(QgsJFNWsF8r%63v=(B0)-x1`Ebbl9{)H|k_uIlq;O#(B#^N+&N>?#6BiG_=RtppAR(Le +z+@2%HUb=YZ)Lju25m6lEM^X`50DC9vkrf$Y6!iLa2Rbe`AR{X8LmWY345PxbIn3!7 +zmL!4$Qyq4-8?>qsduQi3v^2w>xoJAh8fgr1R8VPm_{P_tXIJMOv$I1WrCKraF^9vP +z%U4IdbFRq>!~+c +zI=H~X-FGt1g0 +ziO-;)q}-Wp;|41{m)y;)V6T@ZG&fb~->Nc^vb&BjeWhxngEx!R;HNU7CykSlp7sXS +zkhbSve~CB0`6T^*pL(Ywwchh=R3>FhNbA!`JW#L4sNbXBY$2?y=GqnCs_xn~w%4zb +zRBBk8%EvT$@-0A +zM7FRtUOIToXsp_cdO0^k1ug@?>xIZ9Xjng^=d*TiK$jnXwobaZh4Dn$I{r-;O6^oMWDu7@cv1vrVfRAp9&}}4UOUvBp6oRknKi` +z6v%}=LDj43iY`F2wCeS4(FInzBQ7t_(}@h-!ZDSARW*F*KB2DX +z$oT-16`@r8QgysH%sP$mL@7UdKWSgc44w}M-xb3DigPc&LVaP8cB@4`E)Wz?60o^s +z(=SFV7)I0*!^Y{av6}mgl?hQ?!^9QhYC|TdYP}(VZCnZ!QSr;t>u!1@qd07uG!a(K +zjs|45U}oJl_f#DLDH9wh9aycHN#-MBN{Yg +zEu%DMH#x7r^ekU1Zf5u47KgSj(7Se-tH;|_q-7`AiE(N4-wJa8w+_wS`ztqPw%E((U(NWF#2YmAJVEB$o} +zW~WD@&dhV~o%2}R<>cZtTYW1vLOAywsvECiNYa@hPMS>5-Nwey@UK1*v9wqt +z2C{6(a5y9z4(M)gv$3(x`n7AEKX-{2U%vt?tL(jFj#LtKj7W^d#dYir4X45(61jvO +z3gdGo4aWlpnIH$6(Ia$O2SPFi;@7k?DtL5vonQRuLrhK0GR$%Yqa2^t$QW-*M%|dx +z7)*>5wQn@I_?54-cI_%h_s;RwsjF-c;lqy{lA?>f-iS1Tt?fSD!I-z-a{R=H?&OYk +z!FXen_R +z;`aUQ87^a12K<++Eq>>h`xzGL;FJ)~^bDn(t60_Y? +z)XW%DZ9}YHQ&>Pk4GKm(+f460$eA}^XZ6x~9{$wNQt!-A6l0n5mS5SBQ^)yuL-H+r#t*m{CFZ5C4oywUoK-9cGg;jhPtn-t1>c +zL7hR)`gYERe!@o|o96C&4wGjE&Kj1t_weRQO2r`jc|6Wpy7L|`q!pfg<0DK0T +zu`i^F<;t5cuzBeM?S(~JGjr6Y+H~d@B_FM*%?k^G>$~i#s@R8(`}&N8;kL4t=Z_g} +zZ*l3ZSE+TD$p-)?m$%PCQfmT8SQK_ +zv-==#eDyONzWV`=+<70ah+Jfgq~2_=F2QF5>hS|0NCLch$HO1`+S6bB2R{*;xX7|$ +z($5BA0qD|&UfFUtF&9;0LpQg~Cu2VS(TACuo?|fR%ZsRa#UOlTRAB5XWS23OA?csI +zgnRWA!zgEUeT#GF-r@7Rk1|tj&|O_&=%CV^qSqf%ucnL!8JpW9&aS8Yjr(W$(B1n* +z*o%hSbfbBGe?3fkOt9#7JP2Ac@+S(TT_8ep9)N7-T8L+?YUb$ +zNNUZKHCeVL3u6m|ZQ_sshG$HMmq&m+0|{h+gn$!B7#uJd9$_@GF<@K5mMvQzWJ|57 +zTVv02_nq&pxz4cne%80D)>*asIq@Te$cA&*>AH2Rx`uQ1cfRTW{r^rtjj8DpwVQ0c +z=?yHMJHwX!*YL`kcFy=$-SN6dfg=O&@pTwTw$k6ZYvlJ2eoB2unnt=Eb%u;1gN!>qO;vd3>9T~@0)I6c50eVr-wA)oThK;pnGVujOrXibAEabZ(Sx7{JKIlO|${@EOP4eQ+wfAEEKY`FeP^2s8~ +zFXL1mFbo*eO)q6MU*)Df*YV0pzH`r<1`!bHWK;;F1>V!mGc+a +za_tR_Z`c4>LDV`;#s{nzGGwxu@gc6n0oQM<)uc4Fk+iu^@u~w9Cb!`E7A=Jvk5YNE +z?}^*w@;*7!BDSOCb3R%FCWA29z86s-IaL~4KK-#Tvgw*}>JyX9O^;Caz?Y~Xre3d0 +zIXATm)z629$|%+f3_gv!csj@W@(R6XgVIP%p7_1H%D)HMn*VwY!~g&fj?Zm5bn57H +z2LyWJoF4E~4Mr-nP&Df(gCfvM7V%il=bwGmI5yAD(A7Rcu7p}`VuJ|h*YVn?(e;z) +zm6OnDkhD{d%qKj2BH$UFCubV?X+mgHs*M({bw@Wg#5hD~(xl{a0j>2m);do10^YPu +zVY;jM;buCkYy8Ub5x)DXGC3Uq;OPXM!P8dUyCxwFoRk$0Y^?F@>Jr@~rI63CNe(AO +zam^^^H&34Djw^RjD?(~LtkZN%j!rttLeDVfg#-j_EF}c~EIre~xR^vGINuJ2e#B!g +zES=(}s~(_ODiOt9UVP$dzVIiXU~121(m3gR8J`sJ@&Schh%+WbR1LAh46eQ~&@T*& +zRp~gl*B5A6D|n{v$_bwT^f{(??qkF5y{~XU;joxKa8eEPR%I)v4<8yL+SQ7v*A)fX +zwAEnl?|vV6;FebKb2)TBzqFBKsg-ckrZTU;b~C^Du{x!~NTz>?7>9&cQ5=Ial*&a; +zzId902QQM_Hbbs9!c;jRNZaHztaM{8F4rmL1B^*A7OXY+zLbnX>B0E4d~a)FR?nTI +z+gP`ykug0rxBU?C5CCAlegnxKi{AZ~JAVDg-*@=^N{fN$Y-9n38C^38F~JR`hH~ih +z<8Qr_jpHNq2=Jdff;)5+Rb9Z_7vQ(fV=f${z1n8ITi{ZX=f#H4Q;jNlU$KQqV%KJz +z(lyXc6j9gF>U3ooA4MO76A6;D9eZpm1#GQ#86DezH57ub5kooqXBJ*Dc1@EptM6K>>; +zbi)%>*c{eK!;Zlsh`U`HtE>3NqUYwQ%*@j5)WvXo=fpU*O$p0BvF)%NYyRTd4vLtZ +z?w~0Q;kv9feQcYSkooY_$FO4~obrm?JXN6@K+ae~tyo`K>67K^p^3&gO64Nx=kh3F +zm{f?76w2hP6)v7QV#nvU>7RV-ZNCmg!{OD}Zy*+63O+nKHT5HFiwirnQr23h#aj%C +z$ODkgFN?Tl!jIg43lsT(wqGLm(kaw`{t3)YHR6dX>yMo0(xr$qW}LH3v#diBe6%sl +z1hIq_jX0s%>0y?`EkvxUU3`_NZQmMkZ +z7cYt~#MoF!oTM4;j7%lMNTtS!)AJO|ihL+dPa@NI_eySs#c`$8=8KIE-?4WKsf|g= +z4MMMsYtr_6Lt&1zm*S}aYn_A^z7DX)BF|1eNpP2sDrZy6cj*%v*=#4L&KFZ1G4l%p)N}^5|v${mJHcA)37@;PH6b>aB_b6rvC>9 +z5&%p0f6v?hNCAM&*!YHgM@9##HRqZg-hJDC_G}oVjYqKFA$|XUV(GqJoXA)B%vTzG +zTHb+X4UX+s0l{m1`80TZ07-{q(Q#zSX^GA5gF>;m^d{U>!>$kdZ{K!Qqr!W +z+m3LBa6tamXFH7KD%eazCMpK&708#ziBl!U>M&fhYn%(m&#_duQth(YB7^>fo9{zj +zY3cM^ja0A(@D|_GthYKm)@pFW#xYWxlHwtRX{aIA=T?%Vn>c*u;hB)g_Gp+TF1Ajw +z5}&7@EYnO^N&GJJYx68UcbKO?^*N58S|L}+;jENS1&P}2Mqs7fIV_>(vI5EW&6_CX +zOPsv0%>2qaonC~=yh%nHGc+KSkst)?De6*$>&DFOy_H8E{1cYH@;ORLkB;)BgWXxW +zNa?d5Wb2!Mh#$CNj{LcoNwm!8h!D-jE3Tw<;wcJ~qXe}H;v^woEhF5?XjCpop-@1F +zik?mcAUatMC>H~qB`Xf}yY#8G)K}V^I@RKY??WlahFXb&a?&#BwYyxLUn4K_1q`NE +zhdBad2Fb(d0Mo29=wexG{jgA8I{1YT{~Ba#{4Z-D4)B2U-}w*T_5ME2%E$?VVRsL* +zke0eV-g@H|Qkt|u0kgc!v6Bz-*w|L)&o85GL{&SEA9|7Fr_b@iv6KAXpZ_^WPMsEm +zYV{%-J;%awn?~1w0R0?E*U*b&a;3b?3Istw+>Nk_A$1<}iDTDnj*IJ-PIsAnXz&Ar +zrz253y?3w2m!CL6oM>`^?su4lx`zuFS9$W_OK9H{?~;gNSR7!CmBsq>g-dLj8D*>( +z(lr_VQASW;>7)Uz#KTA9n~?Qrja74zM!X^+nX^hrq;o-mGv}5_UOY~G?krz^=?t?o +zV|2PbG2SGJCCqBKJJPgt8Qc93qB=?p_ug|W-~aa?;0M3^0q%Lt^-PVA%EENRz{4!z +z7Q;7)9>!Wq&~)ff)3zyzi`cMZ7y9v!@}}btvbD2H(+`MF9%b~l2cWiz+0DDTBezU= +z^l6ONL`jM+*VsB9|Ie-?!koge#875M9UO*JZ#4C$vtwr>5 +z*B$r2A2<(qfcwiENMSnbR{7G&C8j27q}Iqh&C1$3>+Q~0_aJhfu`*&-5dnv +zkPX`LV0j1s^{JEWJMs_{QHK>TPq8*eE}y4k6qT8++)!BM#G$8IY&DtLe}G`eYe;>K +zm&;)iL+AzgL7t#mk&vrcDu_YoVww8HIHj=>MCD0oxzby-TtGZ8PuvSwaaE4@I#3AM +zJXvAX*W{EVjuMs@n_w+jxGzaK*`~67zcIKVPt@t!?)r)!6e}11@W=k%Z+@-P?$@8B +zU!IFx`1W^y*9ZUW`+nu81fS)c1^`Ds@Ht&?@YZYg@QydUhV}I|4n22-<40cL@WX#f +z8q5;-9h|R-OhW3Unj^foofrYES5mU!Z^muNKFlGqu8c)B=FnVqPTZ?<{tk*C>n?QS-28j}gg +z4Kw37SHq;K0NzsP=p_a~Lau{n&a-{%W=4uRdPxtZRmPU?G{!+AE?}KxFW?kw@v_AD +zgd8BI?Z+8)j#OoUYaGaQM0BIe7SGCTkU;5>jLu11@!99A4Mp*PXz?+o_?YpfVy7 +zZuG2SuX6lOBVh3ppWsa!uH&NbeOXy7fF&}9B5|*@|~`Axez;^j~p1qT*0z^Okn|omF8X;D7vb|mMZMnHi52| +z!5TJi7-My%NwHdjj4e^HU)6GnTel{>bm%$GpPOdy{;iBwLg7ym!Z_*hmZ{|{l2|V{ +zn|!60@-43*RMUpM4%yHU7AMWgBoQP4&U*Ar3#(#sxdLa;E^_FJ7uZ=1xoW1!=oLGK +zspFx8hv=mqBb73VHKb8YzEG56fH41QABP +z`ty(R!i5E{n3>GxOa`D3143Y69l^Sl&VR>Q+7(T?;GsjwY4DY%*@-#eQXX2|%w6Z6 +z=Wb8YDP4<}Sp|WYi&wt(s(rltm7~m0%#uHMl#$!-BMJh>$|Yeq419%inihFBbe7q_ +z@FE-5k5HtAD!mE36h|x}U)hg|9ki1!hfYUd{Vs>e&6Jp~6&WuC6qF~usnyjEy<*=UzfwE1_NzbjOZ>`Df>Pp^|7r{*n-y99sqgzcKmX6( +z_bb1ul`;b#=E%to%f?t%d#`9di~nG1&1Y;31HAJ +ztF)xX$(UsnCy+WK=VVS`PhcQ0%uJTR8aXf~O0m{T$g*NXZk|b~PK<#-%Tp#cOmO!2 +z1={T{X0G+{~2lcBN=?I3jWsmEqp`C)slLGvty6sVz~H@jTEfFUO^ub~9BoPx2&_ +zMv;HRo8I;FpFZ@36~M>26!@z$kgU^KgAdN`*!g{zjvu=|ABN`0(u((k_uh@Ybdu$V +zeu1giej7=Ei)vzT!__RQOGPx +zH^Os|DscCz?Xv503a&LVW3BhzD?K7EOOd$);kWHTLL`bqfsSe3DwVR7{WFMjzV +zrZh%wB;@Mi6k)L_pMPS*Ch~I|NIh^~MZ83y2D9l33I0@pVyG|*)0&;SYA0VSUCSqGpj_klh%A^k)2kiSZG} +zOkbwjSdzprG;w0VrSdpBb%ZTPtYIzh7%hi=DLBv?Tw+Ok34(ERJ`i)&M2==7!D*ii +z>8p{xLSmTPFvrd(kI_(&7OF_Nl%_1@itMy49=P~vyfa@U@h9koW8e&`IDwLRjx^J` +zwBnpNa<0Wko~<)Gkt0_vGg_@skm5~O1XVlTHaf7Fq(71><#sr|95Gc51l}}R^62y| +zF&-Beo91=5?e!1be$!L`;h+Aa5Aw_Z5WoQZ)f!0VMWUbn_Ba0PAN&s|{(P<5o0ISq)}-5Y>6hdSSxWTrf0Dh8CKFs8>_(O +zb2vntQQ*{RFBMGG3L*Q(p-?HxM46Y!T)b#&jEz-szPDz{H- +zip3cmO8N{Y4(N6&_?{v$hQlY%GCf)tH2B2Bq3JWSYce?iM<&bRGAdZD#j^}%kPI(e{H2oXZmu-2IoQ0b*NOa9$HEje;{Tnw-|7}gv2-z7TOUSz$5QN%UG-e+%P?> +zGlqPj%uKup-YW8Kgqi0f8_>}YIEfAMor*@fV&1j+zCfq{dM%NYCmxSTo`DCYYjM!Q^RyT9-btswqG*EA(b&v1U3q< +z0}&3dAf;}B^y`73S)9sM_rzL`TercL8$6ynok(>qwLm7&I;4>fnu%HsGrpAz`8*Y; +z>3Q&;_E%v3D66(Y-cK>|`~Yo%LX`o8elXKt8ZGnm$#p(_c!gafKF5kB?wfM#_Aaqt +zG>fGx8J#LIKBbA%72+hqph?pPCh9V?ah9`}T2!x`;K5HmO{15xao1Gp($sG-@zFi- +zF~G}=<9~^PWZU;0*!kWsJrv$ET@9(@V10A)Awy +zljv(Oee4jap<@i4N}hsrKoKuvVuP<>#RgnaaTIeJ{fdDYGsF%k`G&F3e|_Ts5uncq +zMjZedA~uo9Mhj9<@TnF9MoUE;WDC(*GMXKEKMaZi(|aOiT}wu(ugMi5HOQFp!K266 +zG**?10FqxImABNU44P#Ihp_-Kn3+*z1ku(GMEf-kGQM0lkT-S%1zHJjwX%3U3q3^| +zIC3Qq3AsRajHT1HLe%}X>jEaqj%vw+={9&i1O>rj*^FY9OhH#MQ7Q6NP@}9g3to=@ +zGdan4<7J*(cZ8uOwgQ~Qk0L^U6q6spVL^LRB@C6L5jj47Xc3ZxW4QwFoDR8m!m!$m +z*cY7Q#nV1dzF4Q|$8;kDqy&BmIa;32uRM5~nW9gvTw=N1!S_9Taem#eR0@qJ4*lMb +z@t52*|9`h^^6KcX{@4%y`<%8^re|$)WIyZc4LmInXuN)ROTwqfkhEiLF9iz%rEpTh +zM3U4rl`tbtj3nvOsB@N4FOWL@K(R;?%_PAnkF-=^Q!$}9F)1h3GwBfZXBJLnA}@)9 +z^`4_0*}e$B>My>5#Bm0=v~u;e0=kTnR6bXfByzu-Iy$MPn;5wto0LJL3$ob^Kk!gM +zd+`F5aFw&m=Q!Pnq%1y=V>ZUhlk=qlqR8tBf0pDESn1}AF$n}EHL|;m^9me;?<+f* +zBm*e$0!6cFSZ-P(4XUcqp33?v7F=w((1vR^dhDF^gau!txOS#U+bV*5LFC{V7=`N_ +zcmIA6_$-D+>NdwN7ja|!9A_4LQeiSq@)f*9Q(W(%ixaqFN&b%)1c+z%G>0#=c8uuk%R=F%FF~eZd_rh)5Vz>-ycRCV;onE=O}nc(qAfsTx_NG9rxeB&;P|0Rk=jZmm<_R78e(9RK&FNz=;=(+!^JwDHZpXCh9Xib}NjL5{ +z!^9}W%Lu#~qgC^e+_u7Ls7sHddbB>z7b_yW(c +z*3oLVSiHD^NgeB*7LPr5f~^xHB&q4Yrjs0HAT_5zzHg8ZWC1BGm=r4|U5n%u#(^=| +z*x)3GvcuBOgRf3shrS*Q0I=r=tQ8Di*~7X_z#n5+Y{K=Ein+0nR6;mSnmVqV$@2%# +zoZ)O7P%9NkEr@YgJ8-)hw#(&sDh;{co*`YmK)vhm0#Bae8wlEld@sR_O$$NBKpw;M +zHH+<(=g+r^*IQgP0srR~#qQCBp7JSen3aOfmez4@D0XR>0AG%~mf07%74N1&yr(2- +z#z*oEUzIcgCC(hDcZHagcp$AS&J^>CB89|yZJI&^Diqmt784ZO;YpoM;akb6DdY83!Bo!uW-GJp#`VxWX2?dcDkem=D +zyU=B-epr+mTAhe<3+o&`wZNstRe?B+%}$V?p5;TIJC5-}0*^tcA~ZD0S28sh_?{T2 +z7=T4O_+h>v-Rn?{1ti(FCX?ni29u<+J7WMhB!-hDRz!xXqXj0)P_25IzFr@S-s!@4 +zPI2!IlWdr&F)>yhgsK7#Y}GH(@yarD;|5en!swV`!mKm262+dJ&(7|PEUYK^{y@G> +z;ddORMi)Cij+vY5=SU7=xJezSR}1{j%-3ENsOMY=FQK&_uc<2V@k}nzm^;XNC6-Cd{`X&xBklcpE|H@ +zI$c;+ekCy!Y{FWzAxwBi`xs*ew@=T3r!d!)1G7C|R4p*_x8%iu3FeHP;AQutoGDZp>)x1Yy +z1=mEBSUMep>34TEG9=aKkkv8-76#dz1Son@!fLZe6iG5nKJ=L!uTUxFQ2p}t7oR%I +zp@oRsc2$XzOs&;eE5r=-PM72B4X&t-P$?GBAl_nNsJABA +zHIFoMl-fpS?@i4|>x>5q>m-yrvIzg+&I)7Y9Nj1;9|qJ$O02FmY_U*<9Wvb+HvdAyhBUW1}^NV%p +z2K?j|4R-I`PFkqZSy{!+FW|4NKrYXmpAa?tOpHf^5$Tpds-8BDa9SCq@Mo@k+%das^uc_oFV@)5{>FCP=N{Cd>2UH3$sC1=PnwO-~GwI8PYBFs;`3J#tm5SlO +zqsX6)m3{=i2TSW+K6z-7>$lWM%mAK(RM)~>z~Wk+H{E?3-(D$m@bC*9J9|+=$%#st +zQXz*JtF;U^>4T6-CR=MW7Iz5`#qgkO@z+6j!R3v3?~G|136<8GjvF@wY@aC-Cq_Et +z($y{m^r9S{)R0$2ieqEAfi&Epzcx_)N)uLEF?M{CvEDjD=%&X}4*Ud4T~B&eYVRgg +zsf2YhNF|Nkd+%pT1#Im>$p3 +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 +z#^jBaFr(ya^5qdhL$P)gp*2>fR2=1m+seV&<# +zF)4qK$nzJtdfy(fhE8nw(i6`zGn?;AaD#)4uHC?e<&?Yb8KGM8rMDvC7i8M>zE85g +zPGRTOly!_tdf){NG*XZoEltU@agl=Ok#u{c%Ev>|UGEWxijB!Ke^i@;(FeHq{O|C& +zOFM|K3MpvlSWW1qTrmzd>fv`<7-=Bp(I{LmMx_yjpv_{uf!{cF1#37eevh8Zr}@AJ +zV;ipi)bIVvZQslATH_ndgFsf<>fQII2j2G5(X*egw_3i!B@>fle8;`7HIlN&ycDDs6Zly(A3F!9bRnh=09Ax +ziSw>Z%`;dppSU#kYjZa|`bR%|;B7z;vWnF=q6Zml2JoD74dAVZp84`a$BrJqDW4Bh +zXPy5FMbOh3dqVXVy#&k#+ihB$`Pr(oM=hK1S~B>OThaMI+Mje4LxmYn0} +zk&rvLjPQY@t6&tXEz5Mi!zWL!aii!@Lf1NK#eglzIn>!_3H=FDuONoyk_Z#6Garre +zMB^$xmu@Dc%~S>2uAJKLN-(zZs>grlC$_v5XhH@f`9?Po$jZN~2d=&K%?A%Y{m|nN +zAG{9jC8tlF4bGjZlNLM*P6^jl+O*_+MLyIlwBf*1AliY-IBc;*(K%uGHkni@j$sdx +z;Y_}U#-1DY9G7SAnUP%g6&%?!Z8-d4y_o{R~&d7V#JFhPspYQvm;_zs>fVN6tjy< +zxQQC$lSPg-V_K)zu_(cb6^cbJoH@sZ&pg4K{@&dHXiv#`d~tmoUAdKVzK9nWH;aKlR{mXl_j~^F{Uf{f +ze{8A{+~g-V^>dydTTx5J4-_(OrWI>Z!uj4SwD+N$q{OS>>J3I)EeJGNH-L6zwlVaN +z`|pOSj^R65#Y$c7TC$hS1Tlxy<0Vg#zHL+uu{g$xc~U$+UA}^P*<)`Y{P>b* +zEo&dubd08#m<$ZfW`ap{TJqCicH1pq`Q`7=zXe$OTE_8>nuGj*8i{jmasBA?Z`?RK +z_Ndphz7rJ!Md}2(OJw1AGo|YS{{F2+c8+VA226EGZR0d!6%Wr>_^<392!x^^Yg?x< +z29nefC(wIkj~%_Zf15a>M2H&XIcW;nfSf{J%M*1hAwCN{5Ml-&xeWs)ga`7NKlls- +zlr0!51lhdiz$Q+tIC=c^sZ$g}j|bj#5AXZ&|ARNZ;Vzy(e1eBRf0*;D4Z7V}uvgVe +zne!JHh~q?>i`JQeF1X}2v$NB*d;JMSp6YwaqECkf!7~`OB9KH1( +zjiiS~IptJp(j=I>{WZ@8tRe`?&7v{ajjD;DsY6dHV1%-uv^v&ztYu +z$2C`7!KMwG=*hE`ZAffV8P`R;h6M-o)pm7k=(IKEG0o;lA2nJe +zK~t2+GvgR4W%~$`cRz4NGc0a{HOx2Rv^H#SH6RoLA+$(PXwWex@HzY3vn)3(?5HtW +z@foicCDdF^6st~)q+X+E#GA<|fzqthd!$%bD+P9bX+601TkrgXpSXhW1EQ}**YnM6 +zAdt-p_r7rGbKmvbj~qVr=*Rx>pPRVH>Uz$!g0km%ikuFlvF2+JEo8`!qVmuV+9pBE +z3I~ftIBX%C+dvFrpwusmJ=B4OV-*K7F^HjKEqPC16B)G)Ssbr$SY@aa<6!|ufMvE{ +zmOh^vF{s!mPDU}y#!@?HcCg#+T7{WX$n02=k#ZGbf-#D=fvQeK3&vNl8oly+Kqs4AOH#{)LB$Uw +z?D5fr#;1(O;qEHW_UgPfw@ENZR)I2o3L6Q+!P!2#R)dd6&S?OCr*Vweh12Y-OtI=k +zDC=Z^=`-tRIh}NbB-!G~GIxc+Qq-Dv=67Q~N8$uY%rj|+^R#(KZj2=7SaK1C(U5{y +ze4|d=^luRC)ZkNTb&s8&; +zjiVkmU%<v +z{L4pCcqD<(%t(Q)Gvm@viBm^A(R32SrZDNx>nrYdiM_a +zuM!D^`|laY-vk56B3wn_9KZAapZ>{nzx`7m`uz=lWoBf&lz583Xp6FnT;wqx=Q(Y9 +z?01g+<1;MfBb)}T!&$ij2ihnx94(DPDEEAbolM?s3v0wBnTsYL`}m>8YigV!8?&{u~#>F8Q=Ti-^xX^I6U>Kf~s^ef_Jp +z6Ox6XTtx6;rbp6CNFwR=2o(@o^W_&$@JEk5%MBYRxZ%KF_Fl1zn{PP44cA=Bg$G{G +z(}!Q;&mTR+Xh$2EoHo9x1SX@dD>g&r2U?EphDMI4Zw)5{JXF +zZZtWUQoy3|h%Fpi?I!E(PLQvT&Hwsc8@>y81PC0Y|HBUX-w*>Cgun^lJ9vbD^PM*@ +zescM@o<9B5>vGPMGMCX#$AKl6DwbT2tBbDy79-aD2oFW7Ey`sQezLOVyo+Z+J2>C1 +zGi^i4#gO@+i?#}_p=&I4n-HL3D6CLK*08}Zu&+4DrF@H~Z!lIfuOp7>i1(^Fo|{;v +z5o&6I$L~G)QU1$U{u4Lc@>*OXaW>9aoHgQgppO)A1~2r8qlkL5F2r%;l{{0`BAqDW +zk01ULy@wvwbzUg(m@pZ4^(MP|`@BZGG*?r|0#)vl~i4662!gAuX +zv6APujS;0VA0S__Y7YkTG6tfa39S9HC +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 +zM%R5+syMvKSZb|m>S>9=*U(oBd?%w +zCzvqXh~gHV?lSfIS(cg?1Opao2(-hIA_h80)|Hkrsfl7-kRzARfr8rj7*V$)GTWXr +z1cAnBMZVr-x>Nxy$`3?`q`BInxxR+!t`l20*(`JI75ka4mWkrH4{;yD4)i4{4B9#J +z|tmzWy!z{EIGnuTd=@T^bjJ7@ff(9Q3@@eRNIiGT5J{~1^S0K9KRkMYfB +zATZoF@qh*3$6xcVn|}>>_Y?p6@%IG&(uAiqsZXku(oQ+;91Mg(JAL}0hBn-ipCR!b +ztso{uV-ysOCQc1YNy_G2kuR>i$ThpSQmK?V^vpB7c=&nt-LxNPBC4f1lH?VosV{Q! +z?57wl?`L{?54Ev5iiHtID`Ti&25X;Y%0I%TPM-Ci#X3#sA&`BCB=^KN25X6;9!VS# +z=5lEIBn)Xv^U`SwQ#n#BqbiT?hQz)mZne2|?i?=eP{?_744iIOxOx9RMhZE4afCE2 +z(f#Sje)(6Y1YwsTNU#E7fm$U`F$l00@+PvK;)HE_1z+1l+px40-ukW&T==os-vG{# +ztts=(U>x5}1_GHYSAgF?VgKT$e|hrrwL-zuOFrL9uRYNb4v7!?~s +zos{c?QRc!bCkriv*A|Nf3f5;aiWo5-zD+pjEfCr%j=XrBFMaB>?7eO;3}}CxBTzX; +z!a2z?;&_$gXFrZUQ)aBThYeHPxe%4vS&G?QvdmPG1bDISSZE_sX1;=aAbkYsG4xg& +zG*;{6cjr+!;wWKhd6iMUP7v1UMG?AC!+Ji=Mw``3C-F!L3OUZK8rIVhUUTI>O1S`` +z71BD(z{EH#e$XQfx-sL=2=FkN6{MWV3buW2Iyk# +zG`W10d|?!@bfQzdaN-b&ouE|pM8l(6NTjJYR`A$7;#lp%QcJPaHN>5W&^o+AfER{@ +zVMx2xl*D}L!a4R<156qdkU^@A}{#c72O^itj#n@Uc5Pm)6Ek(B*D#lr6bZdbrY6o>tVV`s;QH_it+Kka0MW3Rmd2(8{4b&zU%~NxR|FZr4XyowO5O +z%jZ|CGg}`$aNApc_fLNGo(CO70RI~Ne+9;UlY~-VYg5j>YPI{aSF@U5@^ +z?T`MQpPE6sj-t1%&1JkM%tb(ktWy&CZQ>+`%zO%iPkc%*%bk9_DuEajG&ni#`a +zlksh3B0597Q`zH!kHr&b1F40sQOJa1A!WRvClvoTN~DBLZn~Bz?$TU5$;EZce>!AWw`1E +z@Df{i>C^w}t^WzQjRtS=|Kf$$oj7;wY8}VE0SY#t*-4CNt@HCrzYsUo&autvb+5Ud +z_dWd6Opo1#O%2ir)MVh6OrM8r_9z?0vKDNAY_8&4jMJR$c${t;3NB?bZ`nNMu{giT +zp8MV)%E0H(Ez#~qbS@laY)cuZ%Z!fA;Q-QzmzO<0c_HM+YQmidc8PI_yj>rIrBMz% +z7v$PD^x6s+dam2jabsO8XWY&WW6!?%mR*nN`~KaZ1CN2EKtR^VxEs>HF_+Z8VFm)( +zW*GJ=3GgUY9=+uq`>VIG{{~|2|H4NO-)oMY-+QLl2+G={>p`2u=`>B-+N#hmN*mD` +ztDLof7zt7#(i${bg(wqDl;0hILyW}AP?w>g97aK60*)-jeCdU=?A*79TCFCuqd_jm +zxuYl9QfdJe%A<1>N;T1xH^$KDDW?5|*Im1lLcTznrcP_^@S`nk8Bb;fa-2J+{n4TKv!i7t;x;>ibjavrRAP` +z%T*iQ#AsFZqC{)0lyw7YD~s=W$z-i|>ZYr=J%8mJ#vUcO>QUJL5)d=ICamm#!@R}c +z8pzjUEE#492QhGnX%3CQC;X*x4ov?52X+8g{kL!Z`1Rwp*&CB2*=4LPiTt0=bf?5~ +z7)xrb)ml5PHL@!)3e~47eZ{uqx$>9Tk{(}VKi6)zYf;p$#Zf$JlBkv@N!41Lx7HSP&*mF1pD$kZ*8ACV +z#T6Z6O>h4dd%Mc_drK?pjdmwmJNnr_U4P$u|LNLzh1JScJI}+0Gr%&i4y*!;Ux$&q +fzcq@lfBSy{cjU`y&SGy;00000NkvXXu0mjf`#zPJ + +literal 14310 +zcmXY21yoy2uugDycPmm{N^yd_Q`}vP7YOd|PH`>8wLo!q*HRn`6f5rV?*HD)IX5{c +zxpy-=`|Zxo_svGBD$Agwkf4A-AaprdNp;|J21vifLD%hMrT$lZeEex|7Xe0mqFAZArC{!qp)zJmbab@utqA`6 +z61Z~|e!k$IbXNT?PvGuuzT7G514$8e!}lsR>%nURMm+~pde``@(!O=ISt0%B93;Ez +za-qRi4n0Q>zQ2#2^_y08QOl3jT*!Ir5@<8VrFx(6f9sP|H8ttjftN;wrX>jP4BcG1;MfU5x^L`zc09u!bDBt#+ll=7@ +zB;}A$BKgu}V?#qfHvm`~pt%wG2y{MOc%B!8I`p|pc +zO#?sq!Zd&j8UPmvY4RQnfo>!6{a}GFV!}g@qu<3Wu$07X(O`vikNW$~q!ngF23Ls2 +z53p8js<-B_Qd?xX6rtq43Mdz(jOg2QXx#Wng_9^1^^~KqFNq{Kvb@Ap9}bf&xFA-C +z5+#cQ`#v$A=kd0O=agATcleBaxXf_(dnqbQz|cL9R&&Ni1omTs+6~YApmk)MCghxj +z1}mq&IU>1nEiF=q=PI`%jQbyRd=hVI83Sm{E-4uTc#w;NNwEW)C(C`xvWzY_%`_MmO +zD&g-sEaE)}6(&g)y-N&rNy;5@+{M`}!{60Y8wMgF5;HmO#B~hG`W$;7xLG*yF((rq +zxP6I#r#o`B3FppK{v(q1!C+YLFSfySDcHyoW!}EfzuCB1B|C5+oP}dtocnwkcNy1EZ6#5JX4=ePl&cu~0tMnt&79+I4%PaK>VqFx;r!QdNmnxlEqdU-QR%Nmu{aWP +zJxwXvt5fFTCOVgB)Zq +z%H0U=9q7Y0lu&1kc4zYT3*lHA@XJfoK>3WFM&WWf2u6^+wCm8##D$x@Gkw+t^HoO( +z4pxDRqg;$5S=t^k22H5^V3V0Qfy%Ogl8I%LD$52=7)J>Ki9Ej1HyEi_ujELlz8$-+?cdD1Zxi02kW0 +zaY=caFq4~s^R?zxcc3Z0X|az}Aww<{P$>6rk+5Di5J7$kWor0{Q&>+DWSBH^Gf`SP +zT{4}IOFh-hB7xwBdewq%de)q6QvxorV(()2>@j8i!kj)=^hN +zl_N{$9xTHHA;V&Zx#tX&1pOO;v^NiOP#_UK@J;;lp+OOhOOO2mlMdxM;Qv-mWG+^vzox|8t`w| +z=gPlM3)y6G*hfV1WwuMe>bO-vP9g`h5BqgO9x{ROBD;aPl>XDmvt(3PUxt|4RFRpK +z5OEtRz{(Oa_W_!Z4XHf#h;Z-~71XM7wlF*L!-#h_Uy2tGuy-rAZ)4{qE~feNkp}qf +zgvBtLkFPI~I7%C=OHZfPZz$j>L9)rb;l +z@J^dxncy52;wmHg=wC3|Xn6jPYCR7xc}~D0wNjoYxmoRh_zh=6@8coM1UQIa_z*1)cZPw4v40qoZQp-uy#DLv=oP +zX9b3vzFA2r8}|_AO8W1(OMG__0{1AUD&Z%&7-(>s+Z-X6Sv}G5QguIbZ3mYa--?09 +z;wNw?n=yAag4%m#w$$-YZ{(ZJUcwHfzu&!gykNjG)e}!=q8xy2_KS=ULsQwv45NK! +zVqqD8#S{vRjg4(Q6HM_F&tihNIQns<%DVjE$cv33ET>Dvc^#{z&#u&&9RgXO?ZLuebczKv#;! +zCS|2lIa37Bp#3RWj0$V3=I2>o40{(J^LD|EUH?!2;Z&HS*>7*V%{v1)wHaUP85mcX +z%q!K}Ntr*IzJD%++btJ;VQO*OjJL1t{GvR3cy@OC-~pe^bV?N`z0QKCr?Tom)4u%A +z3mi2k&eIgh0^rGI#Di+&3lrsy-r+}zwBkDQtswtPbkj!Y^l`{f!# +zLseC0M;DiifDa!({-G4{W$Wxsgv*(NX%HMyXhArVwY105dUHg?+=@6Sy8n@slS76x +zU7%PI8ToKm#qahfR;7kn#|t@9y(0EkooWBDqA1(mpO)>BBz))giBi8xVHlj#dR9U8 +zRo%`iBdlj8%_tRn^qa%T>{nsLLwTNld&WHLyfbPzv2W62m6q=Nsdxnk +z#{P==5!Lidx3bcr_qlUl%BX!xjywA?jv>FU^mJDa0zQT9Kw8RRHq>7B +zb~DXw0(oqBrOQunsm2ghWV2i1VmN{F?)U;0%*j{FEUxazAJ3)KSWomuhklkDi?5h*MTLDS5ma_Nk1sNZYzZ#$maGRyiXBzjG@(G__fuyBl(^A>s&{jF+J%5| +zv#7nD1XK806#_U_4#N2ANAxznk%;U$Y$z#{K*O07mADqx6LjACqwP<`HFV#C6Q*wx +z8JVP_qGF}V7B?^8)f*2F5AON7v$L~Kr?2}oPai_kG!_6MI(U`LS~+Mo*CSyrw>pPE +zllqxy +z^&rnDn4XA@AUY7~`1lwTCrm8KlVRqX&!kZFH&;i9@=R}UDxNSh*)Iq2U+#9}@ag1t +z%KUOEw0DXT)>hQoLTprY^z=BC=8NAyi3pZWT7A`?;rI<3%65Nqb93%pJ=!+dNtB>W +z7f3O-e-S7ZBgBntcyt~wOG_p$AU2zlGH8=%TEm+z8kLYReEMTkIo#2YiA=iKWrH); +zS%uT3xAyyY=!U)0Evpgx{{38MPR2nN<3913M<0O#YCO=TSt^4IzV3^D%2zC>t_OO} +z_h~AVOk+IIi$Ov;-g93a4j@WaekCC#HFm2_Vu9s)8-GbYtr{LgrxnSIN^PW9)!jYX +z?%-yssA~&R3F)C)wj5i|@!atCx?Qy%P1QEGSZm;iUNai`-F(8a%y+_a>CMzx$XEKx +z>sW|JbN36s+Y{4SZsrspH%UH=+Q6J`c&_-JLGL&5|$XUA1vFOC+rgoc&xT{dFT&pMaEBKwyD;plX0>2nla;jTlQ{!fn2M=Ak*=K*g% +zBm0-$ly1~}CT-5gv){jex9)7&b8u!a+vYHXU>=NF2>g3+_rN{(LUMGwRWKk49sS$v +zazyX8zZ1hwZ|U*5{fK@i@hRl*U%Q2cg+!iIfb)6W%S5F{91qinEZE%~4Gl>rBw9S< +zMP5$exl1jESyt}d~jo?hf`z^32b!}UGtJH+w9(0UrI#~Ei*ii&6z(AVE?(}k_A +zE9Z@mj7HF-ch46I0ipe3gapRj{=zk_J1E^b_JwdrhKi4ytBuwP)m>e$@9v`A{1N{h +zwUN6H=_W+h(a?rGaQ%%LP5C4)XiZ*`1uUwgqWvk`LyDD!Ps#Q5oI($KDJ%8n5kBi- +zghsLx`~mf<>WT)6-cJBbp|htk1NfkZ@e#B4@l?UH7!MDMpO?1NETGk_Eg{z!N3!D< +zWg8gtgS%b(0Bg7dw9u35xq)1vNdnM8iu7Eje*u?#sZ~%^q*HDaZC?5z4ZzhSA%ndS +z4&$M&7(|(9nWY%QShCnuN0 +z`n9&UeypypUgx;R+x;XM#8uDM{p`9~j<49)^dotHJVO*A@HL&g7F={FP#trj@{dzm +zeQUiqRWJ&pkKkA1O-|vOf8O1UQ$$0lIExffio|}F@ROV#MXcPH$ +z?$$kxAF@B#KT}u;R@SVyIO>1sw1!i?C(_013w9@?8$bKaLQi34zC$g*^}F&(%NEO6 +zQzD-^6}HQMnGJ{h$J*)HjSxjblWegsW&rLC8Ov_r_20jLjUS$Ptnm|p9fK%r0j+4; +z57^mjL&lISh8>DC;eB$B69$h4XxE3qU4T&zUpDeV@4g>or%D-x@qhie>6mqD959ck74(h?S0BA0}YQ18d?hr6}%}y{%ZNJ^-(?=Op~; +z#2-UNh)jH9>RXmvPJ(Y!8(uhyW|sFpyvv)AaNeljHj^Fx+RC +z!`@c->W1C^FUKHmG2w_atkdsMnzY+l!CV8havQ8-Gu)<8t{#V*2Pwp4h?ayXsi5Z> +zo!guta>TA~iv#iJpQkN>#)QF%As@2WgU&V_Y^qm#E*O}M_ijJfFWq}ts)-l4>D)kCqJJ@MG2$69ph0jzwI8ry1u8D@CyinC$oT?7S*Z}Eg +zYs}PWLqr4u@)w}#!{cMx;KxO6W2H6~3k$laJjAt+C{0mmCRnfs=OJYbh}HMh&e`#> +zj;jrpjqKCh41OK{FOS`@_sPP$iCm46G^EMNk8(l-1f>!gEV+4vMVRZ#8infUenP+k +zL^tBOHF^=)k&U-Tw{gfijqQ&^ +z-RHHII5yp}2|o8pTsf6x7$teW9Em!~iy2DN?D@|U)g%I6VG%JBO$|~;c~1Q^3|x`1 +z6HRbq1#~Ke)wWpALcc&@P;m+*sGavR0{aOx3=IwUE3YPWAwV45pzD$~02inxi7(6X +z$zk683M=_r#M*+6fQ)&FK0y|lm7JLwS)K=t&ZJk!U_-y%_o@fhr{s37MUEQOF*M)3 +zB$;4>Zx;Xk*(hwFjb>1iJ1f*D#nyWL{=>{2|9*^vCNN!%bF8Oe<`xz#s;jFz?;I}4M3lL;!fy_;J-E96Of+;sG%K=fZdR)99pJ}fM( +zq%(s8UrsEL{NrdF`!#RY+VjFyPpE_vtqPMM!MQ+QnE)+_g9Z^{4^;k&Sa^=w*yuxB_*Z!U%!3{_9Qr)Jfz4IeS#io4oj_Kqhq`HCUub|Ke!v$1-$v=kc+O#rlCej?%dhY +zxxKUTsFPG1nfoFp3%7@gh9S?vM0N27#*fpJyaX;Vy{!pt*}!9_mX9uC#J5RyjknW2Dm3dCvZYU +zSW?0kvI9!o2un}*%`AYhr^CQT1aZF=-Nt^atn@Kt%b2!hT(pK!|MclbBv3-<+6{>_ +z8toMfWc9rpOk(8|KW>Z-k>Fr(xc_+q9ocf`8!_n}XYUrW?Ax|*_|=5m*4F0V+46wJ +z1IGS^Z5t=0Zj86J2MfJc +zUq#WKCfhoB<;P2&&`*_G4^_0uqDR20m!>T8ay_rxSzA&9_v5##g6tzXTkx+KRfz32 +z9vvpp?+YxHTxDthCBu7)&Q052y4s9*$M4_2w-OdPyK?F-EBoUuSsIk@@(!gA*A_!0 +z2eu1y;-Q$Ut(M>8FCOtw?vZR-%*ly^x)<95vK@P0tJoZws@+M*NGhg_NU`!}DZnWBHQz%*@6))$BWN;EM0xAF+B4Mph#S??J?K+&viwPmes*n^HGDL9iBf +zCk|mDu46wwughN!isu&G((DO>Ws`(VLY?^#w=RONxUgFGby--Y=5NJ|(>qXOS`;lZhmXyMEyBdVM@jJh71E-})~`?t4w8^Kwy) +z<+KACjs!F^TS-;FT24_iWF+=l(nR}j7U#;Vd +z)IT3=b&}A}1PUKFa6DKfgHkJci!~7u?a%k9h7Rri^{y`|;;xNDoQbV}+oJ=LdApL}|77o@C= +z;~aed)XpbrMtt1x3gHPWxbliQH4nKBCew{9 +z*-_PTyn~`1VrwKcc4ZrhI^!MsZ{D0O0%O2!SHHi^Dfyr9*x*DGFKwc()b;q6nM*M7 +zvA$x_?$BMJJHN5HIn9Ps{_7-sn79~BZegaa5V;s(BA<5BnU?^AeJHXtd)cIj_UCjA +zW|N@MjV~vrJz{sE0Dzv}tXxUDQAXm)1(kX7C_ZVFX%!TlZ850i(P1A0BxaJu)#LcH +zoxMFRzxoxw$bM=B6gpuMD#vcsa^00?%=D+T9-dQqV*=zD|)W!3BLun2&^n)~$ +z2_^{i9~sGXOAsF_S=k&4mWJ@`mD+G%MiPTlhuomboeFNwHb(< +zVpVR!mwf;JmpO3JL|B%L-!;@7TG}+`HZA;-{VIlQGY|T=f|!9!S=!c?sq5|KeEQ*~ +zm!1xeZcJPbSsfjU9e>K|=Ni<+YgrIG!|5@|Z>4bjx+`1j^O-{QK8XARf +zUG$nLRiTEtt;)9F30rvw>nj)@vCF{$d7>o2n>}~Y2^^C79l@s`uXRZOcuy>^%2@t- +zRGv={pKlDXFUgvG_^DWGR==il1rIzn{$p4r(FVOQxZi!_*Ksfl2hR{Aj>01RbFAM= +zpr0wzMwlOwlkt4|JLK)$>VL+{4nv>^`yMa)T;(9f*B(9;{T+)_=M4dN>M&&hS-#(G +z)-sW(WxVkHR)`x#g)25Lu7qnN;~Q-bvKDZ=;^fyLy@okDpvt&ZU{!U)WVtmnp +zAN-CzM{jPFWep9NAKDDq@=kynkGi_GQ@Z2y_Wn)xc_q3-&+9`qdGy_{PF-2c^$)%x +zd0sonEJhtG*2|P*Q-f_3`Akk96HzBz2 +z!5tnJaCcA2hGQrSw*{F)epvfYX?7toP=O0dN +zizY2w`>O@4Vqff!dBhQ^><#TjMP}loM9ProiD-Og@$V=*zQ|Avg0D!+96lr^u(1fl +z3J52PHoJYDdvdiIW?q?JIC*r?88VruLx#bp0lys39v$(c6uC*j}2IFFh +zViOX|K+DH18cd9%Rgjs$*sXuoW<>p^Fv-7CV|zpgTUnj812pyyX-nhA4TZ^UyYY9; +z?}BOarTT1q;0xSTjV_DPWE11?Y2+wSA*ybzebDoy8JwhznKa6SvYxE$WswX7Z6pG$ +zsA2GgHFFL3^zA@XTYK{a+6$Q8di%@1-|q9U15y+~R-L7Kwx8*xr(FP{g*JDPa`e((jSl#~?Rx=3ne(nLfeP9k0grubJK +zU4euzZqt~$Cl%k^{-!e6YQZi|D3#+MUS}VsYZ)0S>y@)kyqRI?A_esvAu-{`1Uq@! +zC+b`wnMK&<_mitl+k@e*$*{&S>vayX*>D>Q5sw2FZ?l(8ff%(8lo<^mBMrwQXOXe+ +z*7sZdWzBTIwZO$y^F)qZL1XbOMY<@M_a56y{({Vg@YN<_y}toq41V%~w=+4ZQvg)X +zVw~l$z-sId^nKU%dlk7W(mG}eS&KV2BdYqNJnX-p=YrG&&`_m0fzA_|iKD${5?oL* +zdS$heR@%Q+(3!!T&k;tIN|v2j=UI))rgkvyC7MTTrKP3g>Fma@_R0`GE5(tL%sS$7 +zG41ag%(Y(xZ5cjlk=R~(3XC+$25r*Fo=G5OhGgR}i!nDoG?^sult?Eo*x$x6CH-3L@LtZ0dfq!Bbbw-S}RwlN%lpH8c=4l2qH +z1wRszHSPh~=esnWvXD8B{D4<}?}6cA+@Ob1760Is6`g!zl@WL(L&={LA}SxAt0>Tw +z%b7i^&yNKM;(vGcNwuxAK{g|S3Y1&pH_6U1G +z3M4zx5FU=O;=l_?VzQ-~bx~xN1axPgYI0am3d25BjYmfSTX7Q}==Vcryl6@Se0(Jv +zxKW_o%H`jdnC7QXlkFbCsACHN1Dx=0gf<~@PW-&<=`1Hd)@#ypH7%OpalDj-P=ts+3^~yWs~TV}BD20HjkW6zc1L +z0#HzMkn3JV%7N-18_@tgE82*YnmEzxirriDSx#_|<|q1vL{k}7>^mRzO(ueTSN2~H +zG}kxp)Qn!&)><3|e>62+GXSpQKcemfqU!&BHZ5Ca;DT<63bBM&uV1BDS?MM$M;x8w>gShAPMxJM^BbMZn}Unm{OC9^4x3%% +zlmX8!km-u$N4fQXQ>jRe`7)3+RFGjhz +z18zf(Fo2<>YV^7LJO^UTZ2Ivd#mpN}o?7pBV&q=f%ID>haV7M8R3jsF*@a%iwIy>| +zsZ!-y{!%&j7`B?W8TcF4NH-RHH1xZ{;7BsA<#APu!;cND)te)FhoXz$BIU}2&^7WP +zT}TX>ZO58$VNPuh6JV7~s(W$vAj`^%AtUamex3YdVl3~4+pqk?G)qUibNMrj0*M25 +zY>5Ac|Dnv6xBQmV#$3JA?&HTN(lYl~J}@$l{*TY^kORrCB)3dDO}^^v!dcLf^CHty +zanjllIQeSLmpuG+h&ae`r*v!C*0A&W^a&q>93?BAXzG7n +z2*3TGPIcN`-_hY9&oaiv#fiv~>}7`T`4=pInEqWX*3e8+yPm^9h-tr&ts55$l+388 +zW)~F}2JH!}VLbQ>?6~H@&k`MnSsTeVj0TRVP4jGbP*!!CwM6`Z11c)yI2w$+R0zxo +zT|obYS1&&`{>>Z9(jnVU&=yI*%PGe*f78ie*_9oap?sd7fx7{r^WT>=XHF +zl`f{=UJEn2?tRw`Fem?eRE6#*nOes(ebRcmaK3~a3{a3EyE1zXSF0p7I_iDJ&%;3V +zU;AS}e?*mH#Yh2P9E3QBigIqu2iXf=@t)2+I~f*_E^JtEP1@IR{CBfTj%T}E3e#n% +zUa{@vU?D$l4DEANwkkK@ruP4ta)E*e^KLGg%$PizyPmHvKNMWtuJQ6sPXY=(1m#>W +z7V?9E!Vj}>a|KfQx5ESpH+q6$@gAp-P#~lbz`aj1_?xinN>3o8b2-Z3w>UZ3QZ}W0 +zWg-!>p>AADDcU^4;0*L4UFgB0QLlXd^y1E&4>txV!T|!`RwjZGl`;-4ZgFf>luHIy +zZ8d8Rh{I3r!g-ht6mAZxMB6VxRqnA0UY`h|mJZy2 +z17BazT$jMKFL3J6Ue_HL1^)4s%$Jj~Qx~1HG#tS@kwL(KP_ZI3dWz0SH(sqj#-*TNGsIWqPj>cj?!GyWvfdEiNOu4$>MIqL=F&Cc0{g*~L5 +zA1wt)=_zMFUkCT5$l!G{1-Y9QtGQ#qm5E(3fYPms_EP*sSVI)bfXN|uNO`BqVuCvd +zv)z8IGRgtM1<_trndVhQ^xA)wn~*W~#d*X@E=W)jcQWI8+?kdzHe;DZ`%+JE%gE}m +z6H=FO8rJxM{N90S=Gi!Mel)TyanxPa;E}C?hJl@e9UWad->;S|v;axgFjrY$z3(rV{MiJ}3M)t;Q?P5wZy0e3G{dcDO7n}3slDXLMrB$;#*W@Qv)D$=?Xs$F(8eTcyGIQ~IWgD%Gn&E>F9y#o>cR-7spE;Rur<_E~Pu)e0I +z#&y1|@8D~8c55<|KMf;&x;hg!A%VOZ38_+uk`jH4#=b9M&xcpxV-7cMN{jXVRnKSe +zlKJJ%=VBV{$DNeI1QkiA;DfdVT?$;O#22z6v6bTK9)fjrfIh!Hq__l~KzuNqT{&kA +zKs@YV6^1ZLGjTgR%(=NHS-DvWnnP)NM#qbHINqmQdCE5??co$3nuikqgm=s7*#Kd*+j_weKrZjMeLeHEoiJm>zuDRU` +zh~ggr^knneWU!Nn}AQt=0Id6Hk; +z4bJqse|V$H`stT?NS0yreYvaZ9YF!fw+N}{3#yXRU!C7?exl35BDC%+!jDMGT^DN# +zN9FGd#5t#;$h}5UgQ?q-Gr15>C6=nLUszle9<+_!!oi_m@_L^-R>_Qty7_g|C%m|5 +z-7^5X5V_ARi?h9_LW%2vByD3X_IvUktqBv{%SYXO1&;e&O#Ll_cfC`Wv1u+l_#RI< +zQ5Kly0;P`%TXaQN(heOg~>V&L{d+ZDA%eq-UKo#1)$rkjSm=nzAE2r +z5--RyKhxfXoGVU3^ab{5XGlyL1+26foG)4HZvN +zG@&I3h0fnK5lIjcrg*XxPy1(gK3_TN`&VYnxP;C|j$~0rT$0f|*#=OzM^NbE-1T5D +z%Csnt)n!sx3N#b(8G&+G3W~Q_B#StA6jZZ=p#wuu`DrAMXm{T@#S;ku4Dme@{Njmk +zCtrh3z6O>o)~o{&Htx+6kn*)$NNBH-biu^aYtWUq +z(G>4rCEKr#tO>!x8A@%W@6g)Xs%2Hq!y#Mbb@9R2@GDWi&!{jhZvzQ1D9nMuPoOS+ +z+cj{9nx5X{jJOIavbFf)Kz5Jnbe5Bu#(XE-z$j&iaP%c9W59OoT0~|N#D*(N2kz={ +zs(|)nH!_+_g1)#ZH2xk>ZTG#6WN#qa3BxZM{NWxq`*#$H255k6Ky?hw*hSA6`c_fl +zT@Ua%E5Ez3;~`kQFmrC#$Nlvc_Uy3#yzhd-6UYuuIwgIBZZC-`dwOBJbfurL(FfhH +z{YkjE+9OrOveY`{t{sGw&51YO1@{iO4)Ki=!Z5#q=m_Hi)_j0`>?;t2j);vv%BUif +z;wpTZdLQLsGvZ()DCdxYudn^Pt;BZ}Rin$4F8h{R`HxT2z`uc&aMXIQOvwgA5%{&) +zFW52MiN!$!EXgx}Px~e1!EMp;#&kY65oDho95j~!qD%YJr`+aK4jCJ4UJ^;q>w@Lf +zvDfg|M`S^@DGxu+7aR3Cx#;%?advj&1~L-m +zJqCP9&TW3migV*`Z$#)Qa>3>Jf)g9D6Ki28P@iX(uso)hic8Dp1F< +zeF;(n8Po8A*~^T{De(J)Z2nqLl@Vv3yoSlGwq0aeOg4ymI(KIkTeur-=J-yp9z?qe)it6gq-wl@I +z0D-_I{|T<5kwD9uH3yf1GWXp5*8eOgJf*q0IRoK|+r{}Fug&0WpNDKMTC@(Xc)9K8 +zy`lByMn!1fnY)1KYP(0Je1)c~WilUuh<&Q8^OE?L9Q^xK*Y@M$`6D6TDCZ^@l8{|} +zxmmNw)mng$hYBii+&ZqedxWT0dnV#LG4zC%+kzcK+-??vEHT>Q-T8zu|s_1IbA#OV)^+1pg1OmmZn` + diff --git a/patches/server/0003-Time-Utilities.patch b/patches/server/0003-Time-Utilities.patch new file mode 100644 index 0000000..df0c908 --- /dev/null +++ b/patches/server/0003-Time-Utilities.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: YatopiaMC +Date: Fri, 23 Oct 2020 09:20:01 -0700 +Subject: [PATCH] Time Utilities + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/org/yatopiamc/yatopia/server/util/TimeUtils.java b/src/main/java/org/yatopiamc/yatopia/server/util/TimeUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bb023bcb4b1e1ab5261c83358ce0951cc35ba16d +--- /dev/null ++++ b/src/main/java/org/yatopiamc/yatopia/server/util/TimeUtils.java +@@ -0,0 +1,27 @@ ++package org.yatopiamc.yatopia.server.util; ++ ++import java.util.concurrent.TimeUnit; ++ ++public class TimeUtils { ++ ++ public static String getFriendlyName(TimeUnit unit) { ++ switch (unit) { ++ case NANOSECONDS: ++ return "ns"; ++ case MILLISECONDS: ++ return "ms"; ++ case MICROSECONDS: ++ return "micros"; ++ case SECONDS: ++ return "s"; ++ case MINUTES: ++ return "m"; ++ case DAYS: ++ return "d"; ++ case HOURS: ++ return "h"; ++ default: ++ throw new AssertionError(); ++ } ++ } ++} +\ No newline at end of file diff --git a/patches/server/0004-Swaps-the-predicate-order-of-collision.patch b/patches/server/0004-Swaps-the-predicate-order-of-collision.patch new file mode 100644 index 0000000..44a4a41 --- /dev/null +++ b/patches/server/0004-Swaps-the-predicate-order-of-collision.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=E3=84=97=E3=84=A0=CB=8B=20=E3=84=91=E3=84=A7=CB=8A?= + +Date: Fri, 10 Apr 2020 15:47:15 +0800 +Subject: [PATCH] Swaps the predicate order of collision + +Original code by Akarin-project, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/Akarin-project/Akarin + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c9e23c09bc9db25a813e703817d51e1763b0b3b6..38cb136d34cbc83dde1c766196f60a30d38005e3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1869,37 +1869,35 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public void playerTouch(Player player) {} + + public void push(Entity entity) { +- if (!this.isPassengerOfSameVehicle(entity)) { +- if (!entity.noPhysics && !this.noPhysics) { +- if (this.level.paperConfig.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper +- double d0 = entity.getX() - this.getX(); +- double d1 = entity.getZ() - this.getZ(); +- double d2 = Mth.absMax(d0, d1); +- +- if (d2 >= 0.009999999776482582D) { +- d2 = Math.sqrt(d2); +- d0 /= d2; +- d1 /= d2; +- double d3 = 1.0D / d2; +- +- if (d3 > 1.0D) { +- d3 = 1.0D; +- } +- +- d0 *= d3; +- d1 *= d3; +- d0 *= 0.05000000074505806D; +- d1 *= 0.05000000074505806D; +- if (!this.isVehicle()) { +- this.push(-d0, 0.0D, -d1); +- } ++ if (!entity.noPhysics && !this.noPhysics && !this.isPassengerOfSameVehicle(entity)) { // Akarin - Swap the predicate order of collision ++ if (this.level.paperConfig.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper ++ double d0 = entity.getX() - this.getX(); ++ double d1 = entity.getZ() - this.getZ(); ++ double d2 = Mth.absMax(d0, d1); ++ ++ if (d2 >= 0.009999999776482582D) { ++ d2 = Math.sqrt(d2); ++ d0 /= d2; ++ d1 /= d2; ++ double d3 = 1.0D / d2; ++ ++ if (d3 > 1.0D) { ++ d3 = 1.0D; ++ } + +- if (!entity.isVehicle()) { +- entity.push(d0, 0.0D, d1); +- } ++ d0 *= d3; ++ d1 *= d3; ++ d0 *= 0.05000000074505806D; ++ d1 *= 0.05000000074505806D; ++ if (!this.isVehicle()) { ++ this.push(-d0, 0.0D, -d1); + } + ++ if (!entity.isVehicle()) { ++ entity.push(d0, 0.0D, d1); ++ } + } ++ + } + } + diff --git a/patches/server/0005-reduce-allocs.patch b/patches/server/0005-reduce-allocs.patch new file mode 100644 index 0000000..3b5587b --- /dev/null +++ b/patches/server/0005-reduce-allocs.patch @@ -0,0 +1,624 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Tue, 9 Nov 2021 16:53:39 -0500 +Subject: [PATCH] reduce allocs + +This patch includes code from the lithium project under the GNU Lesser General Public License v3.0: https://github.com/CaffeineMC/lithium-fabric/ but also original code created by your's truly + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 45d1807b31f5acd5f08f729701cec4b464ad9398..e85175910454fc08704ea01e4b852136a6c6c60f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -214,7 +214,7 @@ public class PaperCommand extends Command { + case "version": + Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (ver != null) { +- ver.execute(sender, commandLabel, new String[0]); ++ ver.execute(sender, commandLabel, me.titaniumtown.Constants.EMPTY_string_arr); // JettPack + break; + } + // else - fall through to default +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +index 0133ea6feb1ab88f021f66855669f58367e7420b..cd5499e750764eaa5e361e73eb581bfce7f9f7c1 100644 +--- a/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/EntityList.java +@@ -17,9 +17,9 @@ public final class EntityList implements Iterable { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + +- protected static final Entity[] EMPTY_LIST = new Entity[0]; ++ // protected static final Entity[] EMPTY_LIST = new Entity[0]; // JettPack + +- protected Entity[] entities = EMPTY_LIST; ++ protected Entity[] entities = me.titaniumtown.Constants.EMPTY_entity_arr; // JettPack + protected int count; + + public int size() { +diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +index 277cfd9d1e8fff5d9b5e534b75c3c5162d58b0b7..52ec153be9d7177102dd137dfc356e29173f62bd 100644 +--- a/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java ++++ b/src/main/java/com/destroystokyo/paper/util/maplist/IBlockDataList.java +@@ -20,9 +20,7 @@ public final class IBlockDataList { + this.map.defaultReturnValue(Long.MAX_VALUE); + } + +- private static final long[] EMPTY_LIST = new long[0]; +- +- private long[] byIndex = EMPTY_LIST; ++ private long[] byIndex = me.titaniumtown.Constants.EMPTY_long_arr; // JettPack + private int size; + + public static int getLocationKey(final int x, final int y, final int z) { +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +index 47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749..a919e8a2aa10ba01d7f389985591a0681c1b4426 100644 +--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -63,7 +63,7 @@ public final class ChunkEntitySlices { + } + } + +- return ret.toArray(new org.bukkit.entity.Entity[0]); ++ return ret.toArray(me.titaniumtown.Constants.EMPTY_bukkit_entity_arr); // JettPack + } + // Paper end - optimise CraftChunk#getEntities + +@@ -189,7 +189,7 @@ public final class ChunkEntitySlices { + + protected static final class BasicEntityList { + +- protected static final Entity[] EMPTY = new Entity[0]; ++ // protected static final Entity[] EMPTY = new Entity[0]; // JettPack + protected static final int DEFAULT_CAPACITY = 4; + + protected E[] storage; +@@ -200,7 +200,7 @@ public final class ChunkEntitySlices { + } + + public BasicEntityList(final int cap) { +- this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); ++ this.storage = (E[])(cap <= 0 ? me.titaniumtown.Constants.EMPTY_entity_arr : new Entity[cap]); // JettPack + } + + public boolean isEmpty() { +@@ -212,7 +212,7 @@ public final class ChunkEntitySlices { + } + + private void resize() { +- if (this.storage == EMPTY) { ++ if (this.storage == me.titaniumtown.Constants.EMPTY_entity_arr) { // JettPack + this.storage = (E[])new Entity[DEFAULT_CAPACITY]; + } else { + this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); +diff --git a/src/main/java/me/titaniumtown/Constants.java b/src/main/java/me/titaniumtown/Constants.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1a39467fcbb849c79bea21a020b4270a2850e02c +--- /dev/null ++++ b/src/main/java/me/titaniumtown/Constants.java +@@ -0,0 +1,14 @@ ++package me.titaniumtown; ++ ++import net.minecraft.core.BlockPos; ++ ++public final class Constants { ++ public static final Object[] EMPTY_object_arr = new Object[0]; ++ public static final int[] EMPTY_int_arr = new int[0]; ++ public static final int[] ZERO_int_arr = new int[]{0}; ++ public static final byte[] EMPTY_byte_arr = new byte[0]; ++ public static final String[] EMPTY_string_arr = new String[0]; ++ public static final long[] EMPTY_long_arr = new long[0]; ++ public static final org.bukkit.entity.Entity[] EMPTY_bukkit_entity_arr = new org.bukkit.entity.Entity[0]; ++ public static final net.minecraft.world.entity.Entity[] EMPTY_entity_arr = new net.minecraft.world.entity.Entity[0]; ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/nbt/ByteArrayTag.java b/src/main/java/net/minecraft/nbt/ByteArrayTag.java +index 3dd8a189c26f41759c59c3b9d0e5282038989a9f..147beddf8ef369cdae4b1ed2dcd0d7b2fd865315 100644 +--- a/src/main/java/net/minecraft/nbt/ByteArrayTag.java ++++ b/src/main/java/net/minecraft/nbt/ByteArrayTag.java +@@ -170,7 +170,7 @@ public class ByteArrayTag extends CollectionTag { + } + + public void clear() { +- this.data = new byte[0]; ++ this.data = me.titaniumtown.Constants.EMPTY_byte_arr; // JettPack + } + + @Override +diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index 29f6fc5f83042713704b83f3b5257eb8b9b747f2..0211e70eede03cbe6cdc657e2265c5a1683b7b6c 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -364,7 +364,7 @@ public class CompoundTag implements Tag { + throw new ReportedException(this.createReport(key, ByteArrayTag.TYPE, var3)); + } + +- return new byte[0]; ++ return me.titaniumtown.Constants.EMPTY_byte_arr; // JettPack + } + + public int[] getIntArray(String key) { +@@ -376,7 +376,7 @@ public class CompoundTag implements Tag { + throw new ReportedException(this.createReport(key, IntArrayTag.TYPE, var3)); + } + +- return new int[0]; ++ return me.titaniumtown.Constants.EMPTY_int_arr; // JettPack + } + + public long[] getLongArray(String key) { +@@ -388,7 +388,7 @@ public class CompoundTag implements Tag { + throw new ReportedException(this.createReport(key, LongArrayTag.TYPE, var3)); + } + +- return new long[0]; ++ return me.titaniumtown.Constants.EMPTY_long_arr; // JettPack + } + + public CompoundTag getCompound(String key) { +diff --git a/src/main/java/net/minecraft/nbt/IntArrayTag.java b/src/main/java/net/minecraft/nbt/IntArrayTag.java +index a14b01cee7a8d7022c4fa7264d349a76be143ba5..44f441d6c102fa5bd50071cae991a8a6ba0ec713 100644 +--- a/src/main/java/net/minecraft/nbt/IntArrayTag.java ++++ b/src/main/java/net/minecraft/nbt/IntArrayTag.java +@@ -184,7 +184,7 @@ public class IntArrayTag extends CollectionTag { + } + + public void clear() { +- this.data = new int[0]; ++ this.data = me.titaniumtown.Constants.EMPTY_int_arr; // JettPack + } + + @Override +diff --git a/src/main/java/net/minecraft/nbt/ListTag.java b/src/main/java/net/minecraft/nbt/ListTag.java +index 17a37d453b3d6cdb838dd7e8071bb9ad34cd2c20..1e2aca86f30c43f25aea7f18b67b4f6ddbd4c2cf 100644 +--- a/src/main/java/net/minecraft/nbt/ListTag.java ++++ b/src/main/java/net/minecraft/nbt/ListTag.java +@@ -221,7 +221,7 @@ public class ListTag extends CollectionTag { + } + } + +- return new int[0]; ++ return me.titaniumtown.Constants.EMPTY_int_arr; // JettPack + } + + public long[] getLongArray(int index) { +@@ -232,7 +232,7 @@ public class ListTag extends CollectionTag { + } + } + +- return new long[0]; ++ return me.titaniumtown.Constants.EMPTY_long_arr; // JettPack + } + + public double getDouble(int index) { +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 9bbf990212ee55a267d0eb1e863618c50fa706da..b9b93606b5771de854cd0e1af0001cc8cf6aca2f 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -316,7 +316,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + public void setListener(PacketListener listener) { +- Validate.notNull(listener, "packetListener", new Object[0]); ++ Validate.notNull(listener, "packetListener", me.titaniumtown.Constants.EMPTY_object_arr); // JettPack + this.packetListener = listener; + } + // Paper start +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 1e0d261439255091a6f61485c0747231fbd5b1db..490f5d86df7fff6537c7080361a7df80951647df 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -80,7 +80,7 @@ public class Main { + OptionSpec optionspec6 = optionparser.accepts("safeMode", "Loads level with vanilla datapack only"); + OptionSpec optionspec7 = optionparser.accepts("help").forHelp(); + OptionSpec optionspec8 = optionparser.accepts("singleplayer").withRequiredArg(); +- OptionSpec optionspec9 = optionparser.accepts("universe").withRequiredArg().defaultsTo(".", new String[0]); ++ OptionSpec optionspec9 = optionparser.accepts("universe").withRequiredArg().defaultsTo(".", me.titaniumtown.Constants.EMPTY_string_arr); // JettPack + OptionSpec optionspec10 = optionparser.accepts("world").withRequiredArg(); + OptionSpec optionspec11 = optionparser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1, new Integer[0]); + OptionSpec optionspec12 = optionparser.accepts("serverId").withRequiredArg(); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7ba86703433322e2b166cc263cedd6e7310d36a4..26841729126b022f75e55e584fe850771dedd4cd 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1412,8 +1412,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> list = Lists.newArrayList(); +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack + int i = aenumitemslot.length; + + for (int j = 0; j < i; ++j) { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 93c1f9e3e9d62cc993fedb93fe43cbeed7d39542..da6156f1d61ca20fd39f35da36c64afe62b2731b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -851,7 +851,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); + BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); + +- iblockdata.randomTick(this, blockposition2, this.randomTickRandom); ++ iblockdata.randomTick(this, blockposition2.immutable(), this.randomTickRandom); // JettPack - reduce allocs + // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). + // TODO CHECK ON UPDATE + } +@@ -1106,7 +1106,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + public static List getCurrentlyTickingEntities() { + Entity ticking = currentlyTickingEntity.get(); +- List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); ++ List ret = java.util.Arrays.asList(ticking == null ? me.titaniumtown.Constants.EMPTY_entity_arr : new Entity[] { ticking }); // JettPack + + return ret; + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 676f2cd4b152984aa76903fcf4377c745775bfc8..f0e7c7c84052f29349b60ad06ec4f9ee1771f4bb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -359,7 +359,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + if (this.keepAlivePending) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info +- this.disconnect(new TranslatableComponent("disconnect.timeout", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause ++ this.disconnect(new TranslatableComponent("disconnect.timeout", me.titaniumtown.Constants.EMPTY_object_arr), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause // JettPack + } + } else { + if (elapsedTime >= 15000L) { // 15 seconds +@@ -799,13 +799,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start + if (this.chatSpamTickCount.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable +- server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", me.titaniumtown.Constants.EMPTY_object_arr), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // JettPack + return; + } + // Paper start + String str = packet.getCommand(); int index = -1; + if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { +- server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", me.titaniumtown.Constants.EMPTY_object_arr), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // JettPack + return; + } + // Paper end +@@ -2952,7 +2952,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + // Paper start + if (!org.bukkit.Bukkit.isPrimaryThread()) { + if (recipeSpamPackets.addAndGet(com.destroystokyo.paper.PaperConfig.autoRecipeIncrement) > com.destroystokyo.paper.PaperConfig.autoRecipeLimit) { +- server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause ++ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", me.titaniumtown.Constants.EMPTY_object_arr), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // JettPack + return; + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index befcb501b4b1b6330bf3cd53e00e30b01efade6f..0c4e34c02060c00bdbed6388989660ec023b4d1c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -249,9 +249,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + + @Override + public void handleHello(ServerboundHelloPacket packet) { +- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); ++ Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", me.titaniumtown.Constants.EMPTY_object_arr); // JettPack + this.gameProfile = packet.getGameProfile(); +- Validate.validState(ServerLoginPacketListenerImpl.isValidUsername(this.gameProfile.getName()), "Invalid characters in username", new Object[0]); ++ Validate.validState(ServerLoginPacketListenerImpl.isValidUsername(this.gameProfile.getName()), "Invalid characters in username", me.titaniumtown.Constants.EMPTY_object_arr); // JettPack + // Paper start - validate usernames + if (com.destroystokyo.paper.PaperConfig.isProxyOnlineMode() && com.destroystokyo.paper.PaperConfig.performUsernameValidation) { + if (!this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation && !validateUsername(this.gameProfile.getName())) { +@@ -300,7 +300,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + + @Override + public void handleKey(ServerboundKeyPacket packet) { +- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.KEY, "Unexpected key packet", new Object[0]); ++ Validate.validState(this.state == ServerLoginPacketListenerImpl.State.KEY, "Unexpected key packet", me.titaniumtown.Constants.EMPTY_object_arr); // JettPack + PrivateKey privatekey = this.server.getKeyPair().getPrivate(); + + final String s; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 624946f48f0811c94d05174f28d0b1fc6d5e6a00..49e27efb5fdf26f6a8026c8c17e25d87026ab45d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -316,7 +316,7 @@ public abstract class PlayerList { + } + + void disconnectPendingPlayer(ServerPlayer entityplayer) { +- TranslatableComponent msg = new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0]); ++ TranslatableComponent msg = new TranslatableComponent("multiplayer.disconnect.duplicate_login", me.titaniumtown.Constants.EMPTY_object_arr); // JettPack + entityplayer.networkManager.send(new ClientboundDisconnectPacket(msg), (future) -> { + entityplayer.networkManager.disconnect(msg); + entityplayer.networkManager = null; +@@ -731,7 +731,7 @@ public abstract class PlayerList { + while (iterator.hasNext()) { + entityplayer = (ServerPlayer) iterator.next(); + this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved +- entityplayer.connection.disconnect(new TranslatableComponent("multiplayer.disconnect.duplicate_login", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause ++ entityplayer.connection.disconnect(new TranslatableComponent("multiplayer.disconnect.duplicate_login", me.titaniumtown.Constants.EMPTY_object_arr), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause // JettPack + } + + // Instead of kicking then returning, we need to store the kick reason +diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java +index 4fd709a550bf8da1e996894a1ca6b91206c31e9e..63c0fe30ff035b6b5c1b4b35d3ad6c649d94e421 100644 +--- a/src/main/java/net/minecraft/server/players/StoredUserList.java ++++ b/src/main/java/net/minecraft/server/players/StoredUserList.java +@@ -95,7 +95,7 @@ public abstract class StoredUserList> { + } + + public String[] getUserList() { +- return (String[]) this.map.keySet().toArray(new String[0]); ++ return (String[]) this.map.keySet().toArray(me.titaniumtown.Constants.EMPTY_string_arr); // JettPack + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java +index eec830f9924f1458e9d3035192408f31a0b87871..225324615cc5d20b928714695f62adee91f43c5a 100644 +--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java ++++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java +@@ -5,7 +5,7 @@ import java.util.function.IntConsumer; + import org.apache.commons.lang3.Validate; + + public class ZeroBitStorage implements BitStorage { +- public static final long[] RAW = new long[0]; ++ public static final long[] RAW = me.titaniumtown.Constants.EMPTY_long_arr; // JettPack + private final int size; + + public ZeroBitStorage(int size) { +diff --git a/src/main/java/net/minecraft/world/entity/EquipmentSlot.java b/src/main/java/net/minecraft/world/entity/EquipmentSlot.java +index b68e7db2fbb085c2b25af3e93d501fbaac98893b..d7d39f11fdf4646a78868c22310e77fc4e86ea06 100644 +--- a/src/main/java/net/minecraft/world/entity/EquipmentSlot.java ++++ b/src/main/java/net/minecraft/world/entity/EquipmentSlot.java +@@ -13,6 +13,8 @@ public enum EquipmentSlot { + private final int filterFlag; + private final String name; + ++ public static final EquipmentSlot[] VALUES = EquipmentSlot.values(); // JettPack - reduce allocs ++ + private EquipmentSlot(EquipmentSlot.Type type, int entityId, int armorStandId, String name) { + this.type = type; + this.index = entityId; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 2fe2307a10cb2e4e2b055cbe615b3750bf8fd310..f646545ee7178717c1e3bb48e98fd2af2af745e0 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3032,7 +3032,7 @@ public abstract class LivingEntity extends Entity { + @Nullable + private Map collectEquipmentChanges() { + Map map = null; +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack + int i = aenumitemslot.length; + + for (int j = 0; j < i; ++j) { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 8ee0a80e3427ecef9fa15b7388a8287e26a25fd6..340589e02b316553acefee480f86cf53d5d17560 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1010,7 +1010,7 @@ public abstract class Mob extends LivingEntity { + @Override + protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { + super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack - reduce allocs + int j = aenumitemslot.length; + + for (int k = 0; k < j; ++k) { +@@ -1072,7 +1072,7 @@ public abstract class Mob extends LivingEntity { + } + + boolean flag = true; +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack - reduce allocs + int j = aenumitemslot.length; + + for (int k = 0; k < j; ++k) { +@@ -1159,7 +1159,7 @@ public abstract class Mob extends LivingEntity { + float f = difficulty.getSpecialMultiplier(); + + this.enchantSpawnedWeapon(f); +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack - reduce allocs + int i = aenumitemslot.length; + + for (int j = 0; j < i; ++j) { +@@ -1370,7 +1370,7 @@ public abstract class Mob extends LivingEntity { + t0.setInvulnerable(this.isInvulnerable()); + if (flag) { + t0.setCanPickUpLoot(this.canPickUpLoot()); +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack - reduce allocs + int i = aenumitemslot.length; + + for (int j = 0; j < i; ++j) { +diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +index fc6639bd33a13c84c84603db7f9bbb1c5df239a4..814e5abe38087f6ca3322923564b59fb31b2bc83 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -229,7 +229,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + return; + } + // CraftBukkit end +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack - reduce allocs + int i = aenumitemslot.length; + + for (int j = 0; j < i; ++j) { +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 6837c965592d4584cfc958a1008b98791a0fc780..586ce0bec1ab2a17a3818690e47be23643cd5a48 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -957,7 +957,7 @@ public final class ItemStack { + int k; + + if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.MODIFIERS)) { +- EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); ++ EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // JettPack - reduce allocs + + k = aenumitemslot.length; + +diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java +index 9e3f9099cc47e6c6e40d11ef6d6e83fbf19a3cf7..b9c1db394ee35ed1acead620f7e9e47c60fa1532 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java ++++ b/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java +@@ -242,7 +242,7 @@ public class ShapedRecipe implements CraftingRecipe { + } + + if (pattern.length == l) { +- return new String[0]; ++ return me.titaniumtown.Constants.EMPTY_string_arr; // JettPack + } else { + String[] astring1 = new String[pattern.length - l - k]; + +diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java +index ef36f0a9b1849dd3152c0a1c81cded5c4f06aa3c..946bb3c6a59d0e611d28549ceee8946b46eaa9e3 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java +@@ -42,8 +42,8 @@ public class Enchantments { + public static final Enchantment MULTISHOT = Enchantments.register("multishot", new MultiShotEnchantment(Enchantment.Rarity.RARE, new EquipmentSlot[]{EquipmentSlot.MAINHAND})); + public static final Enchantment QUICK_CHARGE = Enchantments.register("quick_charge", new QuickChargeEnchantment(Enchantment.Rarity.UNCOMMON, new EquipmentSlot[]{EquipmentSlot.MAINHAND})); + public static final Enchantment PIERCING = Enchantments.register("piercing", new ArrowPiercingEnchantment(Enchantment.Rarity.COMMON, new EquipmentSlot[]{EquipmentSlot.MAINHAND})); +- public static final Enchantment MENDING = Enchantments.register("mending", new MendingEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.values())); +- public static final Enchantment VANISHING_CURSE = Enchantments.register("vanishing_curse", new VanishingCurseEnchantment(Enchantment.Rarity.VERY_RARE, EquipmentSlot.values())); ++ public static final Enchantment MENDING = Enchantments.register("mending", new MendingEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.VALUES)); // JettPack ++ public static final Enchantment VANISHING_CURSE = Enchantments.register("vanishing_curse", new VanishingCurseEnchantment(Enchantment.Rarity.VERY_RARE, EquipmentSlot.VALUES)); // JettPack + + // CraftBukkit start + static { +diff --git a/src/main/java/net/minecraft/world/level/EntityBasedExplosionDamageCalculator.java b/src/main/java/net/minecraft/world/level/EntityBasedExplosionDamageCalculator.java +index 4257e5bf92a219f20026e4ec7e0d7323f2adf30e..24612061174b659666e3c09a44ddc597b34f549a 100644 +--- a/src/main/java/net/minecraft/world/level/EntityBasedExplosionDamageCalculator.java ++++ b/src/main/java/net/minecraft/world/level/EntityBasedExplosionDamageCalculator.java +@@ -15,9 +15,17 @@ public class EntityBasedExplosionDamageCalculator extends ExplosionDamageCalcula + + @Override + public Optional getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState) { +- return super.getBlockExplosionResistance(explosion, world, pos, blockState, fluidState).map((max) -> { +- return this.source.getBlockExplosionResistance(explosion, world, pos, blockState, fluidState, max); +- }); ++ // JettPack start - lithium: reduce allocs ++ Optional optionalBlastResistance = super.getBlockExplosionResistance(explosion, world, pos, blockState, fluidState); ++ if (optionalBlastResistance.isPresent()) { ++ float blastResistance = optionalBlastResistance.get(); ++ float effectiveExplosionResistance = this.source.getBlockExplosionResistance(explosion, world, pos, blockState, fluidState, blastResistance); ++ if (effectiveExplosionResistance != blastResistance) { ++ return Optional.of(effectiveExplosionResistance); ++ } ++ } ++ return optionalBlastResistance; ++ // JettPack end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index b830c2199a22252116619520f407fd84eddb28f3..5df2601aecb93fd6014b70d0da645085ddec8076 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1566,7 +1566,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { + io.papermc.paper.world.ChunkEntitySlices slices = this.entitySliceManager.getChunk(chunkX, chunkZ); + if (slices == null) { +- return new org.bukkit.entity.Entity[0]; ++ return me.titaniumtown.Constants.EMPTY_bukkit_entity_arr; // JettPack + } + return slices.getChunkEntities(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +index fb8b8a9733ac50096d8406487ab1ae167ef5f7b1..644714723911d950fb179216c4976baa2da85644 100644 +--- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +@@ -373,7 +373,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + + @Override + public int[] getSlotsForFace(Direction side) { +- return side == Direction.DOWN ? new int[]{0} : new int[0]; ++ return side == Direction.DOWN ? me.titaniumtown.Constants.ZERO_int_arr : me.titaniumtown.Constants.EMPTY_int_arr; // JettPack + } + + @Override +@@ -422,7 +422,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + + @Override + public int[] getSlotsForFace(Direction side) { +- return side == Direction.UP ? new int[]{0} : new int[0]; ++ return side == Direction.UP ? me.titaniumtown.Constants.ZERO_int_arr : me.titaniumtown.Constants.EMPTY_int_arr; // JettPack + } + + @Override +@@ -459,7 +459,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + + @Override + public int[] getSlotsForFace(Direction side) { +- return new int[0]; ++ return me.titaniumtown.Constants.EMPTY_int_arr; // JettPack + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index 601f8099f74e81c17600566b3c9b7a6dd39c9bcb..f816fae46568b83ef4441bd3bb06f2081308921e 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -119,7 +119,7 @@ public class PlayerDataStorage { + String[] astring = this.playerDir.list(); + + if (astring == null) { +- astring = new String[0]; ++ astring = me.titaniumtown.Constants.EMPTY_string_arr; // JettPack + } + + for (int i = 0; i < astring.length; ++i) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java +index e91714a66f582c14184ddb5e4bf2443d3ed45efb..174585c55450d6005c822801b0ce43b29091fc64 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java +@@ -4,8 +4,8 @@ import org.bukkit.inventory.EquipmentSlot; + + public class CraftEquipmentSlot { + +- private static final net.minecraft.world.entity.EquipmentSlot[] slots = new net.minecraft.world.entity.EquipmentSlot[EquipmentSlot.values().length]; +- private static final EquipmentSlot[] enums = new EquipmentSlot[net.minecraft.world.entity.EquipmentSlot.values().length]; ++ private static final net.minecraft.world.entity.EquipmentSlot[] slots = net.minecraft.world.entity.EquipmentSlot.VALUES; ++ private static final EquipmentSlot[] enums = new EquipmentSlot[net.minecraft.world.entity.EquipmentSlot.VALUES.length]; + + static { + set(EquipmentSlot.HAND, net.minecraft.world.entity.EquipmentSlot.MAINHAND); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +index 6827979a5b270ced53b46ecb9eff548727dadb81..d42175344699d3742c17e658aa689755813ec0ac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +@@ -165,7 +165,7 @@ public class CraftEntityEquipment implements EntityEquipment { + + @Override + public void clear() { +- for (net.minecraft.world.entity.EquipmentSlot slot : net.minecraft.world.entity.EquipmentSlot.values()) { ++ for (net.minecraft.world.entity.EquipmentSlot slot : net.minecraft.world.entity.EquipmentSlot.VALUES) { + this.setEquipment(slot, null, false); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java b/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java +index 049d750d3af991dd14ac8cf644330404e74b2151..f965d6751c8f5a8ec4b26e6ac65414540345b04a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/WeakCollection.java +@@ -166,7 +166,7 @@ public final class WeakCollection implements Collection { + + @Override + public Object[] toArray() { +- return this.toArray(new Object[0]); ++ return this.toArray(me.titaniumtown.Constants.EMPTY_object_arr); + } + + @Override diff --git a/patches/server/0006-lithium-fast-util.patch b/patches/server/0006-lithium-fast-util.patch new file mode 100644 index 0000000..344b1e8 --- /dev/null +++ b/patches/server/0006-lithium-fast-util.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Tue, 14 Dec 2021 12:04:01 -0500 +Subject: [PATCH] lithium: fast util + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index a75a7a83bea89db4e4a4ca1c06233b3f4350fa73..4be4d9e6611dc3b7a34dbf2f5d6990bc0980ee63 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -191,7 +191,7 @@ public enum Direction implements StringRepresentable { + } + + public Direction getOpposite() { +- return from3DDataValue(this.oppositeIndex); ++ return VALUES[this.oppositeIndex]; // JettPack - lithium: fast_util + } + + public Direction getClockWise(Direction.Axis axis) { +@@ -441,7 +441,7 @@ public enum Direction implements StringRepresentable { + } + + public static Direction getRandom(Random random) { +- return Util.getRandom(VALUES, random); ++ return VALUES[random.nextInt(VALUES.length)]; // JettPack - lithium: fast_util + } + + public static Direction getNearest(double x, double y, double z) { +diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +index 0d24ed60976d862c14cca4af0f6efc15f7c52c7e..9c1e6287b290df11072e1d7b892e77ae10c95af1 100644 +--- a/src/main/java/net/minecraft/world/phys/AABB.java ++++ b/src/main/java/net/minecraft/world/phys/AABB.java +@@ -6,6 +6,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.util.Mth; + import net.minecraft.world.level.levelgen.structure.BoundingBox; ++import net.minecraft.core.Direction; // JettPack + + public class AABB { + private static final double EPSILON = 1.0E-7D; +@@ -16,6 +17,15 @@ public class AABB { + public final double maxY; + public final double maxZ; + ++ // JettPack start - lithium: fast_util ++ static { ++ assert Direction.Axis.X.ordinal() == 0; ++ assert Direction.Axis.Y.ordinal() == 1; ++ assert Direction.Axis.Z.ordinal() == 2; ++ assert Direction.Axis.values().length == 3; ++ } ++ // JettPack end ++ + public AABB(double x1, double y1, double z1, double x2, double y2, double z2) { + this.minX = Math.min(x1, x2); + this.minY = Math.min(y1, y2); +@@ -81,11 +91,33 @@ public class AABB { + } + + public double min(Direction.Axis axis) { +- return axis.choose(this.minX, this.minY, this.minZ); ++ // JettPack start - lithium: fast_util ++ switch (axis.ordinal()) { ++ case 0: //X ++ return this.minX; ++ case 1: //Y ++ return this.minY; ++ case 2: //Z ++ return this.minZ; ++ } ++ ++ throw new IllegalArgumentException(); ++ // JettPack end + } + + public double max(Direction.Axis axis) { +- return axis.choose(this.maxX, this.maxY, this.maxZ); ++ // JettPack start - lithium: fast_util ++ switch (axis.ordinal()) { ++ case 0: //X ++ return this.maxX; ++ case 1: //Y ++ return this.maxY; ++ case 2: //Z ++ return this.maxZ; ++ } ++ ++ throw new IllegalArgumentException(); ++ // JettPack end + } + + @Override diff --git a/patches/server/0007-lithium-HashedList.patch b/patches/server/0007-lithium-HashedList.patch new file mode 100644 index 0000000..28557ee --- /dev/null +++ b/patches/server/0007-lithium-HashedList.patch @@ -0,0 +1,309 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Fri, 31 Jul 2020 21:46:32 -0500 +Subject: [PATCH] lithium: HashedList + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/HashedList.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/HashedList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2d79932dbd1fc386a94b8d6ea3526934c54c2aad +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/HashedList.java +@@ -0,0 +1,280 @@ ++package me.jellysquid.mods.lithium.common.util.collections; ++ ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceArrayList; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import java.util.ListIterator; ++import java.util.NoSuchElementException; ++ ++/** ++ * Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type ++ * contained by this list must use reference-equality semantics. ++ */ ++@SuppressWarnings("SuspiciousMethodCalls") ++public class HashedList implements List { ++ private final ReferenceArrayList list; ++ private final Reference2IntOpenHashMap counter; ++ ++ public HashedList(List list) { ++ this.list = new ReferenceArrayList<>(); ++ this.list.addAll(list); ++ ++ this.counter = new Reference2IntOpenHashMap<>(); ++ this.counter.defaultReturnValue(0); ++ ++ for (T obj : this.list) { ++ this.counter.addTo(obj, 1); ++ } ++ } ++ ++ @Override ++ public int size() { ++ return this.list.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.list.isEmpty(); ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ return this.counter.containsKey(o); ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return this.listIterator(); ++ } ++ ++ @Override ++ public Object[] toArray() { ++ return this.list.toArray(); ++ } ++ ++ @SuppressWarnings("SuspiciousToArrayCall") ++ @Override ++ public T1[] toArray(T1[] a) { ++ return this.list.toArray(a); ++ } ++ ++ @Override ++ public boolean add(T t) { ++ this.trackReferenceAdded(t); ++ ++ return this.list.add(t); ++ } ++ ++ @Override ++ public boolean remove(Object o) { ++ this.trackReferenceRemoved(o); ++ ++ return this.list.remove(o); ++ } ++ ++ @Override ++ public boolean containsAll(Collection c) { ++ for (Object obj : c) { ++ if (!this.counter.containsKey(obj)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public boolean addAll(Collection c) { ++ for (T obj : c) { ++ this.trackReferenceAdded(obj); ++ } ++ ++ return this.list.addAll(c); ++ } ++ ++ @Override ++ public boolean addAll(int index, Collection c) { ++ for (T obj : c) { ++ this.trackReferenceAdded(obj); ++ } ++ ++ return this.list.addAll(index, c); ++ } ++ ++ @Override ++ public boolean removeAll(Collection c) { ++ for (Object obj : c) { ++ this.trackReferenceRemoved(obj); ++ } ++ ++ return this.list.removeAll(c); ++ } ++ ++ @Override ++ public boolean retainAll(Collection c) { ++ return this.list.retainAll(c); ++ } ++ ++ @Override ++ public void clear() { ++ this.counter.clear(); ++ this.list.clear(); ++ } ++ ++ @Override ++ public T get(int index) { ++ return this.list.get(index); ++ } ++ ++ @Override ++ public T set(int index, T element) { ++ T prev = this.list.set(index, element); ++ ++ if (prev != element) { ++ if (prev != null) { ++ this.trackReferenceRemoved(prev); ++ } ++ ++ this.trackReferenceAdded(element); ++ } ++ ++ return prev; ++ } ++ ++ @Override ++ public void add(int index, T element) { ++ this.trackReferenceAdded(element); ++ ++ this.list.add(index, element); ++ } ++ ++ @Override ++ public T remove(int index) { ++ T prev = this.list.remove(index); ++ ++ if (prev != null) { ++ this.trackReferenceRemoved(prev); ++ } ++ ++ return prev; ++ } ++ ++ @Override ++ public int indexOf(Object o) { ++ return this.list.indexOf(o); ++ } ++ ++ @Override ++ public int lastIndexOf(Object o) { ++ return this.list.lastIndexOf(o); ++ } ++ ++ @Override ++ public ListIterator listIterator() { ++ return this.listIterator(0); ++ } ++ ++ @Override ++ public ListIterator listIterator(int index) { ++ return new ListIterator() { ++ private final ListIterator inner = HashedList.this.list.listIterator(index); ++ ++ @Override ++ public boolean hasNext() { ++ return this.inner.hasNext(); ++ } ++ ++ @Override ++ public T next() { ++ return this.inner.next(); ++ } ++ ++ @Override ++ public boolean hasPrevious() { ++ return this.inner.hasPrevious(); ++ } ++ ++ @Override ++ public T previous() { ++ return this.inner.previous(); ++ } ++ ++ @Override ++ public int nextIndex() { ++ return this.inner.nextIndex(); ++ } ++ ++ @Override ++ public int previousIndex() { ++ return this.inner.previousIndex(); ++ } ++ ++ @Override ++ public void remove() { ++ int last = this.previousIndex(); ++ ++ if (last == -1) { ++ throw new NoSuchElementException(); ++ } ++ ++ T prev = HashedList.this.get(last); ++ ++ if (prev != null) { ++ HashedList.this.trackReferenceRemoved(prev); ++ } ++ ++ this.inner.remove(); ++ } ++ ++ @Override ++ public void set(T t) { ++ int last = this.previousIndex(); ++ ++ if (last == -1) { ++ throw new NoSuchElementException(); ++ } ++ ++ T prev = HashedList.this.get(last); ++ ++ if (prev != t) { ++ if (prev != null) { ++ HashedList.this.trackReferenceRemoved(prev); ++ } ++ ++ HashedList.this.trackReferenceAdded(t); ++ } ++ ++ this.inner.remove(); ++ } ++ ++ @Override ++ public void add(T t) { ++ HashedList.this.trackReferenceAdded(t); ++ ++ this.inner.add(t); ++ } ++ }; ++ } ++ ++ @Override ++ public List subList(int fromIndex, int toIndex) { ++ return this.list.subList(fromIndex, toIndex); ++ } ++ ++ private void trackReferenceAdded(T t) { ++ this.counter.addTo(t, 1); ++ } ++ ++ @SuppressWarnings("unchecked") ++ private void trackReferenceRemoved(Object o) { ++ if (this.counter.addTo((T) o, -1) <= 1) { ++ this.counter.removeInt(o); ++ } ++ } ++ ++ public static HashedList wrapper(List list) { ++ return new HashedList<>(list); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 5df2601aecb93fd6014b70d0da645085ddec8076..8fb58a59181fdf3a6ca5710d42eac060fc756395 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -115,8 +115,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- protected final List blockEntityTickers = Lists.newArrayList(); public final int getTotalTileEntityTickers() { return this.blockEntityTickers.size(); } // Paper +- private final List pendingBlockEntityTickers = Lists.newArrayList(); ++ protected final List blockEntityTickers = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); public final int getTotalTileEntityTickers() { return this.blockEntityTickers.size(); } // Paper // Jettpack - lithium: hashed_list ++ private final List pendingBlockEntityTickers = me.jellysquid.mods.lithium.common.util.collections.HashedList.wrapper(Lists.newArrayList()); // Jettpack - lithium: hashed_list + private boolean tickingBlockEntities; + public final Thread thread; + private final boolean isDebug; diff --git a/patches/server/0008-lithium-CompactSineLUT.patch b/patches/server/0008-lithium-CompactSineLUT.patch new file mode 100644 index 0000000..d200080 --- /dev/null +++ b/patches/server/0008-lithium-CompactSineLUT.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JellySquid +Date: Fri, 5 Feb 2021 00:16:30 -0600 +Subject: [PATCH] lithium: CompactSineLUT + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/math/CompactSineLUT.java b/src/main/java/me/jellysquid/mods/lithium/common/util/math/CompactSineLUT.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d2b00d9a91b60716d5f458048df3d944060eb749 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/math/CompactSineLUT.java +@@ -0,0 +1,90 @@ ++package me.jellysquid.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: ++ * https://gitlab.com/coderbot16/i73/-/tree/master/i73-trig/src ++ * @author jellysquid3 Additional optimizations, port to Java ++ */ ++public class CompactSineLUT { ++ private static final int[] SINE_TABLE_INT = new int[16384 + 1]; ++ private static final float SINE_TABLE_MIDPOINT; ++ ++ static { ++ final float[] SINE_TABLE = Mth.getSinTable(); ++ // 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.38) & 0xFFFF); ++ } ++ ++ // [VanillaCopy] MathHelper#cos(float) ++ public static float cos(float f) { ++ return lookup((int) (f * 10430.38 + 16384.0) & 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); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/util/Mth.java b/src/main/java/net/minecraft/util/Mth.java +index 42e0c370092eb6c190dbf3b279649d47b81950af..23d4efac14b391a5f654013fc549c3274c160ffa 100644 +--- a/src/main/java/net/minecraft/util/Mth.java ++++ b/src/main/java/net/minecraft/util/Mth.java +@@ -8,6 +8,7 @@ import net.minecraft.core.Vec3i; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang3.math.NumberUtils; ++import me.jellysquid.mods.lithium.common.util.math.CompactSineLUT; // Mirai + + public class Mth { + private static final int BIG_ENOUGH_INT = 1024; +@@ -30,7 +31,8 @@ public class Mth { + } + + }); +- private static final Random RANDOM = new Random(); ++ private static final Random RANDOM = new Random(); // Mirai - too lazy to edit correctly this line ++ public static float[] getSinTable() { return SIN; } // Mirai + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + private static final double ONE_SIXTH = 0.16666666666666666D; + private static final int FRAC_EXP = 8; +@@ -40,11 +42,13 @@ public class Mth { + private static final double[] COS_TAB = new double[257]; + + public static float sin(float value) { +- return SIN[(int)(value * 10430.378F) & '\uffff']; ++ //return SIN[(int)(value * 10430.378F) & '\uffff']; ++ return CompactSineLUT.sin(value); // Mirai + } + + public static float cos(float value) { +- return SIN[(int)(value * 10430.378F + 16384.0F) & '\uffff']; ++ //return SIN[(int)(value * 10430.378F + 16384.0F) & '\uffff']; ++ return CompactSineLUT.cos(value); // Mirai + } + + public static float sqrt(float value) { diff --git a/patches/server/0009-lithium-fast-retrieval.patch b/patches/server/0009-lithium-fast-retrieval.patch new file mode 100644 index 0000000..24f5c49 --- /dev/null +++ b/patches/server/0009-lithium-fast-retrieval.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Wed, 15 Dec 2021 11:20:48 -0500 +Subject: [PATCH] lithium: fast retrieval + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +index 2ad47b33aea2493b2cdc0431849ecd50a12f9146..ba8aef74c0c23a39b0e4387916a904e14d41a25a 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +@@ -32,33 +32,66 @@ public class EntitySectionStorage { + this.intialSectionVisibility = chunkStatusDiscriminator; + } + ++ // JettPack start - lithium: entity.fast_retrieval + public void forEachAccessibleNonEmptySection(AABB box, Consumer> action) { +- int i = SectionPos.posToSectionCoord(box.minX - 2.0D); +- int j = SectionPos.posToSectionCoord(box.minY - 2.0D); +- int k = SectionPos.posToSectionCoord(box.minZ - 2.0D); +- int l = SectionPos.posToSectionCoord(box.maxX + 2.0D); +- int m = SectionPos.posToSectionCoord(box.maxY + 2.0D); +- int n = SectionPos.posToSectionCoord(box.maxZ + 2.0D); +- +- for(int o = i; o <= l; ++o) { +- long p = SectionPos.asLong(o, 0, 0); +- long q = SectionPos.asLong(o, -1, -1); +- LongIterator longIterator = this.sectionIds.subSet(p, q + 1L).iterator(); +- +- while(longIterator.hasNext()) { +- long r = longIterator.nextLong(); +- int s = SectionPos.y(r); +- int t = SectionPos.z(r); +- if (s >= j && s <= m && t >= k && t <= n) { +- EntitySection entitySection = this.sections.get(r); +- if (entitySection != null && !entitySection.isEmpty() && entitySection.getStatus().isAccessible()) { +- action.accept(entitySection); ++ int minX = SectionPos.posToSectionCoord(box.minX - 2.0D); ++ int minY = SectionPos.posToSectionCoord(box.minY - 2.0D); ++ int minZ = SectionPos.posToSectionCoord(box.minZ - 2.0D); ++ int maxX = SectionPos.posToSectionCoord(box.maxX + 2.0D); ++ int maxY = SectionPos.posToSectionCoord(box.maxY + 2.0D); ++ int maxZ = SectionPos.posToSectionCoord(box.maxZ + 2.0D); ++ ++ if (maxX >= minX + 4 || maxZ >= minZ + 4) { ++ // [VanillaCopy] ++ for(int o = minX; o <= maxX; ++o) { ++ long p = SectionPos.asLong(o, 0, 0); ++ long q = SectionPos.asLong(o, -1, -1); ++ LongIterator longIterator = this.sectionIds.subSet(p, q + 1L).iterator(); ++ ++ while(longIterator.hasNext()) { ++ long r = longIterator.nextLong(); ++ int s = SectionPos.y(r); ++ int t = SectionPos.z(r); ++ if (s >= minY && s <= maxY && t >= minZ && t <= maxZ) { ++ EntitySection entitySection = this.sections.get(r); ++ if (entitySection != null && !entitySection.isEmpty() && entitySection.getStatus().isAccessible()) { ++ action.accept(entitySection); ++ } + } + } + } ++ } else { ++ for (int x = minX; x <= maxX; x++) { ++ for (int z = Math.max(minZ, 0); z <= maxZ; z++) { ++ this.forEachInColumn(x, minY, maxY, z, action); ++ } ++ ++ int bound = Math.min(-1, maxZ); ++ for (int z = minZ; z <= bound; z++) { ++ this.forEachInColumn(x, minY, maxY, z, action); ++ } ++ } + } ++ } + ++ private void forEachInColumn(int x, int minY, int maxY, int z, Consumer> action) { ++ //y from negative to positive, but y is treated as unsigned ++ for (int y = Math.max(minY, 0); y <= maxY; y++) { ++ this.consumeSection(SectionPos.asLong(x, y, z), action); ++ } ++ int bound = Math.min(-1, maxY); ++ for (int y = minY; y <= bound; y++) { ++ this.consumeSection(SectionPos.asLong(x, y, z), action); ++ } ++ } ++ ++ private void consumeSection(long pos, Consumer> action) { ++ EntitySection section = this.getSection(pos); ++ if (section != null && 0 != section.size() && section.getStatus().isAccessible()) { ++ action.accept(section); ++ } + } ++ // JettPack end + + public LongStream getExistingSectionPositionsInChunk(long chunkPos) { + int i = ChunkPos.getX(chunkPos); diff --git a/patches/server/0010-Add-config-for-logging-login-location.patch b/patches/server/0010-Add-config-for-logging-login-location.patch new file mode 100644 index 0000000..a99c165 --- /dev/null +++ b/patches/server/0010-Add-config-for-logging-login-location.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Wed, 20 Jan 2021 16:36:48 -0500 +Subject: [PATCH] Add config for logging login location + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 49e27efb5fdf26f6a8026c8c17e25d87026ab45d..15b5d142a8261f397ab2ab939d92aeb5701e28fb 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -494,7 +494,13 @@ public abstract class PlayerList { + } + // Paper end + // CraftBukkit - Moved from above, added world +- PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); ++ // Yatopia start - configurable logging of player login location ++ if (wtf.etil.mirai.MiraiConfig.logPlayerLoginLoc) { ++ PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); ++ } else { ++ PlayerList.LOGGER.info("{}[{}] logged in with entity id {}", player.getName().getString(), s1, player.getId()); ++ } ++ // Yatopia end - configurable logging of player login location + } + + public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) { +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 5b30b329959811f99f13f32166574f90e765bcb1..63d750d5858d20304b9608697ae618eb97cb6eb6 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -184,6 +184,11 @@ public class MiraiConfig { + + // Mirai - settings below + +- ++ public static boolean logPlayerLoginLoc = true; ++ private static void logPlayerLoc() { ++ logPlayerLoginLoc = getBoolean("log-player-login-location", logPlayerLoginLoc); ++ } ++ ++ + + } +\ No newline at end of file diff --git a/patches/server/0011-Global-Eula-file.patch b/patches/server/0011-Global-Eula-file.patch new file mode 100644 index 0000000..a557de6 --- /dev/null +++ b/patches/server/0011-Global-Eula-file.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: tr7zw +Date: Sat, 25 Jul 2020 17:16:18 +0200 +Subject: [PATCH] Global Eula file + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/Eula.java b/src/main/java/net/minecraft/server/Eula.java +index 30599028f6263fbf61ab95d68573073426db19db..9e169c96fa1bc9dea9efb27cb996b4dd5d2a4c0c 100644 +--- a/src/main/java/net/minecraft/server/Eula.java ++++ b/src/main/java/net/minecraft/server/Eula.java +@@ -16,12 +16,26 @@ public class Eula { + + public Eula(Path eulaFile) { + this.file = eulaFile; +- this.agreed = SharedConstants.IS_RUNNING_IN_IDE || this.readFile(); ++ this.agreed = SharedConstants.IS_RUNNING_IN_IDE || globalEula() || this.readFile(eulaFile); + } + +- private boolean readFile() { ++ // Yatopia start - global eula file ++ private boolean globalEula() { ++ java.io.File globalEula = new java.io.File(System.getProperty("user.home"), "eula.txt"); ++ ++ if (globalEula.exists()) { ++ System.out.println("Loaded global eula file from " + globalEula.getAbsolutePath()); ++ return readFile(globalEula.toPath()); ++ } else { ++ System.out.println("No global eula file found at " + globalEula.getAbsolutePath()); ++ } ++ return false; ++ } ++ // Yatopia end ++ ++ private boolean readFile(java.nio.file.Path path) { + try { +- InputStream inputStream = Files.newInputStream(this.file); ++ InputStream inputStream = Files.newInputStream(path); + + boolean var3; + try { diff --git a/patches/server/0012-Stop-wasting-resources-on-JsonList-get.patch b/patches/server/0012-Stop-wasting-resources-on-JsonList-get.patch new file mode 100644 index 0000000..69c2a46 --- /dev/null +++ b/patches/server/0012-Stop-wasting-resources-on-JsonList-get.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ivan Pekov +Date: Fri, 4 Sep 2020 10:07:42 +0300 +Subject: [PATCH] Stop wasting resources on JsonList#get + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 15b5d142a8261f397ab2ab939d92aeb5701e28fb..ba8e58fe70f16dbc0b64a92264d29c91461d2cdf 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -754,13 +754,19 @@ public abstract class PlayerList { + if (getBans().isBanned(gameprofile) && (gameprofilebanentry = getBans().get(gameprofile)) != null) { + // Paper end + ++ if (!gameprofilebanentry.hasExpired()) { // Yatopia + chatmessage = new TranslatableComponent("multiplayer.disconnect.banned.reason", new Object[]{gameprofilebanentry.getReason()}); + if (gameprofilebanentry.getExpires() != null) { + chatmessage.append((Component) (new TranslatableComponent("multiplayer.disconnect.banned.expiration", new Object[]{PlayerList.BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires())}))); + } + + // return chatmessage; ++ // Yatopia start - Stop wasting resources on JsonList#get + event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure ++ } else { ++ getBans().remove(gameprofile); ++ } ++ // Yatopia end + } else if (!this.isWhitelisted(gameprofile, event)) { // Paper + //chatmessage = new ChatMessage("multiplayer.disconnect.not_whitelisted"); // Paper + //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, org.spigotmc.SpigotConfig.whitelistMessage); // Spigot // Paper - moved to isWhitelisted +diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java +index 63c0fe30ff035b6b5c1b4b35d3ad6c649d94e421..f01621bf34da2ea877e010834511c119664a03b7 100644 +--- a/src/main/java/net/minecraft/server/players/StoredUserList.java ++++ b/src/main/java/net/minecraft/server/players/StoredUserList.java +@@ -73,9 +73,14 @@ public abstract class StoredUserList> { + // Paper start + // this.g(); + // return (V) this.d.get(this.a(k0)); // CraftBukkit - fix decompile error ++ // Yatopia start - only remove if it expires and has been requested (Stop wasting resources on JsonList#get) ++ return this.map.get(this.getKeyForUser(key)); ++ /* + return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> { + return v.hasExpired() ? null : v; + }); ++ */ ++ // Sugarcane end + // Paper end + } + +@@ -150,6 +155,8 @@ public abstract class StoredUserList> { + public void save() throws IOException { + this.removeExpired(); // Paper - remove expired values before saving + JsonArray jsonarray = new JsonArray(); ++ // Yatopia start - we're nuking streams wherever possible (Stop wasting resources on JsonList#get) ++ /* + Stream stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error + JsonObject jsonobject = new JsonObject(); + +@@ -159,6 +166,13 @@ public abstract class StoredUserList> { + + Objects.requireNonNull(jsonarray); + stream.forEach(jsonarray::add); ++ */ ++ for (V value : this.map.values()) { ++ JsonObject obj = new JsonObject(); ++ value.serialize(obj); ++ jsonarray.add(obj); ++ } ++ // Yatopia end + BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); + + try { diff --git a/patches/server/0013-Configurable-max-bees-in-hive.patch b/patches/server/0013-Configurable-max-bees-in-hive.patch new file mode 100644 index 0000000..735878d --- /dev/null +++ b/patches/server/0013-Configurable-max-bees-in-hive.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 15:39:29 +0100 +Subject: [PATCH] Configurable max bees in hive + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index 483dc52c793d9f90a35b1d091dd8ce71115e4640..0282c11936f817dd86861960cf58b8466e8d27f6 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -42,7 +42,7 @@ public class BeehiveBlockEntity extends BlockEntity { + private final List stored = Lists.newArrayList(); + @Nullable + public BlockPos savedFlowerPos; +- public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold ++ public int maxBees = wtf.etil.mirai.MiraiConfig.maxBees; // CraftBukkit - allow setting max amount of bees a hive can hold // Mirai - configurable max bees in hive + + public BeehiveBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.BEEHIVE, pos, state); +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 63d750d5858d20304b9608697ae618eb97cb6eb6..667eaabe95e9b44cad64dd5aba5ac693f4d726ed 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -189,6 +189,9 @@ public class MiraiConfig { + logPlayerLoginLoc = getBoolean("log-player-login-location", logPlayerLoginLoc); + } + +- ++ public static int maxBees = 3; ++ private static void maximumBees() { ++ maxBees = getInt("max-bees-in-hive", maxBees); ++ } + + } +\ No newline at end of file diff --git a/patches/server/0014-Remove-TickTask.patch b/patches/server/0014-Remove-TickTask.patch new file mode 100644 index 0000000..4646120 --- /dev/null +++ b/patches/server/0014-Remove-TickTask.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: foss-mc <69294560+foss-mc@users.noreply.github.com> +Date: Thu, 1 Jul 2021 11:59:11 +0000 +Subject: [PATCH] Remove TickTask + +Original code by PatinaMC, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/PatinaMC/Patina + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 26841729126b022f75e55e584fe850771dedd4cd..38754510081b130e045900bec6d7e0a9cb47b767 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -185,7 +185,7 @@ import org.bukkit.event.server.ServerLoadEvent; + + import co.aikar.timings.MinecraftTimings; // Paper + +-public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements CommandSource, AutoCloseable { ++public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements CommandSource, AutoCloseable { // Patina + + private static MinecraftServer SERVER; // Paper + public static final Logger LOGGER = LogUtils.getLogger(); +@@ -1352,19 +1352,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; + } + // Paper end +- return new TickTask(this.tickCount, runnable); ++ return runnable; // Patina + } + ++ /* // Patina + protected boolean shouldRun(TickTask ticktask) { + return ticktask.getTick() + 3 < this.tickCount || this.haveTime(); + } ++ */ + + @Override + public boolean pollTask() { +@@ -1396,10 +1398,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop optional = Optional.of(this.getFile("server-icon.png")).filter(File::isFile); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index b42e32bd1a6a669f498b577ec04661c4ba651198..fed79d31c36b9fafbdc71461e27b1f4e1ca0385a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -1257,10 +1257,12 @@ public class ServerChunkCache extends ChunkSource { + return runnable; + } + ++ /* // Patina + @Override + protected boolean shouldRun(Runnable task) { + return true; + } ++ */ + + @Override + protected boolean scheduleExecutables() { +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index 6158420c629d6acdaafb01a7e00d788023e922d1..9321e3a8b2f6f2eaebfa55679f287429d009e834 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -29,7 +29,7 @@ public abstract class BlockableEventLoop implements Profiler + + protected abstract R wrapRunnable(Runnable runnable); + +- protected abstract boolean shouldRun(R task); ++ //protected abstract boolean shouldRun(R task); // Patina + + public boolean isSameThread() { + return Thread.currentThread() == this.getRunningThread(); +@@ -120,7 +120,7 @@ public abstract class BlockableEventLoop implements Profiler + R runnable = this.pendingRunnables.peek(); + if (runnable == null) { + return false; +- } else if (this.blockingCount == 0 && !this.shouldRun(runnable)) { ++ } else if (this.blockingCount == 0 && !true/*this.shouldRun(runnable)*/) { // Patina + return false; + } else { + this.doRunTask(this.pendingRunnables.remove()); diff --git a/patches/server/0015-Make-a-field-final.patch b/patches/server/0015-Make-a-field-final.patch new file mode 100644 index 0000000..1f9b44d --- /dev/null +++ b/patches/server/0015-Make-a-field-final.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: foss-mc <69294560+foss-mc@users.noreply.github.com> +Date: Thu, 1 Jul 2021 12:11:49 +0000 +Subject: [PATCH] Make a field final + +Original code by PatinaMC, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/PatinaMC/Patina + +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index 00bbf33aa44e2d5a4e755a2d7c1a60de9e36674f..ec6a06460469a8bc2e10064d5b78b4b5daa6cc13 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -56,7 +56,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy + private final ResultConsumer consumer; + private final EntityAnchorArgument.Anchor anchor; + private final Vec2 rotation; +- public java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper ++ public final java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper // Patina - make a field final + + public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { + this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { diff --git a/patches/server/0016-Don-t-create-new-random-instance.patch b/patches/server/0016-Don-t-create-new-random-instance.patch new file mode 100644 index 0000000..278e610 --- /dev/null +++ b/patches/server/0016-Don-t-create-new-random-instance.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Wed, 5 Jan 2022 20:49:49 +0100 +Subject: [PATCH] Don't create new random instance + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index a6edf5117d4ae03d8294e7bb74dd2d77048895a5..1a9afa43a953e5634edb23ef617ead4b329ae89f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -367,7 +367,7 @@ public class ServerPlayer extends Player { + long l = k * k; + int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l; + int j1 = this.getCoprime(i1); +- int k1 = (new Random()).nextInt(i1); ++ int k1 = worldserver.random.nextInt(i1); // Patina - don't create new random instance + + for (int l1 = 0; l1 < i1; ++l1) { + int i2 = (k1 + j1 * l1) % i1; +@@ -404,7 +404,7 @@ public class ServerPlayer extends Player { + long l = k * k; + int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l; + int j1 = this.getCoprime(i1); +- int k1 = (new Random()).nextInt(i1); ++ int k1 = world.random.nextInt(i1); // Patina - don't create new random instance + + for (int l1 = 0; l1 < i1; ++l1) { + int i2 = (k1 + j1 * l1) % i1; +diff --git a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java +index 55c66f59c3946f79d8d0bd4dacd27dafb6090646..4bc858dc779a4553384091d5dd4e96911223302b 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java +@@ -348,7 +348,7 @@ public class QueryThreadGs4 extends GenericThread { + this.identBytes[2] = bs[5]; + this.identBytes[3] = bs[6]; + this.ident = new String(this.identBytes, StandardCharsets.UTF_8); +- this.challenge = (new Random()).nextInt(16777216); ++ this.challenge = java.util.concurrent.ThreadLocalRandom.current().nextInt(16777216); // Patina - don't create new random instance + this.challengeBytes = String.format("\t%s%d\u0000", this.ident, this.challenge).getBytes(StandardCharsets.UTF_8); + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 8fb58a59181fdf3a6ca5710d42eac060fc756395..af603994275c93842d2c3b6e9a7fcf0b0fbad168 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -121,13 +121,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final Thread thread; + private final boolean isDebug; + private int skyDarken; +- protected int randValue = (new Random()).nextInt(); ++ //protected int randValue = (new Random()).nextInt(); // Patina - moved down + protected final int addend = 1013904223; + protected float oRainLevel; + public float rainLevel; + protected float oThunderLevel; + public float thunderLevel; + public final Random random = new Random(); ++ protected int randValue = random.nextInt(); // Patina - don't create new random instance + final DimensionType dimensionType; + private final Holder dimensionTypeRegistration; + public final WritableLevelData levelData; diff --git a/patches/server/0017-Completely-remove-bootstrapExecutor.patch b/patches/server/0017-Completely-remove-bootstrapExecutor.patch new file mode 100644 index 0000000..1b53efb --- /dev/null +++ b/patches/server/0017-Completely-remove-bootstrapExecutor.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: foss-mc <69294560+foss-mc@users.noreply.github.com> +Date: Thu, 1 Jul 2021 12:26:15 +0000 +Subject: [PATCH] Completely remove bootstrapExecutor + +Original code by PatinaMC, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/PatinaMC/Patina + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index fa14cedb8437b1e1713b0023253845a4bc2b11fc..bc6600fe322ef9733233c0273e318f2c16f6d359 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -67,7 +67,7 @@ public class Util { + private static final int DEFAULT_MAX_THREADS = 255; + private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads"; + private static final AtomicInteger WORKER_COUNT = new AtomicInteger(1); +- private static final ExecutorService BOOTSTRAP_EXECUTOR = makeExecutor("Bootstrap", -2); // Paper - add -2 priority ++ //private static final ExecutorService BOOTSTRAP_EXECUTOR = makeExecutor("Bootstrap", -2); // Paper - add -2 priority // Patina + private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - add -1 priority + // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() { +@@ -190,7 +190,7 @@ public class Util { + } + + public static ExecutorService bootstrapExecutor() { +- return BOOTSTRAP_EXECUTOR; ++ return BACKGROUND_EXECUTOR; // Patina + } + + public static ExecutorService backgroundExecutor() { +diff --git a/src/main/java/net/minecraft/util/datafix/DataFixers.java b/src/main/java/net/minecraft/util/datafix/DataFixers.java +index e4090fa0f01078277d70b1a88e870a56f6b1cb0f..0c5abb040d156c8a855c3c40718411f2c7e8e43d 100644 +--- a/src/main/java/net/minecraft/util/datafix/DataFixers.java ++++ b/src/main/java/net/minecraft/util/datafix/DataFixers.java +@@ -78,15 +78,15 @@ public class DataFixers { + + private static final BiFunction SAME = Schema::new; + private static final BiFunction SAME_NAMESPACED = NamespacedSchema::new; +- private static final DataFixer DATA_FIXER = DataFixers.createFixerUpper(); ++ private static final DataFixer DATA_FIXER; // Patina + + public DataFixers() {} + +- private static DataFixer createFixerUpper() { ++ static { // Patina + DataFixerBuilder datafixerbuilder = new DataFixerBuilder(SharedConstants.getCurrentVersion().getWorldVersion()); + + DataFixers.addFixers(datafixerbuilder); +- return datafixerbuilder.build(Util.bootstrapExecutor()); ++ DATA_FIXER = datafixerbuilder.build(Util.backgroundExecutor()); // Patina + } + + public static DataFixer getDataFixer() { diff --git a/patches/server/0018-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch b/patches/server/0018-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch new file mode 100644 index 0000000..b417b29 --- /dev/null +++ b/patches/server/0018-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Thu, 8 Jul 2021 15:03:15 -0400 +Subject: [PATCH] Use LinkedBlockingDeque in IAsyncTaskHandler + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index 9321e3a8b2f6f2eaebfa55679f287429d009e834..a270169efccfdd92b2c18af37477b9020746861c 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -1,13 +1,10 @@ + package net.minecraft.util.thread; + + import com.google.common.collect.ImmutableList; +-import com.google.common.collect.Queues; + import com.mojang.logging.LogUtils; + import java.util.List; +-import java.util.Queue; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; +-import java.util.concurrent.locks.LockSupport; + import java.util.function.BooleanSupplier; + import java.util.function.Supplier; + import net.minecraft.util.profiling.metrics.MetricCategory; +@@ -15,12 +12,15 @@ import net.minecraft.util.profiling.metrics.MetricSampler; + import net.minecraft.util.profiling.metrics.MetricsRegistry; + import net.minecraft.util.profiling.metrics.ProfilerMeasured; + import org.slf4j.Logger; ++import java.util.concurrent.LinkedBlockingDeque; // JettPack ++import java.util.concurrent.TimeUnit; // JettPack + + public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, Executor { + private final String name; + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); ++ private final LinkedBlockingDeque pendingRunnables = new LinkedBlockingDeque(); // JettPack + private int blockingCount; ++ private R next = null; // JettPack + + protected BlockableEventLoop(String name) { + this.name = name; +@@ -89,7 +89,7 @@ public abstract class BlockableEventLoop implements Profiler + @Override + public void tell(R runnable) { + this.pendingRunnables.add(runnable); +- LockSupport.unpark(this.getRunningThread()); ++ //LockSupport.unpark(this.getRunningThread()); // JettPack + } + + @Override +@@ -117,15 +117,20 @@ public abstract class BlockableEventLoop implements Profiler + } + + public boolean pollTask() { +- R runnable = this.pendingRunnables.peek(); +- if (runnable == null) { +- return false; +- } else if (this.blockingCount == 0 && !true/*this.shouldRun(runnable)*/) { // Patina ++ // JettPack start ++ if (this.next == null && !this.pendingRunnables.isEmpty()) { ++ this.waitForTasks(); ++ } ++ ++ if (this.next == null) { + return false; + } else { +- this.doRunTask(this.pendingRunnables.remove()); ++ R r2 = this.next; ++ this.next = null; ++ this.doRunTask(r2); + return true; + } ++ // JettPack end + } + + public void managedBlock(BooleanSupplier stopCondition) { +@@ -144,8 +149,18 @@ public abstract class BlockableEventLoop implements Profiler + } + + protected void waitForTasks() { +- Thread.yield(); +- LockSupport.parkNanos("waiting for tasks", 100000L); ++ // JettPack start ++ if (this.next != null) { ++ throw new IllegalStateException("next != null"); ++ } ++ try { ++ this.next = this.pendingRunnables.poll(100L, TimeUnit.MICROSECONDS); ++ return; ++ } ++ catch (InterruptedException interruptedException) { ++ return; ++ } ++ // JettPack end + } + + protected void doRunTask(R task) { diff --git a/patches/server/0019-Better-handling-of-async-tasks.patch b/patches/server/0019-Better-handling-of-async-tasks.patch new file mode 100644 index 0000000..eb78529 --- /dev/null +++ b/patches/server/0019-Better-handling-of-async-tasks.patch @@ -0,0 +1,190 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Thu, 8 Jul 2021 17:00:21 -0400 +Subject: [PATCH] Better handling of async tasks + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index e8e49e1bdf1ff4ea60a4feb6ec52d999cbced256..b49f53a89d9c096355d6cc20a2a59033f2637432 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -53,11 +53,14 @@ import java.util.concurrent.atomic.AtomicBoolean; + import java.util.function.BiConsumer; + import java.util.function.Consumer; + import java.util.function.Supplier; ++import java.util.concurrent.SynchronousQueue; // Jettpack ++import java.util.concurrent.ConcurrentLinkedQueue; // Jettpack + + public final class MCUtil { ++ public static final ConcurrentLinkedQueue smallAsyncTasks = new ConcurrentLinkedQueue(); // Jettpack + public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( +- 0, 2, 60L, TimeUnit.SECONDS, +- new LinkedBlockingQueue<>(), ++ 4, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, // Jettpack ++ new SynchronousQueue(), // Jettpack + new ThreadFactoryBuilder() + .setNameFormat("Paper Async Task Handler Thread - %1$d") + .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER)) +@@ -74,6 +77,30 @@ public final class MCUtil { + + public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); + ++ // Jettpack start ++ public static void flushAsyncTasks() { ++ if (!smallAsyncTasks.isEmpty()) { ++ asyncExecutor.submit(() -> { ++ Runnable runnable; ++ while((runnable = (Runnable)smallAsyncTasks.poll()) != null) { ++ runnable.run(); ++ } ++ }); ++ } ++ } ++ ++ public static void flushAsyncTasksMidTick() { ++ int i = smallAsyncTasks.size() < 16 ? smallAsyncTasks.size() : 16; // if smallAsyncTasks is over size 16, only execute 16 tasks ++ asyncExecutor.submit(() -> { ++ Runnable runnable; ++ int i_tmp = 0; ++ while((runnable = (Runnable)smallAsyncTasks.poll()) != null && i_tmp != i-1) { ++ runnable.run(); ++ i_tmp += 1; ++ } ++ }); ++ } ++ // Jettpack end + + public static Runnable once(Runnable run) { + AtomicBoolean ran = new AtomicBoolean(false); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 38754510081b130e045900bec6d7e0a9cb47b767..1ef00b9a0b502fac69ffdd0e233c5222dd9178ff 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1059,6 +1059,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ net.minecraft.server.MCUtil.asyncExecutor.execute(() -> { // Jettpack + try { + new LoginHandler().fireEvents(); + } catch (Exception ex) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +index 9c1aff17aabd062640e3f451a2ef8c50a7c62f10..78073cbb7f36950537dc65633e264c30ee8f8a35 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -32,23 +32,29 @@ import java.util.Iterator; + import java.util.List; + import java.util.concurrent.Executor; + import java.util.concurrent.Executors; ++/* + import java.util.concurrent.SynchronousQueue; + import java.util.concurrent.ThreadPoolExecutor; + import java.util.concurrent.TimeUnit; ++*/ + + public class CraftAsyncScheduler extends CraftScheduler { + ++ /* + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), + new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper ++ */ + private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat("Craft Async Scheduler Management Thread").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper + private final List temp = new ArrayList<>(); + + CraftAsyncScheduler() { + super(true); ++ /* + executor.allowCoreThreadTimeOut(true); + executor.prestartAllCoreThreads(); ++ */ + } + + @Override +@@ -93,7 +99,7 @@ public class CraftAsyncScheduler extends CraftScheduler { + private boolean executeTask(CraftTask task) { + if (isValid(task)) { + this.runners.put(task.getTaskId(), task); +- this.executor.execute(new ServerSchedulerReportingWrapper(task)); ++ net.minecraft.server.MCUtil.asyncExecutor.execute(new ServerSchedulerReportingWrapper(task)); // Jettpack + return true; + } + return false; +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 24fefa521093448e608e217af7b88a6397a4b054..f3400bdf23f79098c7e7b68f6b93aa65ca5b09dd 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -141,6 +141,7 @@ public class WatchdogThread extends Thread + { + while ( !this.stopping ) + { ++ net.minecraft.server.MCUtil.flushAsyncTasks(); // Jettpack + // + // Paper start + Logger log = Bukkit.getServer().getLogger(); diff --git a/patches/server/0020-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch b/patches/server/0020-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch new file mode 100644 index 0000000..8676b2e --- /dev/null +++ b/patches/server/0020-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Thu, 8 Jul 2021 17:03:31 -0400 +Subject: [PATCH] Use MCUtil.asyncExecutor for MAIN_WORKER_EXECUTOR in + SystemUtils + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index bc6600fe322ef9733233c0273e318f2c16f6d359..73693a3a7faaa2be24c367e351f75aeb0d0f2208 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -61,6 +61,10 @@ import net.minecraft.util.datafix.DataFixers; + import net.minecraft.world.level.block.state.properties.Property; + import org.apache.commons.io.IOUtils; + import org.slf4j.Logger; ++import java.util.concurrent.AbstractExecutorService; ++import wtf.etil.mirai.server.util.ServerWorkerWrapper; ++import net.minecraft.server.MCUtil; ++import java.util.Collections; + + public class Util { + static final Logger LOGGER = LogUtils.getLogger(); +@@ -148,7 +152,43 @@ public class Util { + if (i <= 0) { + executorService = MoreExecutors.newDirectExecutorService(); + } else { +- executorService = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue(), target -> new net.minecraft.server.ServerWorkerThread(target, s, priorityModifier)); ++ executorService = Integer.getInteger("Paper.WorkerThreadCount", i) <= 0 ? MoreExecutors.newDirectExecutorService() : new AbstractExecutorService(){ ++ private volatile boolean shutdown = false; ++ ++ @Override ++ public final List shutdownNow() { ++ this.shutdown = true; ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public final void shutdown() { ++ this.shutdown = true; ++ } ++ ++ @Override ++ public final boolean isShutdown() { ++ return this.shutdown; ++ } ++ ++ @Override ++ public final boolean isTerminated() { ++ return this.shutdown; ++ } ++ ++ @Override ++ public final boolean awaitTermination(long l2, TimeUnit timeUnit) throws InterruptedException { ++ if (!this.shutdown) { ++ throw new UnsupportedOperationException(); ++ } ++ return true; ++ } ++ ++ @Override ++ public final void execute(Runnable runnable) { ++ MCUtil.asyncExecutor.execute(new ServerWorkerWrapper(runnable)); ++ } ++ }; + } + /* + @Override +diff --git a/src/main/java/wtf/etil/mirai/server/util/ServerWorkerWrapper.java b/src/main/java/wtf/etil/mirai/server/util/ServerWorkerWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4633764f3d806c7e29cbda6e85db5a3432ad0c29 +--- /dev/null ++++ b/src/main/java/wtf/etil/mirai/server/util/ServerWorkerWrapper.java +@@ -0,0 +1,24 @@ ++package wtf.etil.mirai.server.util; ++ ++import com.google.common.base.Preconditions; ++import net.minecraft.Util; ++ ++public final class ServerWorkerWrapper implements Runnable { ++ private final Runnable internalRunnable; ++ ++ public ServerWorkerWrapper(Runnable runnable) { ++ this.internalRunnable = Preconditions.checkNotNull(runnable, "internalRunnable"); ++ } ++ ++ @Override ++ public final void run() { ++ try { ++ this.internalRunnable.run(); ++ return; ++ } ++ catch (Throwable throwable) { ++ Util.onThreadException(Thread.currentThread(), throwable); ++ return; ++ } ++ } ++} +\ No newline at end of file diff --git a/patches/server/0021-Remove-sync-chunk-writes-in-server.properties.patch b/patches/server/0021-Remove-sync-chunk-writes-in-server.properties.patch new file mode 100644 index 0000000..fd0330b --- /dev/null +++ b/patches/server/0021-Remove-sync-chunk-writes-in-server.properties.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Mon, 6 Dec 2021 10:27:12 -0500 +Subject: [PATCH] Remove 'sync-chunk-writes' in server.properties + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index cea07f3898a85bf7949bdc3db3cfff7734d0ce03..760a1756cf210df2d012b7cd771f57695de54ea0 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -439,7 +439,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + BufferedWriter bufferedwriter = Files.newBufferedWriter(file); + + try { +- bufferedwriter.write(String.format("sync-chunk-writes=%s%n", dedicatedserverproperties.syncChunkWrites)); ++ //bufferedwriter.write(String.format("sync-chunk-writes=%s%n", dedicatedserverproperties.syncChunkWrites)); // JettPack - remove 'sync-chunk-writes' in server.properties + bufferedwriter.write(String.format("gamemode=%s%n", dedicatedserverproperties.gamemode)); + bufferedwriter.write(String.format("spawn-monsters=%s%n", dedicatedserverproperties.spawnMonsters)); + bufferedwriter.write(String.format("entity-broadcast-range-percentage=%d%n", dedicatedserverproperties.entityBroadcastRangePercentage)); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index ebfaa8d5de5b905bd2dd7778728b8c9939d01252..1fd7b37a7da803af0e61d5341b7574d4f4a10da3 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -111,7 +111,7 @@ public class DedicatedServerProperties extends Settings { + return Mth.clamp(integer, (int) 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag ++ this.syncChunkWrites = Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag // JettPack - remove 'sync-chunk-writes' in server.properties + this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); + this.enableStatus = this.get("enable-status", true); + this.hideOnlinePlayers = this.get("hide-online-players", false); diff --git a/patches/server/0022-Remove-Spigot-tick-limiter.patch b/patches/server/0022-Remove-Spigot-tick-limiter.patch new file mode 100644 index 0000000..7cef11d --- /dev/null +++ b/patches/server/0022-Remove-Spigot-tick-limiter.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Mon, 6 Dec 2021 10:34:33 -0500 +Subject: [PATCH] Remove Spigot tick limiter + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index af603994275c93842d2c3b6e9a7fcf0b0fbad168..f816ae7bf9a372aef8de769d4f6192e6ed9ac776 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -167,8 +167,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot +- private org.spigotmc.TickLimiter entityLimiter; +- private org.spigotmc.TickLimiter tileLimiter; ++ //private org.spigotmc.TickLimiter entityLimiter; // JettPack - remove tick limiter ++ //private org.spigotmc.TickLimiter tileLimiter; // JettPack - remove tick limiter + private int tileTickPosition; + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here +@@ -355,8 +355,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit end + timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings + this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper +- this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); +- this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); ++ //this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); // JettPack - remove tick limiter ++ //this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); // JettPack - remove tick limiter + this.chunkPacketBlockController = this.paperConfig.antiXray ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.entitySliceManager = new io.papermc.paper.world.EntitySliceManager((ServerLevel)this); // Paper + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 31a447e6e156b7b244b2ac69a9c03c09c7ed388c..c405457a775df902540602dfb4cdcd6872d35e2a 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -433,6 +433,7 @@ public class SpigotWorldConfig + this.hangingTickFrequency = this.getInt( "hanging-tick-frequency", 100 ); + } + ++ /* // JettPack start - remove tick limiter + public int tileMaxTickTime; + public int entityMaxTickTime; + private void maxTickTimes() +@@ -441,6 +442,8 @@ 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"); + } ++ */ ++ // JettPack end + + 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 4074538ea6090bf99d8ab04b1e98c2832a0e9a98..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/patches/server/0023-Add-last-tick-time-API.patch b/patches/server/0023-Add-last-tick-time-API.patch new file mode 100644 index 0000000..714c18f --- /dev/null +++ b/patches/server/0023-Add-last-tick-time-API.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ivan Pekov +Date: Sun, 27 Sep 2020 18:30:10 +0300 +Subject: [PATCH] Add last tick time API + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 1ef00b9a0b502fac69ffdd0e233c5222dd9178ff..30156d3c983f83c4d96ce089a27ff9bfed29555e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1160,6 +1160,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { + sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); + if (!hasShownMemoryWarning) { +@@ -50,4 +54,15 @@ public class TicksPerSecondCommand extends Command + return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() + + ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise + } ++ ++ // Yatopia start - Last tick time API ++ public static String formatTo(java.time.Duration duration, java.util.concurrent.TimeUnit unit) { ++ java.util.concurrent.TimeUnit nanosUnit = java.util.concurrent.TimeUnit.NANOSECONDS; ++ long nanos = duration.toNanos(); ++ long toAskedUnit = unit.convert( nanos, nanosUnit ); ++ long ms = nanosUnit.toMillis( nanos ); ++ ChatColor startingColor = ms < 40 ? ChatColor.GREEN : ( ms < 50 ) ? ChatColor.YELLOW : ChatColor.RED; ++ return startingColor.toString() + toAskedUnit + ChatColor.GOLD + org.yatopiamc.yatopia.server.util.TimeUtils.getFriendlyName( unit ); ++ } ++ // Yatopia end + } diff --git a/patches/server/0024-Configurable-flight-checks.patch b/patches/server/0024-Configurable-flight-checks.patch new file mode 100644 index 0000000..6fb24bb --- /dev/null +++ b/patches/server/0024-Configurable-flight-checks.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: l_MrBoom_l +Date: Wed, 30 Sep 2020 18:20:12 +0300 +Subject: [PATCH] Configurable flight checks + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f0e7c7c84052f29349b60ad06ec4f9ee1771f4bb..62e348c46b7a48e83a5bed5bc7bfa0878e61bc3c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -316,7 +316,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + ++this.tickCount; + this.knownMovePacketCount = this.receivedMovePacketCount; + if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger()) { +- if (++this.aboveGroundTickCount > 80) { ++ if (wtf.etil.mirai.MiraiConfig.checkFlying && ++this.aboveGroundTickCount > 80) { // Yatopia - Configurable flight checks + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); + this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; +@@ -335,7 +335,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + this.vehicleLastGoodY = this.lastVehicle.getY(); + this.vehicleLastGoodZ = this.lastVehicle.getZ(); + if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { +- if (++this.aboveGroundVehicleTickCount > 80) { ++ if (wtf.etil.mirai.MiraiConfig.checkVehicleFlying && ++this.aboveGroundVehicleTickCount > 80) { // Yatopia - Configurable flight checks + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); + this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 667eaabe95e9b44cad64dd5aba5ac693f4d726ed..a24a35f3ef2812d2951fb13439f856ee9495226b 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -194,4 +194,13 @@ public class MiraiConfig { + maxBees = getInt("max-bees-in-hive", maxBees); + } + ++ public static boolean checkFlying = true; ++ public static boolean checkVehicleFlying = true; ++ private static void flightChecks() { ++ checkFlying = getBoolean("checks.flight", checkFlying); ++ checkVehicleFlying = getBoolean("checks.vehicle-flight", checkVehicleFlying); ++ } ++ ++ ++ + } +\ No newline at end of file diff --git a/patches/server/0025-Don-t-save-Fireworks.patch b/patches/server/0025-Don-t-save-Fireworks.patch new file mode 100644 index 0000000..19cae53 --- /dev/null +++ b/patches/server/0025-Don-t-save-Fireworks.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 28 Dec 2013 21:25:06 -0500 +Subject: [PATCH] Don't save Fireworks + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +index 5a062a19bb2fc427c9391bb4731f071719b72c52..76110656d922fc734df5b08cb24eecf4dc97e38c 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +@@ -358,4 +358,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { + public boolean isAttackable() { + return false; + } ++ ++ // EMC start ++ @Override ++ public boolean shouldBeSaved() { ++ return false; ++ } ++ // EMC end + } diff --git a/patches/server/0026-Do-not-drop-items-from-Give-command.patch b/patches/server/0026-Do-not-drop-items-from-Give-command.patch new file mode 100644 index 0000000..db47180 --- /dev/null +++ b/patches/server/0026-Do-not-drop-items-from-Give-command.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 14 Jan 2016 00:49:14 -0500 +Subject: [PATCH] Do not drop items from Give command + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java +index a60a84d5df0b05cf35668df8c5f08b811bc42a9f..b76f315355407b3c4b8efb490acc4848a4a67934 100644 +--- a/src/main/java/net/minecraft/server/commands/GiveCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java +@@ -56,6 +56,7 @@ public class GiveCommand { + ItemStack itemstack = item.createItemStack(i1, false); + boolean flag = entityplayer.getInventory().add(itemstack); + ItemEntity entityitem; ++ if (true) { continue; } // EMC - never drop items + + if (flag && itemstack.isEmpty()) { + itemstack.setCount(1); diff --git a/patches/server/0027-Don-t-load-chunks-for-physics.patch b/patches/server/0027-Don-t-load-chunks-for-physics.patch new file mode 100644 index 0000000..d9ef5c0 --- /dev/null +++ b/patches/server/0027-Don-t-load-chunks-for-physics.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 Mar 2020 03:07:02 -0400 +Subject: [PATCH] Don't load chunks for physics + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index f816ae7bf9a372aef8de769d4f6192e6ed9ac776..14e4f5c376126c264d59266ddeeafff764eabc40 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -855,7 +855,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public void neighborChanged(BlockPos pos, Block sourceBlock, BlockPos neighborPos) { + if (!this.isClientSide) { +- BlockState iblockdata = this.getBlockState(pos); ++ BlockState iblockdata = this.getBlockStateIfLoaded(pos); // EMC ++ if (iblockdata == null) return; // EMC + + try { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 7f83c9390823b42fc30d04e1d3222e2825eaad50..a1231d2347de63db9e607519328063dbed62b4c9 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -987,7 +987,8 @@ public abstract class BlockBehaviour { + Direction enumdirection = aenumdirection[l]; + + blockposition_mutableblockposition.setWithOffset(pos, enumdirection); +- BlockState iblockdata = world.getBlockState(blockposition_mutableblockposition); ++ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition_mutableblockposition); // EMC ++ if (iblockdata == null) { continue; } // EMC + BlockState iblockdata1 = iblockdata.updateShape(enumdirection.getOpposite(), this.asState(), world, blockposition_mutableblockposition, pos); + + Block.updateOrDestroy(iblockdata, iblockdata1, world, blockposition_mutableblockposition, flags, maxUpdateDepth); diff --git a/patches/server/0028-Do-not-process-chat-commands-before-player-has-joine.patch b/patches/server/0028-Do-not-process-chat-commands-before-player-has-joine.patch new file mode 100644 index 0000000..2ae03c1 --- /dev/null +++ b/patches/server/0028-Do-not-process-chat-commands-before-player-has-joine.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Tue, 3 Aug 2021 10:39:44 -0500 +Subject: [PATCH] Do not process chat/commands before player has joined + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 62e348c46b7a48e83a5bed5bc7bfa0878e61bc3c..f31fc4a40d817a4115159bca39b7f907fdc765a9 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2107,6 +2107,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + private void handleChat(TextFilter.FilteredText message) { + if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales + this.send(new ClientboundChatPacket((new TranslatableComponent("chat.disabled.options")).withStyle(ChatFormatting.RED), ChatType.SYSTEM, Util.NIL_UUID)); ++ } else if (!player.didPlayerJoinEvent) { return; // EMC - do not handle chat messages before they joined + } else { + this.player.resetLastActionTime(); + String s = message.getRaw(); diff --git a/patches/server/0029-Always-convert-Lore-Name-of-an-item-to-ensure-consis.patch b/patches/server/0029-Always-convert-Lore-Name-of-an-item-to-ensure-consis.patch new file mode 100644 index 0000000..6ce9f97 --- /dev/null +++ b/patches/server/0029-Always-convert-Lore-Name-of-an-item-to-ensure-consis.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 21 Dec 2020 17:24:43 -0500 +Subject: [PATCH] Always convert Lore/Name of an item to ensure consistency + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 586ce0bec1ab2a17a3818690e47be23643cd5a48..ceea5592b2964365b533c1641fdd9ee83f821e34 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -176,9 +176,9 @@ public final class ItemStack { + if (display != null) { + if (display.contains("Name", 8)) { + String json = display.getString("Name"); +- if (json != null && json.contains("\u00A7")) { ++ if (true || json != null && json.contains("\u00A7")) { + try { +- display.put("Name", convert(json)); ++ display.put("Name", convert(json)); // Paper + } catch (com.google.gson.JsonParseException jsonparseexception) { + display.remove("Name"); + } +@@ -188,7 +188,7 @@ public final class ItemStack { + ListTag list = display.getList("Lore", 8); + for (int index = 0; index < list.size(); index++) { + String json = list.getString(index); +- if (json != null && json.contains("\u00A7")) { // Only try if it has legacy in the unparsed json ++ if (true || json != null && json.contains("\u00A7")) { // Only try if it has legacy in the unparsed json // Paper + try { + list.set(index, convert(json)); + } catch (com.google.gson.JsonParseException e) { +@@ -205,6 +205,9 @@ public final class ItemStack { + if (component instanceof TextComponent && component.getContents().contains("\u00A7") && component.getSiblings().isEmpty()) { + // Only convert if the root component is a single comp with legacy in it, don't convert already normal components + component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(component.getContents())[0]; ++ } else { ++ // pass this to legacy format and back to strip invisible components and ensure consistent modifier format ++ component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(component))[0]; + } + return net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 5607dc10dc1c9d2dbf4e3007890e5e89a175605e..c24793eeff34281194292bd83c5361a56ebea301 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -687,7 +687,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + @Overridden + void applyToItem(CompoundTag itemTag) { + if (this.hasDisplayName()) { +- this.setDisplayTag(itemTag, NAME.NBT, StringTag.valueOf(displayName)); ++ this.setDisplayTag(itemTag, NAME.NBT, StringTag.valueOf(CraftChatMessage.reencode(displayName))); // Paper + } + if (this.hasLocalizedName()) { + this.setDisplayTag(itemTag, LOCNAME.NBT, StringTag.valueOf(locName)); +@@ -779,7 +779,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + ListTag tagList = new ListTag(); + for (String value : list) { + // SPIGOT-5342 - horrible hack as 0 version does not go through the Mojang updater +- tagList.add(StringTag.valueOf(this.version <= 0 || this.version >= 1803 ? value : CraftChatMessage.fromJSONComponent(value))); // SPIGOT-4935 ++ tagList.add(StringTag.valueOf(this.version <= 0 || this.version >= 1803 ? CraftChatMessage.reencode(value) : CraftChatMessage.fromJSONComponent(value))); // SPIGOT-4935 // Paper + } + + return tagList; +@@ -882,7 +882,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + // Paper end + @Override + public final void setDisplayName(String name) { +- this.displayName = CraftChatMessage.fromStringOrNullToJSON(name); ++ this.displayName = CraftChatMessage.fromStringOrNullToJSON(CraftChatMessage.reencode(name)); // Paper + } + + // Paper start +@@ -1557,7 +1557,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + if (possiblyJsonInput) { + addTo.add(CraftChatMessage.fromJSONOrStringToJSON(entry)); + } else { +- addTo.add(CraftChatMessage.fromStringToJSON(entry)); ++ addTo.add(CraftChatMessage.fromStringToJSON(CraftChatMessage.reencode(entry))); + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +index 26d43c229caf9f8504af7071c3a61ec6da7e27ec..6e8edc80dd7dfa867901769fda82706746f1bcc5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -41,6 +41,12 @@ public final class CraftChatMessage { + return ChatColor.getByChar(format.code); + } + ++ // Paper start ++ public static String reencode(String string) { ++ return CraftChatMessage.fromComponent(CraftChatMessage.fromString(string)[0]); ++ } ++ // Paper end ++ + private static final class StringMessage { + private static final Pattern INCREMENTAL_PATTERN = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-orx])|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " \\n]|$))))|(\\n)", Pattern.CASE_INSENSITIVE); + // Separate pattern with no group 3, new lines are part of previous string diff --git a/patches/server/0030-Bound-villager-trade-maxUses-by-16.patch b/patches/server/0030-Bound-villager-trade-maxUses-by-16.patch new file mode 100644 index 0000000..d6125b3 --- /dev/null +++ b/patches/server/0030-Bound-villager-trade-maxUses-by-16.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Sat, 6 Jun 2020 21:07:15 -0500 +Subject: [PATCH] Bound villager trade maxUses by 16 + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +index c9cb0717c2793acd5b5870a6cc4d672d69a40026..40385c7b267ecd1e00e4daa652b94533cc8febea 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +@@ -53,7 +53,7 @@ public class MerchantOffer { + this.result = ItemStack.of(nbt.getCompound("sell")); + this.uses = nbt.getInt("uses"); + if (nbt.contains("maxUses", 99)) { +- this.maxUses = nbt.getInt("maxUses"); ++ this.maxUses = Math.min(16, nbt.getInt("maxUses")); // EMC + } else { + this.maxUses = 4; + } diff --git a/patches/server/0031-Mitigate-ConcurrentModificationException-by-copying-.patch b/patches/server/0031-Mitigate-ConcurrentModificationException-by-copying-.patch new file mode 100644 index 0000000..ea14856 --- /dev/null +++ b/patches/server/0031-Mitigate-ConcurrentModificationException-by-copying-.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Mon, 6 Sep 2021 02:05:16 -0500 +Subject: [PATCH] Mitigate ConcurrentModificationException by copying + entitySection access + +Original code by Starlis, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/starlis/empirecraft + +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index 3d5f38a1ca0206e5bcae4ed49d3df7ce0967f722..649df137ac36a62e53b42b7675c606e9e57795b2 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -264,23 +264,23 @@ public class PersistentEntitySectionManager implements A + if (flag2 && !flag3) { + entitysection.getEntities().filter((entityaccess) -> { + return !entityaccess.isAlwaysTicking(); +- }).forEach(this::stopTicking); ++ }).collect(Collectors.toList()).forEach(this::stopTicking); // EMC + } + + if (flag && !flag1) { + entitysection.getEntities().filter((entityaccess) -> { + return !entityaccess.isAlwaysTicking(); +- }).forEach(this::stopTracking); ++ }).collect(Collectors.toList()).forEach(this::stopTracking); // EMC + } else if (!flag && flag1) { + entitysection.getEntities().filter((entityaccess) -> { + return !entityaccess.isAlwaysTicking(); +- }).forEach(this::startTracking); ++ }).collect(Collectors.toList()).forEach(this::startTracking); // EMC + } + + if (!flag2 && flag3) { + entitysection.getEntities().filter((entityaccess) -> { + return !entityaccess.isAlwaysTicking(); +- }).forEach(this::startTicking); ++ }).collect(Collectors.toList()).forEach(this::startTicking); // EMC + } + + }); diff --git a/patches/server/0032-Fix-cow-rotation-when-shearing-mooshroom.patch b/patches/server/0032-Fix-cow-rotation-when-shearing-mooshroom.patch new file mode 100644 index 0000000..66bfdbc --- /dev/null +++ b/patches/server/0032-Fix-cow-rotation-when-shearing-mooshroom.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 May 2019 23:53:16 -0500 +Subject: [PATCH] Fix cow rotation when shearing mooshroom + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +index 6d4dc4b09278eca509f86655c6562fb4b05d5069..53a081af5b6a386ebde1765c7cedff372ffbd7b2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +@@ -180,7 +180,13 @@ public class MushroomCow extends Cow implements Shearable { + + entitycow.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + entitycow.setHealth(this.getHealth()); ++ // Purpur start ++ entitycow.copyPosition(this); + entitycow.yBodyRot = this.yBodyRot; ++ entitycow.setYHeadRot(this.getYHeadRot()); ++ entitycow.yRotO = this.yRotO; ++ entitycow.xRotO = this.xRotO; ++ // Purpur end + if (this.hasCustomName()) { + entitycow.setCustomName(this.getCustomName()); + entitycow.setCustomNameVisible(this.isCustomNameVisible()); diff --git a/patches/server/0033-End-gateway-should-check-if-entity-can-use-portal.patch b/patches/server/0033-End-gateway-should-check-if-entity-can-use-portal.patch new file mode 100644 index 0000000..44a7c56 --- /dev/null +++ b/patches/server/0033-End-gateway-should-check-if-entity-can-use-portal.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 21 Mar 2020 18:33:05 -0500 +Subject: [PATCH] End gateway should check if entity can use portal + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +index 4e435a16a16c703a2978f4dc82606b8710742670..a952f19530983ca9a72969ad25f9565a2a03688a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -177,6 +177,7 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { + + public static void teleportEntity(Level world, BlockPos pos, BlockState state, Entity entity, TheEndGatewayBlockEntity blockEntity) { + if (world instanceof ServerLevel && !blockEntity.isCoolingDown()) { ++ if (!entity.canChangeDimensions()) return; // Purpur + ServerLevel worldserver = (ServerLevel) world; + + blockEntity.teleportCooldown = 100; diff --git a/patches/server/0034-Arrows-should-not-reset-despawn-counter.patch b/patches/server/0034-Arrows-should-not-reset-despawn-counter.patch new file mode 100644 index 0000000..b72263e --- /dev/null +++ b/patches/server/0034-Arrows-should-not-reset-despawn-counter.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 4 Nov 2020 13:12:50 -0600 +Subject: [PATCH] Arrows should not reset despawn counter + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 53d0024daf6963ac4dab575666b0d6a74a39a958..26cf6291d64561a589bc6c357a90bcaffad89ded 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -321,7 +321,7 @@ public abstract class AbstractArrow extends Projectile { + Vec3 vec3d = this.getDeltaMovement(); + + this.setDeltaMovement(vec3d.multiply((double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F))); +- this.life = 0; ++ //this.life = 0; // Purpur - do not reset despawn counter + } + + @Override diff --git a/patches/server/0035-Spread-out-and-optimise-player-list-ticksSpread-out-.patch b/patches/server/0035-Spread-out-and-optimise-player-list-ticksSpread-out-.patch new file mode 100644 index 0000000..b4ecd1f --- /dev/null +++ b/patches/server/0035-Spread-out-and-optimise-player-list-ticksSpread-out-.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: James Lyne +Date: Mon, 7 Dec 2020 17:52:36 +0000 +Subject: [PATCH] Spread out and optimise player list ticksSpread out and + optimise player list ticks + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ba8e58fe70f16dbc0b64a92264d29c91461d2cdf..93b97bb18f13d533d228d5912d08fbcce664ac06 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1028,22 +1028,21 @@ public abstract class PlayerList { + } + + public void tick() { +- if (++this.sendAllPlayerInfoIn > 600) { +- // CraftBukkit start +- for (int i = 0; i < this.players.size(); ++i) { +- final ServerPlayer target = (ServerPlayer) this.players.get(i); +- +- target.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY, this.players.stream().filter(new Predicate() { +- @Override +- public boolean apply(ServerPlayer input) { +- return target.getBukkitEntity().canSee(input.getBukkitEntity()); +- } +- }).collect(Collectors.toList()))); ++ // Purpur start ++ if (this.sendAllPlayerInfoIn < this.players.size()) { ++ final org.bukkit.craftbukkit.entity.CraftPlayer target = this.players.get(this.sendAllPlayerInfoIn).getBukkitEntity(); ++ final List list = new java.util.ArrayList<>(); ++ for (ServerPlayer player : this.players) { ++ if (target.canSee(player.getUUID())) { ++ list.add(player); ++ } + } +- // CraftBukkit end ++ target.getHandle().connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY, list)); ++ } ++ if (++this.sendAllPlayerInfoIn > 600) { + this.sendAllPlayerInfoIn = 0; + } +- ++ // Purpur end + } + + public void broadcastAll(Packet packet) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ce78e024244c14530270b8276e5b0fd853f0a110..170c7469ac6ed343a8e4e43f03a972d106bcc640 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1732,7 +1732,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean canSee(org.bukkit.entity.Entity entity) { +- return !this.hiddenEntities.containsKey(entity.getUniqueId()); ++ // Purpur start ++ return this.canSee(entity.getUniqueId()); ++ } ++ ++ public boolean canSee(UUID uuid) { ++ return !this.hiddenEntities.containsKey(uuid); ++ // Purpur end + } + + @Override diff --git a/patches/server/0036-Fix-vanilla-command-permission-handler.patch b/patches/server/0036-Fix-vanilla-command-permission-handler.patch new file mode 100644 index 0000000..df6c444 --- /dev/null +++ b/patches/server/0036-Fix-vanilla-command-permission-handler.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 28 Mar 2020 01:51:32 -0500 +Subject: [PATCH] Fix vanilla command permission handler + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index d65defd5fc54086a969c568b93dfb05f40dd5a44..dad5564caa9f0211d8f8321a80dcec7e2cc6abc4 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -34,6 +34,7 @@ public abstract class CommandNode implements Comparable> { + private final RedirectModifier modifier; + private final boolean forks; + private Command command; ++ private String permission = null; public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } // Purpur + // CraftBukkit start + public void removeCommand(String name) { + this.children.remove(name); +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index 4525fb3bc9b137bce3b59310a8aecca96d6ad5ba..75aeceb76b15a4c9cc7cffc35d15d76509c5620a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -94,6 +94,7 @@ public final class VanillaCommandWrapper extends BukkitCommand { + } + + public static String getPermission(CommandNode vanillaCommand) { ++ if (vanillaCommand.getPermission() != null) return vanillaCommand.getPermission(); // Purpur + // Paper start + final String commandName; + if (vanillaCommand.getRedirect() == null) { diff --git a/patches/server/0037-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch b/patches/server/0037-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch new file mode 100644 index 0000000..b7ce1db --- /dev/null +++ b/patches/server/0037-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jan 2020 01:23:22 -0600 +Subject: [PATCH] MC-147659 - Fix non black cats spawning in swamp huts + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +index 0df98efa509518a14d6f76dca9356cee2655a703..a300049a515092b46292c6b05bf057c7b6ce9f62 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +@@ -91,8 +91,9 @@ public class CatSpawner implements CustomSpawner { + if (cat == null) { + return 0; + } else { ++ cat.moveTo(pos, 0.0F, 0.0F); // Purpur + cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), MobSpawnType.NATURAL, (SpawnGroupData)null, (CompoundTag)null); +- cat.moveTo(pos, 0.0F, 0.0F); ++ //cat.moveTo(pos, 0.0F, 0.0F); // Purpur - move up - fixes non black cat types spawning inside swamp huts + world.addFreshEntityWithPassengers(cat); + return 1; + } diff --git a/patches/server/0038-Fix-rotating-UP-DOWN-CW-and-CCW.patch b/patches/server/0038-Fix-rotating-UP-DOWN-CW-and-CCW.patch new file mode 100644 index 0000000..79bc7a6 --- /dev/null +++ b/patches/server/0038-Fix-rotating-UP-DOWN-CW-and-CCW.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 6 Jan 2021 02:19:29 -0600 +Subject: [PATCH] Fix rotating UP/DOWN CW and CCW + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index 4be4d9e6611dc3b7a34dbf2f5d6990bc0980ee63..92789a27aa2978ac323d4cdafa82391998cf0ffa 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -247,6 +247,12 @@ public enum Direction implements StringRepresentable { + case EAST: + var10000 = SOUTH; + break; ++ // Purpur start ++ case UP: ++ return UP; ++ case DOWN: ++ return DOWN; ++ // Purpur end + default: + throw new IllegalStateException("Unable to get Y-rotated facing of " + this); + } +@@ -359,6 +365,12 @@ public enum Direction implements StringRepresentable { + case EAST: + var10000 = NORTH; + break; ++ // Purpur start ++ case UP: ++ return UP; ++ case DOWN: ++ return DOWN; ++ // Purpur end + default: + throw new IllegalStateException("Unable to get CCW facing of " + this); + } diff --git a/patches/server/0039-Disable-outdated-build-check.patch b/patches/server/0039-Disable-outdated-build-check.patch new file mode 100644 index 0000000..2071a24 --- /dev/null +++ b/patches/server/0039-Disable-outdated-build-check.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 15 Dec 2019 12:53:59 -0600 +Subject: [PATCH] Disable outdated build check + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 298c20e4abc04a8bdc93719c282d0b4ef4560819..29ac511169182f399c903638b941796dd5d08e7e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -265,7 +265,7 @@ public class Main { + System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper + } + +- if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { ++ if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur + Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper + + Calendar deadline = Calendar.getInstance(); diff --git a/patches/server/0040-Fix-outdated-server-showing-in-ping-before-server-fu.patch b/patches/server/0040-Fix-outdated-server-showing-in-ping-before-server-fu.patch new file mode 100644 index 0000000..b5bce7a --- /dev/null +++ b/patches/server/0040-Fix-outdated-server-showing-in-ping-before-server-fu.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 4 Jun 2019 15:50:08 -0500 +Subject: [PATCH] Fix 'outdated server' showing in ping before server fully + boots + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +index 4fa79d37ff4e70c61672cce7c55257c46232c026..df43d0a0f3d1e34baabae150cbf01a008eb5a588 100644 +--- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +@@ -153,6 +153,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene + this.connection.send(new ClientboundStatusResponsePacket(ping)); + // CraftBukkit end + */ ++ if (this.server.getStatus().getVersion() == null) return; // Purpur - do not respond to pings before we know the protocol version + com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.server, this.connection); + // Paper end + } diff --git a/patches/server/0041-Dont-send-useless-entity-packets.patch b/patches/server/0041-Dont-send-useless-entity-packets.patch new file mode 100644 index 0000000..ec55586 --- /dev/null +++ b/patches/server/0041-Dont-send-useless-entity-packets.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 6 Jul 2019 17:00:04 -0500 +Subject: [PATCH] Dont send useless entity packets + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 44560edcf339c1749b9df78e6e57075499d5813d..2d7e884d25f352e0a05e9aefb0ff6e296e73c361 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -190,6 +190,7 @@ public class ServerEntity { + this.teleportDelay = 0; + packet1 = new ClientboundTeleportEntityPacket(this.entity); + } ++ if (wtf.etil.mirai.MiraiConfig.dontSendUselessEntityPackets && isUselessPacket(packet1)) packet1 = null; // Purpur + } + + if ((this.trackDelta || this.entity.hasImpulse || this.entity instanceof LivingEntity && ((LivingEntity) this.entity).isFallFlying()) && this.tickCount > 0) { +@@ -258,6 +259,22 @@ public class ServerEntity { + + } + ++ // Purpur start ++ private boolean isUselessPacket(Packet possibleUselessPacket) { ++ if (possibleUselessPacket instanceof ClientboundMoveEntityPacket) { ++ ClientboundMoveEntityPacket packet = (ClientboundMoveEntityPacket) possibleUselessPacket; ++ if (possibleUselessPacket instanceof ClientboundMoveEntityPacket.Pos) { ++ return packet.getXa() == 0 && packet.getYa() == 0 && packet.getZa() == 0; ++ } else if (possibleUselessPacket instanceof ClientboundMoveEntityPacket.PosRot) { ++ return packet.getXa() == 0 && packet.getYa() == 0 && packet.getZa() == 0 && packet.getyRot() == 0 && packet.getxRot() == 0; ++ } else if (possibleUselessPacket instanceof ClientboundMoveEntityPacket.Rot) { ++ return packet.getyRot() == 0 && packet.getxRot() == 0; ++ } ++ } ++ return false; ++ } ++ // Purpur end ++ + public void removePairing(ServerPlayer player) { + this.entity.stopSeenByPlayer(player); + player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()})); +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index a24a35f3ef2812d2951fb13439f856ee9495226b..8be485f3f68f42e09058fc543485137e03056266 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -201,6 +201,11 @@ public class MiraiConfig { + checkVehicleFlying = getBoolean("checks.vehicle-flight", checkVehicleFlying); + } + ++ public static boolean dontSendUselessEntityPackets = true; ++ private static void dontSendUselessEntityPackets() { ++ dontSendUselessEntityPackets = getBoolean("dont-send-useless-entity-packets", dontSendUselessEntityPackets); ++ } ++ + + + } +\ No newline at end of file diff --git a/patches/server/0042-Skip-events-if-there-s-no-listeners.patch b/patches/server/0042-Skip-events-if-there-s-no-listeners.patch new file mode 100644 index 0000000..52dbf47 --- /dev/null +++ b/patches/server/0042-Skip-events-if-there-s-no-listeners.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 4 Apr 2020 03:07:59 -0500 +Subject: [PATCH] Skip events if there's no listeners + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index b66afde6851ceaeccb84aea00cdc333dfbf3d4b0..c79c89bab8972a26c7a48f63ad20ac34b0fc8750 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -391,6 +391,7 @@ public class Commands { + } + + private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { ++ if (PlayerCommandSendEvent.getHandlerList().getRegisteredListeners().length > 0) { // Purpur - skip all this crap if there's nothing listening + // Paper end - Async command map building + new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); +@@ -403,6 +404,7 @@ public class Commands { + } + } + // CraftBukkit end ++ } // Purpur - skip event + player.connection.send(new ClientboundCommandsPacket(rootcommandnode)); + } + diff --git a/patches/server/0043-Fix-the-dead-lagging-the-server.patch b/patches/server/0043-Fix-the-dead-lagging-the-server.patch new file mode 100644 index 0000000..505760d --- /dev/null +++ b/patches/server/0043-Fix-the-dead-lagging-the-server.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 6 Mar 2020 13:37:26 -0600 +Subject: [PATCH] Fix the dead lagging the server + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 38cb136d34cbc83dde1c766196f60a30d38005e3..5744b25104d3b73cedda3c88945dfce1db265b1e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1782,6 +1782,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); + this.setYHeadRot(yaw); // Paper - Update head rotation ++ if (valid && !this.isRemoved()) level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit // Paper // Purpur + } + + public void absMoveTo(double x, double y, double z) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index f646545ee7178717c1e3bb48e98fd2af2af745e0..8e5c8d1e59af15a0b28a8e28e60e50d2ec3b23f9 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2935,7 +2935,7 @@ public abstract class LivingEntity extends Entity { + } + } + +- this.aiStep(); ++ if (!this.isRemoved()) this.aiStep(); // Purpur + double d0 = this.getX() - this.xo; + double d1 = this.getZ() - this.zo; + float f = (float) (d0 * d0 + d1 * d1); diff --git a/patches/server/0044-Alternative-Keepalive-Handling.patch b/patches/server/0044-Alternative-Keepalive-Handling.patch new file mode 100644 index 0000000..bea2715 --- /dev/null +++ b/patches/server/0044-Alternative-Keepalive-Handling.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 11 Oct 2019 00:17:39 -0500 +Subject: [PATCH] Alternative Keepalive Handling + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f31fc4a40d817a4115159bca39b7f907fdc765a9..de99c84622ff69f3f8707d92aacd5e9a1d980ccf 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -227,6 +227,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + private long keepAliveTime = Util.getMillis(); + private boolean keepAlivePending; + private long keepAliveChallenge; ++ private java.util.List keepAlives = new java.util.ArrayList<>(); // Purpur + // CraftBukkit start - multithreaded fields + private final AtomicInteger chatSpamTickCount = new AtomicInteger(); + private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits +@@ -356,6 +357,21 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + long currentTime = Util.getMillis(); + long elapsedTime = currentTime - this.keepAliveTime; + ++ // Purpur start ++ if (wtf.etil.mirai.MiraiConfig.useAlternateKeepAlive) { ++ if (elapsedTime >= 1000L) { // 1 second ++ if (!processedDisconnect && keepAlives.size() > KEEPALIVE_LIMIT) { ++ LOGGER.warn("{} was kicked due to keepalive timeout!", player.getName()); ++ disconnect(new TranslatableComponent("disconnect.timeout")); ++ } else { ++ keepAliveTime = currentTime; // hijack this field for 1 second intervals ++ keepAlives.add(currentTime); // currentTime is ID ++ send(new ClientboundKeepAlivePacket(currentTime)); ++ } ++ } ++ } else ++ // Purpur end ++ + if (this.keepAlivePending) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info +@@ -3137,6 +3153,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + + @Override + public void handleKeepAlive(ServerboundKeepAlivePacket packet) { ++ // Purpur start ++ if (wtf.etil.mirai.MiraiConfig.useAlternateKeepAlive) { ++ long id = packet.getId(); ++ if (keepAlives.size() > 0 && keepAlives.contains(id)) { ++ int ping = (int) (Util.getMillis() - id); ++ player.latency = (player.latency * 3 + ping) / 4; ++ keepAlives.clear(); // we got a valid response, lets roll with it and forget the rest ++ } ++ } else ++ // Purpur end + //PlayerConnectionUtils.ensureMainThread(packetplayinkeepalive, this, this.player.getWorldServer()); // CraftBukkit // Paper - This shouldn't be on the main thread + if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { + int i = (int) (Util.getMillis() - this.keepAliveTime); +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 8be485f3f68f42e09058fc543485137e03056266..ee0b35e703b1930b32a01b5fa532fdbaf60127c4 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -206,6 +206,11 @@ public class MiraiConfig { + dontSendUselessEntityPackets = getBoolean("dont-send-useless-entity-packets", dontSendUselessEntityPackets); + } + ++ public static boolean useAlternateKeepAlive = true; ++ private static void useAlternateKeepAlive() { ++ useAlternateKeepAlive = getBoolean("use-alternate-keepalive", useAlternateKeepAlive); ++ } ++ + + + } +\ No newline at end of file diff --git a/patches/server/0045-Logger-settings-suppressing-pointless-logs.patch b/patches/server/0045-Logger-settings-suppressing-pointless-logs.patch new file mode 100644 index 0000000..a148b3c --- /dev/null +++ b/patches/server/0045-Logger-settings-suppressing-pointless-logs.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 19 Oct 2019 00:52:12 -0500 +Subject: [PATCH] Logger settings (suppressing pointless logs) + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index b16287a47870978706734b928b87f2357e91e3a1..5a80d307487fb824e7b59ac188760bafd06871e1 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -186,6 +186,7 @@ public class PlayerAdvancements { + if (advancement == null) { + // CraftBukkit start + if (entry.getKey().getNamespace().equals("minecraft")) { ++ if (!wtf.etil.mirai.MiraiConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur + PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", entry.getKey(), this.file); + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/stats/ServerRecipeBook.java b/src/main/java/net/minecraft/stats/ServerRecipeBook.java +index d13ed3069e944d138442ea440ac3eaf8d44c18d3..c19367bdad3997aa7aac609762f9cf731433adfc 100644 +--- a/src/main/java/net/minecraft/stats/ServerRecipeBook.java ++++ b/src/main/java/net/minecraft/stats/ServerRecipeBook.java +@@ -122,6 +122,7 @@ public class ServerRecipeBook extends RecipeBook { + Optional> optional = recipeManager.byKey(minecraftkey); + + if (!optional.isPresent()) { ++ if (!wtf.etil.mirai.MiraiConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur + ServerRecipeBook.LOGGER.error("Tried to load unrecognized recipe: {} removed now.", minecraftkey); + } else { + handler.accept((Recipe) optional.get()); +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +index b2a15c986c7500a0ce227a54cb61ec3f5378f6f3..d243af83f3159d8a4d56d08dab022b317cc5687e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +@@ -255,6 +255,7 @@ public final class CraftLegacy { + } + + static { ++ if (!wtf.etil.mirai.MiraiConfig.loggerSuppressInitLegacyMaterialError) // Purpur + System.err.println("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); + if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) { + new Exception().printStackTrace(); +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index ee0b35e703b1930b32a01b5fa532fdbaf60127c4..ede41bc4ab49065cc06e66189610791cab01b39d 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -211,6 +211,15 @@ public class MiraiConfig { + useAlternateKeepAlive = getBoolean("use-alternate-keepalive", useAlternateKeepAlive); + } + ++ public static boolean loggerSuppressInitLegacyMaterialError = true; ++ public static boolean loggerSuppressIgnoredAdvancementWarnings = true; ++ public static boolean loggerSuppressUnrecognizedRecipeErrors = true; ++ private static void loggerSettings() { ++ loggerSuppressInitLegacyMaterialError = getBoolean("logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError); ++ loggerSuppressIgnoredAdvancementWarnings = getBoolean("logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings); ++ loggerSuppressUnrecognizedRecipeErrors = getBoolean("logger.suppress-unrecognized-recipe-errors", loggerSuppressUnrecognizedRecipeErrors); ++ } ++ + + + } +\ No newline at end of file diff --git a/patches/server/0046-Add-5-second-tps-average-in-tps.patch b/patches/server/0046-Add-5-second-tps-average-in-tps.patch new file mode 100644 index 0000000..3442f47 --- /dev/null +++ b/patches/server/0046-Add-5-second-tps-average-in-tps.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 28 Jul 2019 01:27:37 -0500 +Subject: [PATCH] Add 5 second tps average in /tps + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +index fa56cd09102a89692b42f1d14257990508c5c720..f9251183df72ddc56662fd3f02acf21641a2200c 100644 +--- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -58,7 +58,7 @@ public class RAMDetails extends JList { + GraphData data = RAMGraph.DATA.peekLast(); + Vector vector = new Vector<>(); + +- double[] tps = new double[] {server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; ++ double[] tps = new double[] {server.tps5s.getAverage(), server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; // Purpur + String[] tpsAvg = new String[tps.length]; + + for ( int g = 0; g < tps.length; g++) { +@@ -67,7 +67,7 @@ public class RAMDetails extends JList { + vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); + vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); + vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.tickTimes)) + " ms"); +- vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); ++ vector.add("TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg)); // Purpur + + setListData(vector); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 30156d3c983f83c4d96ce089a27ff9bfed29555e..282fc59c3280d5d8478cb9897073d9d73996420b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -302,7 +302,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 14 Jan 2021 16:48:10 -0600 +Subject: [PATCH] Fix stuck in portals + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 1a9afa43a953e5634edb23ef617ead4b329ae89f..bf42644d2053de4e3c7b9e531b18230b07f094c5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1145,6 +1145,7 @@ public class ServerPlayer extends Player { + playerlist.sendPlayerPermissionLevel(this); + worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + this.unsetRemoved(); ++ this.portalPos = net.minecraft.server.MCUtil.toBlockPosition(exit); // Purpur + + // CraftBukkit end + this.setLevel(worldserver); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5744b25104d3b73cedda3c88945dfce1db265b1e..0b28025545bb26aff4a91fe3b78e4fc361ecf4e2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2766,12 +2766,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return Vec3.directionFromRotation(this.getRotationVector()); + } + ++ public BlockPos portalPos = BlockPos.ZERO; // Purpur + public void handleInsidePortal(BlockPos pos) { + if (this.isOnPortalCooldown()) { ++ if (!(wtf.etil.mirai.MiraiConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(portalPos))) // Purpur + this.setPortalCooldown(); + } else { + if (!this.level.isClientSide && !pos.equals(this.portalEntrancePos)) { + this.portalEntrancePos = pos.immutable(); ++ portalPos = BlockPos.ZERO; // Purpur + } + + this.isInsidePortal = true; +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index ede41bc4ab49065cc06e66189610791cab01b39d..bee288d0735af17571902203936c7eb7025184f0 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -220,6 +220,11 @@ public class MiraiConfig { + loggerSuppressUnrecognizedRecipeErrors = getBoolean("logger.suppress-unrecognized-recipe-errors", loggerSuppressUnrecognizedRecipeErrors); + } + ++ public static boolean playerFixStuckPortal = false; ++ private static void FixStuckPortal() { ++ playerFixStuckPortal = getBoolean("player-fix-stuck-in-portal", playerFixStuckPortal); ++ } ++ + + + } +\ No newline at end of file diff --git a/patches/server/0048-Entities-can-use-portals-configuration.patch b/patches/server/0048-Entities-can-use-portals-configuration.patch new file mode 100644 index 0000000..cc9c16f --- /dev/null +++ b/patches/server/0048-Entities-can-use-portals-configuration.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 17 Aug 2020 19:32:05 -0500 +Subject: [PATCH] Entities can use portals configuration + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0b28025545bb26aff4a91fe3b78e4fc361ecf4e2..651baec777f02038a775bd70e9f3e550befaa773 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2771,7 +2771,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (this.isOnPortalCooldown()) { + if (!(wtf.etil.mirai.MiraiConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(portalPos))) // Purpur + this.setPortalCooldown(); +- } else { ++ } else if (wtf.etil.mirai.MiraiConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur + if (!this.level.isClientSide && !pos.equals(this.portalEntrancePos)) { + this.portalEntrancePos = pos.immutable(); + portalPos = BlockPos.ZERO; // Purpur +@@ -3438,7 +3438,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public boolean canChangeDimensions() { +- return isAlive() && valid; // Paper ++ return isAlive() && valid && (wtf.etil.mirai.MiraiConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Paper // Purpur + } + + public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index bee288d0735af17571902203936c7eb7025184f0..a1d88b8cadf6c193deb69bb577936ebd69e63b2a 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -225,6 +225,9 @@ public class MiraiConfig { + playerFixStuckPortal = getBoolean("player-fix-stuck-in-portal", playerFixStuckPortal); + } + +- ++ public static boolean entitiesCanUsePortals = true; ++ private static void entitiesPortal() { ++ entitiesCanUsePortals = getBoolean("entities-can-use-portals", entitiesCanUsePortals); ++ } + + } +\ No newline at end of file diff --git a/patches/server/0049-PaperPR-Optimize-VarInts.patch b/patches/server/0049-PaperPR-Optimize-VarInts.patch new file mode 100644 index 0000000..d96f715 --- /dev/null +++ b/patches/server/0049-PaperPR-Optimize-VarInts.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: astei +Date: Tue, 30 Nov 2021 19:39:21 +0100 +Subject: [PATCH] PaperPR Optimize VarInts + + +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index 896a4237f871d46cf39b0721e909c2cc3b5fc728..e2a0a7fd5afd8cd3467dab049695956c381fb3cd 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -65,19 +65,22 @@ public class FriendlyByteBuf extends ByteBuf { + public java.util.Locale adventure$locale; // Paper + public static final short MAX_STRING_LENGTH = 32767; + public static final int MAX_COMPONENT_STRING_LENGTH = 262144; ++ // Paper start - Optimize VarInts ++ private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; ++ static { ++ for (int i = 0; i <= 32; ++i) { ++ VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d); ++ } ++ VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0. ++ } ++ // Paper end - Optimize VarInts + + public FriendlyByteBuf(ByteBuf parent) { + this.source = parent; + } + + public static int getVarIntSize(int value) { +- for (int j = 1; j < 5; ++j) { +- if ((value & -1 << j * 7) == 0) { +- return j; +- } +- } +- +- return 5; ++ return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)]; // Paper - Optimize VarInts + } + + public static int getVarLongSize(long value) { +@@ -420,7 +423,23 @@ public class FriendlyByteBuf extends ByteBuf { + return new UUID(this.readLong(), this.readLong()); + } + ++ // Paper start - Optimize VarInts + public FriendlyByteBuf writeVarInt(int value) { ++ // Peel the one and two byte count cases explicitly as they are the most common VarInt sizes ++ // that the proxy will write, to improve inlining. ++ if ((value & (0xFFFFFFFF << 7)) == 0) { ++ writeByte(value); ++ } else if ((value & (0xFFFFFFFF << 14)) == 0) { ++ int w = (value & 0x7F | 0x80) << 8 | (value >>> 7); ++ writeShort(w); ++ } else { ++ writeVarInt_(value); ++ } ++ return this; ++ } ++ ++ public FriendlyByteBuf writeVarInt_(int value) { ++ // Paper end - Optimize VarInts + while ((value & -128) != 0) { + this.writeByte(value & 127 | 128); + value >>>= 7; diff --git a/patches/server/0050-PaperPR-Use-DataConverter-for-itemstack-entity-deser.patch b/patches/server/0050-PaperPR-Use-DataConverter-for-itemstack-entity-deser.patch new file mode 100644 index 0000000..7b363bd --- /dev/null +++ b/patches/server/0050-PaperPR-Use-DataConverter-for-itemstack-entity-deser.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic <15055071+Machine-Maker@users.noreply.github.com> +Date: Fri, 10 Dec 2021 08:15:36 -0500 +Subject: [PATCH] PaperPR Use DataConverter for itemstack/entity + deserialization + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 1e74799bb012ca45769a3fd103f0b24eba0a9a99..61ee37d1c81274f58922fde4138536b10847508b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -453,8 +453,8 @@ public final class CraftMagicNumbers implements UnsafeValues { + + CompoundTag compound = deserializeNbtFromBytes(data); + int dataVersion = compound.getInt("DataVersion"); +- Dynamic converted = DataFixers.getDataFixer().update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); +- return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue())); ++ compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK, compound, dataVersion, getDataVersion()); // Paper - rewrite dataconverter ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); + } + + @Override +@@ -474,8 +474,7 @@ public final class CraftMagicNumbers implements UnsafeValues { + + CompoundTag compound = deserializeNbtFromBytes(data); + int dataVersion = compound.getInt("DataVersion"); +- Dynamic converted = DataFixers.getDataFixer().update(References.ENTITY_TREE, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); +- compound = (CompoundTag) converted.getValue(); ++ compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY, compound, dataVersion, getDataVersion()); // Paper - rewrite dataconverter + if (!preserveUUID) compound.remove("UUID"); // Generate a new UUID so we don't have to worry about deserializing the same entity twice + return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) + .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); diff --git a/patches/server/0051-lithium-replace-AI-goal-set-with-optimized-collectio.patch b/patches/server/0051-lithium-replace-AI-goal-set-with-optimized-collectio.patch new file mode 100644 index 0000000..5a416ac --- /dev/null +++ b/patches/server/0051-lithium-replace-AI-goal-set-with-optimized-collectio.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sun, 12 Dec 2021 16:41:06 -0500 +Subject: [PATCH] lithium: replace AI goal set with optimized collection + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 289b78ca3e587ffd42a920b99cbe5078a0d787a4..2834196fa116bdd629adaac010b30e980719d653 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -12,6 +12,7 @@ import java.util.function.Supplier; + import java.util.stream.Stream; + import net.minecraft.util.profiling.ProfilerFiller; + import org.slf4j.Logger; ++import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; // Lithium + + public class GoalSelector { + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -27,7 +28,7 @@ public class GoalSelector { + } + }; + private final Map lockedFlags = new EnumMap<>(Goal.Flag.class); +- public final Set availableGoals = Sets.newLinkedHashSet(); ++ public final Set availableGoals = new ObjectLinkedOpenHashSet<>(); // Lithium - replace AI goal set with optimized collection + private final Supplier profiler; + private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. + private final com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector diff --git a/patches/server/0052-vmp-skip-entity-move-if-movement-is-zero.patch b/patches/server/0052-vmp-skip-entity-move-if-movement-is-zero.patch new file mode 100644 index 0000000..f558a89 --- /dev/null +++ b/patches/server/0052-vmp-skip-entity-move-if-movement-is-zero.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sun, 12 Dec 2021 17:19:00 -0500 +Subject: [PATCH] vmp: skip entity move if movement is zero + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/VMP-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 651baec777f02038a775bd70e9f3e550befaa773..adc4a19039b56de81053e746580dbef9c1fe9c93 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -238,6 +238,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public float yRotO; + public float xRotO; + private AABB bb; ++ private boolean boundingBoxChanged = false; // Mirai - vmp: skip entity move if movement is zero + public boolean onGround; + public boolean horizontalCollision; + public boolean verticalCollision; +@@ -969,6 +970,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + // Paper end - detailed watchdog information + + public void move(MoverType movementType, Vec3 movement) { ++ // Mirai start - vmp: skip entity move if movement is zero ++ if (!boundingBoxChanged && movement.equals(Vec3.ZERO)) { ++ boundingBoxChanged = false; ++ return; ++ } ++ // Mirai end + // Paper start - detailed watchdog information + io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); + synchronized (this.posLock) { +@@ -3643,6 +3650,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public final void setBoundingBox(AABB boundingBox) { ++ if (!this.bb.equals(boundingBox)) boundingBoxChanged = true; // Mirai - vmp: skip entity move if movement is zero + // CraftBukkit start - block invalid bounding boxes + double minX = boundingBox.minX, + minY = boundingBox.minY, diff --git a/patches/server/0053-Optimize-inventory-API-item-handling.patch b/patches/server/0053-Optimize-inventory-API-item-handling.patch new file mode 100644 index 0000000..978aaad --- /dev/null +++ b/patches/server/0053-Optimize-inventory-API-item-handling.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 24 Nov 2019 23:00:58 +0100 +Subject: [PATCH] Optimize inventory API item handling + +Original code by Minebench, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/Minebench/Origami + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index 30ac442049088200e9ab77a561c59cbc58aaa28f..bdacb0098c93be63693218544b6dc1a2937cdb3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -273,11 +273,13 @@ public class CraftInventory implements Inventory { + } + + private int firstPartial(ItemStack item) { +- ItemStack[] inventory = this.getStorageContents(); +- ItemStack filteredItem = CraftItemStack.asCraftCopy(item); + if (item == null) { + return -1; + } ++ // Origami start - Optimize inventory API (moved down from before null check) ++ ItemStack[] inventory = this.getStorageContents(); ++ ItemStack filteredItem = CraftItemStack.asCraftCopy(item); ++ // Origami end + for (int i = 0; i < inventory.length; i++) { + ItemStack cItem = inventory[i]; + if (cItem != null && cItem.getAmount() < cItem.getMaxStackSize() && cItem.isSimilar(filteredItem)) { +@@ -295,9 +297,10 @@ public class CraftInventory implements Inventory { + /* TODO: some optimization + * - Create a 'firstPartial' with a 'fromIndex' + * - Record the lastPartial per Material +- * - Cache firstEmpty result ++ * - Cache firstEmpty result // Implemented in Origami + */ + ++ int firstFree = -2; // Origami - Cache firstEmpty result + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + while (true) { +@@ -307,7 +310,11 @@ public class CraftInventory implements Inventory { + // Drat! no partial stack + if (firstPartial == -1) { + // Find a free spot! +- int firstFree = this.firstEmpty(); ++ // Origami start - Cache firstEmpty result ++ if (firstFree == -2) { ++ firstFree = this.firstEmpty(); ++ } ++ // Origami end + + if (firstFree == -1) { + // No space at all! +@@ -320,9 +327,11 @@ public class CraftInventory implements Inventory { + stack.setAmount(this.getMaxItemStack()); + this.setItem(firstFree, stack); + item.setAmount(item.getAmount() - this.getMaxItemStack()); ++ firstFree = -2; // Origami - Cache firstEmpty result + } else { + // Just store it + this.setItem(firstFree, item); ++ firstFree = -2; // Origami - Cache firstEmpty result + break; + } + } diff --git a/patches/server/0054-Don-t-send-equipment-updates-if-only-durability-chan.patch b/patches/server/0054-Don-t-send-equipment-updates-if-only-durability-chan.patch new file mode 100644 index 0000000..0244e15 --- /dev/null +++ b/patches/server/0054-Don-t-send-equipment-updates-if-only-durability-chan.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tom +Date: Thu, 23 Sep 2021 08:56:42 -0500 +Subject: [PATCH] Don't send equipment updates if only durability changed + +Original code by Cryptite, licensed under MIT +You can find the original code on https://github.com/Cryptite/Slice + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8e5c8d1e59af15a0b28a8e28e60e50d2ec3b23f9..1fdc19830bf4cf677df60c5760e02ae53abc85cc 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3052,7 +3052,7 @@ public abstract class LivingEntity extends Entity { + + ItemStack itemstack1 = this.getItemBySlot(enumitemslot); + +- if (!ItemStack.matches(itemstack1, itemstack)) { ++ if (!ItemStack.isSameIgnoreDurability(itemstack1, itemstack)) { + // Paper start - PlayerArmorChangeEvent + if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.ARMOR) { + final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack); diff --git a/patches/server/0055-Don-t-send-fire-packets-if-player-has-Fire-Resistanc.patch b/patches/server/0055-Don-t-send-fire-packets-if-player-has-Fire-Resistanc.patch new file mode 100644 index 0000000..de1cd8c --- /dev/null +++ b/patches/server/0055-Don-t-send-fire-packets-if-player-has-Fire-Resistanc.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Thu, 23 Sep 2021 09:29:09 -0500 +Subject: [PATCH] Don't send fire packets if player has Fire Resistance + +Original code by Cryptite, licensed under MIT +You can find the original code on https://github.com/Cryptite/Slice + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index adc4a19039b56de81053e746580dbef9c1fe9c93..9b224ca33115aa7e7f100a45fe8bdb68ea7e4b59 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -815,7 +815,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + this.checkOutOfWorld(); + if (!this.level.isClientSide) { +- this.setSharedFlagOnFire(this.remainingFireTicks > 0); ++ if (this instanceof net.minecraft.world.entity.LivingEntity livingEntity) { ++ this.setSharedFlagOnFire(this.remainingFireTicks > 0 && !livingEntity.hasEffect(net.minecraft.world.effect.MobEffects.FIRE_RESISTANCE)); ++ } else { ++ this.setSharedFlagOnFire(this.remainingFireTicks > 0); ++ } + } + + this.firstTick = false; diff --git a/patches/server/0056-Packet-obfuscation-and-reduction.patch b/patches/server/0056-Packet-obfuscation-and-reduction.patch new file mode 100644 index 0000000..5373456 --- /dev/null +++ b/patches/server/0056-Packet-obfuscation-and-reduction.patch @@ -0,0 +1,198 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Wed, 6 Oct 2021 11:03:01 -0500 +Subject: [PATCH] Packet obfuscation and reduction + +Original code by Cryptite, licensed under MIT +You can find the original code on https://github.com/Cryptite/Slice + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java +index 019ccd50ac87557bd971c0fde3113cc0e2e301ff..bb116272d84908649ef0f80618a66094422ecfca 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java +@@ -22,6 +22,13 @@ public class ClientboundSetEntityDataPacket implements Packet> packedItems) { ++ this.id = id; ++ this.packedItems = packedItems; ++ } ++ // Slice end ++ + public ClientboundSetEntityDataPacket(FriendlyByteBuf buf) { + this.id = buf.readVarInt(); + this.packedItems = SynchedEntityData.unpack(buf); +diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +index 1d88edfd09a909044f6e3175af652914b4d06311..9031fb0e8c589b759549f4197772b6c9929736a0 100644 +--- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +@@ -136,6 +136,12 @@ public class SynchedEntityData { + } + + public void set(EntityDataAccessor key, T value) { ++ // Slice start ++ set(key, value, null); ++ } ++ ++ public void set(EntityDataAccessor key, T value, @Nullable T foreignValue) { ++ // Slice end + SynchedEntityData.DataItem datawatcher_item = this.getItem(key); + + if (ObjectUtils.notEqual(value, datawatcher_item.getValue())) { +@@ -145,6 +151,11 @@ public class SynchedEntityData { + this.isDirty = true; + } + ++ // Slice start ++ if (foreignValue != null && ObjectUtils.notEqual(foreignValue, datawatcher_item.getForeignValue())) { ++ datawatcher_item.setForeignValue(foreignValue); ++ } ++ // Slice end + } + + // CraftBukkit start - add method from above +@@ -200,6 +211,28 @@ public class SynchedEntityData { + return list; + } + ++ // Slice start ++ @Nullable ++ public List> packForeignDirty(List> unpackedData) { ++ List> list = null; ++ ++ for (DataItem dataItem : unpackedData) { ++ DataItem item = itemsById.get(dataItem.accessor.getId()); ++ if (item.isDirty(true)) { ++ item.setForeignDirty(false); ++ ++ if (list == null) { ++ list = Lists.newArrayList(); ++ } ++ ++ list.add(item.copy(true)); ++ } ++ } ++ ++ return list; ++ } ++ // Slice end ++ + @Nullable + public List> getAll() { + List> list = null; +@@ -313,11 +346,14 @@ public class SynchedEntityData { + final EntityDataAccessor accessor; + T value; + private boolean dirty; ++ @Nullable T foreignValue = null; // Slice ++ private boolean foreignDirty; // Slice + + public DataItem(EntityDataAccessor data, T value) { + this.accessor = data; + this.value = value; + this.dirty = true; ++ this.foreignDirty = true; // Slice + } + + public EntityDataAccessor getAccessor() { +@@ -343,5 +379,34 @@ public class SynchedEntityData { + public SynchedEntityData.DataItem copy() { + return new SynchedEntityData.DataItem<>(this.accessor, this.accessor.getSerializer().copy(this.value)); + } ++ ++ // Slice start ++ public void setForeignValue(T foreignValue) { ++ this.foreignValue = foreignValue; ++ this.foreignDirty = true; ++ } ++ ++ public @Nullable T getForeignValue() { ++ return foreignValue; ++ } ++ ++ public boolean isDirty(boolean foreign) { ++ if (foreign) { ++ //There must be a foreign value in order for this to be dirty, otherwise we consider this a normal ++ //value and check the normal dirty flag. ++ return foreignValue == null || this.foreignDirty; ++ } ++ ++ return this.dirty; ++ } ++ ++ public void setForeignDirty(boolean dirty) { ++ this.foreignDirty = dirty; ++ } ++ ++ public SynchedEntityData.DataItem copy(boolean foreign) { ++ return new SynchedEntityData.DataItem<>(this.accessor, this.accessor.getSerializer().copy((foreign && this.foreignValue != null ? this.foreignValue : this.value))); ++ } ++ // Slice end + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 2d7e884d25f352e0a05e9aefb0ff6e296e73c361..8b64571c85e8e36d7ed48c42e4dc5957a874ea7c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -390,7 +390,19 @@ public class ServerEntity { + SynchedEntityData datawatcher = this.entity.getEntityData(); + + if (datawatcher.isDirty()) { +- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), datawatcher, false)); ++ // Slice start ++ ClientboundSetEntityDataPacket dataPacket = new ClientboundSetEntityDataPacket(this.entity.getId(), datawatcher, false); ++ if (this.entity instanceof ServerPlayer serverPlayer) { ++ serverPlayer.connection.send(dataPacket); ++ } ++ ++ //Get the packedData that the original packet has, and then determine if any of those are changed in ++ //the foreign version. If null, nothing to notify foreign trackers about. ++ List> dirtyItems = datawatcher.packForeignDirty(dataPacket.getUnpackedData()); ++ if (dirtyItems != null) { ++ this.broadcast(new ClientboundSetEntityDataPacket(this.entity.getId(), dirtyItems)); ++ } ++ // Slice end + } + + if (this.entity instanceof LivingEntity) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 9b224ca33115aa7e7f100a45fe8bdb68ea7e4b59..f585b23c4f958184c4388c5c832dac1eaf7ae788 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3029,7 +3029,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID); + return; + } +- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount()); ++ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount(), getMaxAirSupply()); // Slice + // CraftBukkit end + } + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 2483d7df7f1bf94344afd38b37602c645a4a2dff..5c16235115a9233bce1f5b30bb020d105bdca3d1 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -632,7 +632,7 @@ public abstract class Player extends LivingEntity { + public void increaseScore(int score) { + int j = this.getScore(); + +- this.entityData.set(Player.DATA_SCORE_ID, j + score); ++ this.entityData.set(Player.DATA_SCORE_ID, j + score, 0); // Slice + } + + public void startAutoSpinAttack(int riptideTicks) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 170c7469ac6ed343a8e4e43f03a972d106bcc640..d36a27c06b9b33fe762fcec3afe2bdb90ba0ec2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2202,7 +2202,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.sendHealthUpdate(); + } + } +- this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) this.getScaledHealth()); ++ this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_HEALTH_ID, (float) this.getScaledHealth(), isDead() ? 0f : 20f); // Slice + + this.getHandle().maxHealthCache = getMaxHealth(); + } diff --git a/patches/server/0057-lithium-cached-hashcode.patch b/patches/server/0057-lithium-cached-hashcode.patch new file mode 100644 index 0000000..b3afff0 --- /dev/null +++ b/patches/server/0057-lithium-cached-hashcode.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jellysquid3 +Date: Wed, 15 Dec 2021 11:30:23 -0500 +Subject: [PATCH] lithium: cached hashcode + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index a80f4dc0a642c744223a155232291ace6e007636..f5f40589f53ac4696507699037df1ae4738e1637 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -597,11 +597,18 @@ public class Block extends BlockBehaviour implements ItemLike { + private final BlockState first; + private final BlockState second; + private final Direction direction; ++ private int hash; // JettPack + + public BlockStatePairKey(BlockState self, BlockState other, Direction facing) { + this.first = self; + this.second = other; + this.direction = facing; ++ // JettPack start - lithium: cached_hashcode ++ int hash = this.first.hashCode(); ++ hash = 31 * hash + this.second.hashCode(); ++ hash = 31 * hash + this.direction.hashCode(); ++ this.hash = hash; ++ // JettPack end + } + + public boolean equals(Object object) { +@@ -617,11 +624,7 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + public int hashCode() { +- int i = this.first.hashCode(); +- +- i = 31 * i + this.second.hashCode(); +- i = 31 * i + this.direction.hashCode(); +- return i; ++ return this.hash; // JettPack + } + } + } diff --git a/patches/server/0058-PaperPR-Fix-mobs-spawning-outside-hard-despawn-range.patch b/patches/server/0058-PaperPR-Fix-mobs-spawning-outside-hard-despawn-range.patch new file mode 100644 index 0000000..d20aec5 --- /dev/null +++ b/patches/server/0058-PaperPR-Fix-mobs-spawning-outside-hard-despawn-range.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gaider10 <> +Date: Mon, 30 Aug 2021 16:10:51 +0300 +Subject: [PATCH] PaperPR Fix mobs spawning outside hard despawn range + + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 6e24b86ce315510133b6294f5d348e5c4e7f547f..dfc43fbd10ab9ccb84fb97e1db4d2bb107eee04f 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -386,7 +386,10 @@ public final class NaturalSpawner { + } + + private static boolean isValidPositionForMob(ServerLevel world, Mob entity, double squaredDistance) { +- return squaredDistance > (double) (entity.getType().getCategory().getDespawnDistance() * entity.getType().getCategory().getDespawnDistance()) && entity.removeWhenFarAway(squaredDistance) ? false : entity.checkSpawnRules(world, MobSpawnType.NATURAL) && entity.checkSpawnObstruction(world); ++ // Paper start - fix mobs spawning outside hard despawn range ++ int despawnDistance = entity.isPersistenceRequired() || entity.requiresCustomPersistence() ? entity.getType().getCategory().getDespawnDistance() : world.paperConfig.hardDespawnDistances.getInt(entity.getType().getCategory()); ++ return squaredDistance > (double) (despawnDistance * despawnDistance) && entity.removeWhenFarAway(squaredDistance) ? false : entity.checkSpawnRules((LevelAccessor) world, MobSpawnType.NATURAL) && entity.checkSpawnObstruction((LevelReader) world); ++ // Paper end - fix mobs spawning outside hard despawn range + } + + private static Optional getRandomSpawnMobAt(ServerLevel world, StructureFeatureManager structureAccessor, ChunkGenerator chunkGenerator, MobCategory spawnGroup, Random random, BlockPos pos) { diff --git a/patches/server/0059-PaperPR-Fix-CME-in-CraftPersistentDataTypeRegistry.patch b/patches/server/0059-PaperPR-Fix-CME-in-CraftPersistentDataTypeRegistry.patch new file mode 100644 index 0000000..86f11aa --- /dev/null +++ b/patches/server/0059-PaperPR-Fix-CME-in-CraftPersistentDataTypeRegistry.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gero +Date: Sat, 2 Oct 2021 20:08:30 +0200 +Subject: [PATCH] PaperPR Fix CME in CraftPersistentDataTypeRegistry + + +diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java +index 355c9f79fd3132848a00eacde951d1e1bfa92737..6070f7d954e201e06efb6ef27ad0e7d9ff1ab9ff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java +@@ -92,7 +92,7 @@ public final class CraftPersistentDataTypeRegistry { + } + } + +- private final Map adapters = new HashMap<>(); ++ private final Map adapters = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - Replace HashMap with ConcurrentHashMap to avoid CME + + /** + * Creates a suitable adapter instance for the primitive class type diff --git a/patches/server/0060-lithium-ai-task-goat-jump.patch b/patches/server/0060-lithium-ai-task-goat-jump.patch new file mode 100644 index 0000000..408f6d9 --- /dev/null +++ b/patches/server/0060-lithium-ai-task-goat-jump.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Fri, 10 Sep 2021 16:35:53 -0400 +Subject: [PATCH] lithium: ai task goat jump + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java b/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +index b25d245d0ad892e99663c3d741b2350fb1531d0e..3aafff1b77f137da897bc412edd7d0d57faa1458 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +@@ -25,6 +25,8 @@ import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++import it.unimi.dsi.fastutil.longs.LongArrayList; // JettPack ++import it.unimi.dsi.fastutil.shorts.ShortArrayList; // JettPack + + public class LongJumpToRandomPos extends Behavior { + private static final int FIND_JUMP_TRIES = 20; +@@ -41,12 +43,26 @@ public class LongJumpToRandomPos extends Behavior { + private int findJumpTries; + private long prepareJumpStart; + private Function getJumpSound; ++ private final LongArrayList potentialTargets = new LongArrayList(); // JettPack ++ private final ShortArrayList potentialWeights = new ShortArrayList(); // JettPack + +- public LongJumpToRandomPos(UniformInt cooldownRange, int verticalRange, int horizontalRange, float maxRange, Function entityToSound) { ++ // JettPack start ++ private static int findIndex(ShortArrayList weights, int weightedIndex) { ++ for (int i = 0; i < weights.size(); i++) { ++ weightedIndex -= weights.getShort(i); ++ if (weightedIndex < 0) { ++ return i; ++ } ++ } ++ return -1; ++ } ++ // JettPack end ++ ++ public LongJumpToRandomPos(UniformInt cooldownRange, int maxLongJumpHeight, int maxLongJumpWidth, float maxRange, Function entityToSound) { + super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT), 200); + this.timeBetweenLongJumps = cooldownRange; +- this.maxLongJumpHeight = verticalRange; +- this.maxLongJumpWidth = horizontalRange; ++ this.maxLongJumpHeight = maxLongJumpHeight; ++ this.maxLongJumpWidth = maxLongJumpWidth; + this.maxJumpVelocity = maxRange; + this.getJumpSound = entityToSound; + } +@@ -66,30 +82,63 @@ public class LongJumpToRandomPos extends Behavior { + return bl; + } + ++ // JettPack start ++ /** ++ * @author 2No2Name ++ * @reason only evaluate 20+ instead of ~100 possible jumps without affecting behavior ++ * [VanillaCopy] the whole method, commented changes ++ */ + @Override + protected void start(ServerLevel serverLevel, Mob mob, long l) { ++ this.potentialTargets.clear(); ++ this.potentialWeights.clear(); ++ int potentialTotalWeight = 0; + this.chosenJump = Optional.empty(); +- this.findJumpTries = 20; ++ this.findJumpTries = FIND_JUMP_TRIES; + this.jumpCandidates.clear(); + this.initialPosition = Optional.of(mob.position()); +- BlockPos blockPos = mob.blockPosition(); +- int i = blockPos.getX(); +- int j = blockPos.getY(); +- int k = blockPos.getZ(); +- Iterable iterable = BlockPos.betweenClosed(i - this.maxLongJumpWidth, j - this.maxLongJumpHeight, k - this.maxLongJumpWidth, i + this.maxLongJumpWidth, j + this.maxLongJumpHeight, k + this.maxLongJumpWidth); +- PathNavigation pathNavigation = mob.getNavigation(); +- +- for(BlockPos blockPos2 : iterable) { +- double d = blockPos2.distSqr(blockPos); +- if ((i != blockPos2.getX() || k != blockPos2.getZ()) && pathNavigation.isStableDestination(blockPos2) && mob.getPathfindingMalus(WalkNodeEvaluator.getBlockPathTypeStatic(mob.level, blockPos2.mutable())) == 0.0F) { +- Optional optional = this.calculateOptimalJumpVector(mob, Vec3.atCenterOf(blockPos2)); +- optional.ifPresent((vel) -> { +- this.jumpCandidates.add(new LongJumpToRandomPos.PossibleJump(new BlockPos(blockPos2), vel, Mth.ceil(d))); +- }); ++ BlockPos goatPos = mob.blockPosition(); ++ int goatX = goatPos.getX(); ++ int goatY = goatPos.getY(); ++ int goatZ = goatPos.getZ(); ++ Iterable iterable = BlockPos.betweenClosed(goatX - this.maxLongJumpWidth, goatY - this.maxLongJumpHeight, goatZ - this.maxLongJumpWidth, goatX + this.maxLongJumpWidth, goatY + this.maxLongJumpHeight, goatZ + this.maxLongJumpWidth); ++ PathNavigation entityNavigation = mob.getNavigation(); ++ ++ BlockPos.MutableBlockPos targetPosCopy = new BlockPos.MutableBlockPos(); ++ for (BlockPos targetPos : iterable) { ++ if (goatX == targetPos.getX() && goatZ == targetPos.getZ()) { ++ continue; + } +- } ++ double squaredDistance = targetPos.distSqr(goatPos); + ++ //Optimization: Evaluate the flight path check later (after random selection, but before world can be modified) ++ if (entityNavigation.isStableDestination(targetPos) && mob.getPathfindingMalus(WalkNodeEvaluator.getBlockPathTypeStatic(mob.level, targetPosCopy.set(targetPos))) == 0.0F) { ++ this.potentialTargets.add(targetPos.asLong()); ++ int weight = Mth.ceil(squaredDistance); ++ this.potentialWeights.add((short) weight); ++ potentialTotalWeight += weight; ++ } ++ } ++ // Optimization: Do the random picking of positions before doing the expensive the jump flight path validity check. ++ // up to FIND_JUMP_TRIES random jumpCandidates can be selected in keepRunning, so only this number of jumpCandidates needs to be generated ++ while (this.jumpCandidates.size() < FIND_JUMP_TRIES) { ++ // the number of random calls will be different from vanilla, but this is not reasonably detectable (not affecting world generation) ++ if (potentialTotalWeight == 0) { ++ return; // collection is empty/fully consumed, no more possible jumpCandidates available ++ } ++ int chosenIndex = findIndex(this.potentialWeights, serverLevel.random.nextInt(potentialTotalWeight)); ++ long chosenPos = this.potentialTargets.getLong(chosenIndex); ++ short chosenWeight = this.potentialWeights.set(chosenIndex, (short) 0); ++ potentialTotalWeight -= chosenWeight; ++ // Very expensive method call, it shifts bounding boxes around and checks for collisions with them ++ Optional optional = this.calculateOptimalJumpVector(mob, Vec3.atCenterOf(targetPosCopy.set(chosenPos))); ++ if (optional.isPresent()) { ++ //the weight in Target should be unused, as the random selection already took place ++ this.jumpCandidates.add(new LongJumpToRandomPos.PossibleJump(new BlockPos(targetPosCopy), optional.get(), chosenWeight)); ++ } ++ } + } ++ // JettPack end + + @Override + protected void tick(ServerLevel serverLevel, E mob, long l) { +@@ -106,7 +155,14 @@ public class LongJumpToRandomPos extends Behavior { + } + } else { + --this.findJumpTries; +- Optional optional = WeightedRandom.getRandomItem(serverLevel.random, this.jumpCandidates); ++ // JettPack start ++ Optional optional; ++ if (this.jumpCandidates.isEmpty()) { ++ optional = Optional.empty(); ++ } else { ++ optional = Optional.of(this.jumpCandidates.get(0)); ++ } ++ // JettPack end + if (optional.isPresent()) { + this.jumpCandidates.remove(optional.get()); + mob.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, new BlockPosTracker(optional.get().getJumpTarget())); diff --git a/patches/server/0061-lithium-store-gamerules-in-fastutil-hashmap.patch b/patches/server/0061-lithium-store-gamerules-in-fastutil-hashmap.patch new file mode 100644 index 0000000..1b70d8d --- /dev/null +++ b/patches/server/0061-lithium-store-gamerules-in-fastutil-hashmap.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Tue, 21 Dec 2021 09:43:24 -0500 +Subject: [PATCH] lithium: store gamerules in fastutil hashmap + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index fb2626d2a6babe531dd1849333b119ab36200d80..e22de06aab82896a7c245763e7f3563d3d0912a9 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -27,6 +27,7 @@ import net.minecraft.network.protocol.game.ClientboundGameEventPacket; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerPlayer; + import org.slf4j.Logger; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; // JettPack + + public class GameRules { + +@@ -110,14 +111,16 @@ public class GameRules { + + public GameRules() { + // Pufferfish start - use this to ensure gameruleArray is initialized +- this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { ++ // JettPack start - lithium: store gamerules in fastutil hashmap ++ this(new Object2ObjectOpenHashMap<>((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { + return ((GameRules.Type) entry.getValue()).createRule(); +- }))); ++ })))); ++ // JettPack end + // Pufferfish end + } + + private GameRules(Map, GameRules.Value> rules) { +- this.rules = rules; ++ this.rules = new Object2ObjectOpenHashMap<>(rules); // JettPack - lithium: store gamerules in fastutil hashmap + + // Pufferfish start + int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1; diff --git a/patches/server/0062-carpetfixes-BiomeAccess-prediction.patch b/patches/server/0062-carpetfixes-BiomeAccess-prediction.patch new file mode 100644 index 0000000..cfa3095 --- /dev/null +++ b/patches/server/0062-carpetfixes-BiomeAccess-prediction.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 22 Apr 2022 22:44:56 +0200 +Subject: [PATCH] carpetfixes: BiomeAccess prediction + +Author: fxmorin + +Original license: MIT +Original project: https://github.com/fxmorin/carpet-fixes (Yarn mappings) + +Copyright (c) 2020 Fx Morin + +diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +index dd3f4c2c564be1e9886b5c3ddd9ca1dfd749c028..57b467bcf1561f6b3b42c5e088f71460704804f7 100644 +--- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java ++++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java +@@ -28,41 +28,72 @@ public class BiomeManager { + return new BiomeManager(storage, this.biomeZoomSeed); + } + ++ // Mirai start - carpetfixes: BiomeAccess prediction ++ private static final double maxOffset = 0.4500000001D; // Mirai - carpetfixes: BiomeAccess prediction + public Holder getBiome(BlockPos pos) { +- int i = pos.getX() - 2; +- int j = pos.getY() - 2; +- int k = pos.getZ() - 2; +- int l = i >> 2; +- int m = j >> 2; +- int n = k >> 2; +- double d = (double)(i & 3) / 4.0D; +- double e = (double)(j & 3) / 4.0D; +- double f = (double)(k & 3) / 4.0D; +- int o = 0; +- double g = Double.POSITIVE_INFINITY; +- +- for(int p = 0; p < 8; ++p) { +- boolean bl = (p & 4) == 0; +- boolean bl2 = (p & 2) == 0; +- boolean bl3 = (p & 1) == 0; +- int q = bl ? l : l + 1; +- int r = bl2 ? m : m + 1; +- int s = bl3 ? n : n + 1; +- double h = bl ? d : d - 1.0D; +- double t = bl2 ? e : e - 1.0D; +- double u = bl3 ? f : f - 1.0D; +- double v = getFiddledDistance(this.biomeZoomSeed, q, r, s, h, t, u); +- if (g > v) { +- o = p; +- g = v; ++ int xMinus2 = pos.getX() - 2; ++ int yMinus2 = pos.getY() - 2; ++ int zMinus2 = pos.getZ() - 2; ++ int x = xMinus2 >> 2; // BlockPos to BiomePos ++ int y = yMinus2 >> 2; ++ int z = zMinus2 >> 2; ++ double quartX = (double) (xMinus2 & 3) / 4.0D; // quartLocal divided by 4 ++ double quartY = (double) (yMinus2 & 3) / 4.0D; // 0/4, 1/4, 2/4, 3/4 ++ double quartZ = (double) (zMinus2 & 3) / 4.0D; // [0, 0.25, 0.5, 0.75] ++ int smallestX = 0; ++ double smallestDist = Double.POSITIVE_INFINITY; ++ for (int biomeX = 0; biomeX < 8; ++biomeX) { ++ boolean everyOtherQuad = (biomeX & 4) == 0; // 1 1 1 1 0 0 0 0 ++ boolean everyOtherPair = (biomeX & 2) == 0; // 1 1 0 0 1 1 0 0 ++ boolean everyOther = (biomeX & 1) == 0; // 1 0 1 0 1 0 1 0 ++ double quartXX = everyOtherQuad ? quartX : quartX - 1.0D; //[-1.0,-0.75,-0.5,-0.25,0.0,0.25,0.5,0.75] ++ double quartYY = everyOtherPair ? quartY : quartY - 1.0D; ++ double quartZZ = everyOther ? quartZ : quartZ - 1.0D; ++ ++ //This code block is new ++ double maxQuartYY = 0.0D, maxQuartZZ = 0.0D; ++ if (biomeX != 0) { ++ maxQuartYY = Mth.square(Math.max(quartYY + maxOffset, Math.abs(quartYY - maxOffset))); ++ maxQuartZZ = Mth.square(Math.max(quartZZ + maxOffset, Math.abs(quartZZ - maxOffset))); ++ double maxQuartXX = Mth.square(Math.max(quartXX + maxOffset,Math.abs(quartXX - maxOffset))); ++ if (smallestDist < maxQuartXX + maxQuartYY + maxQuartZZ) continue; ++ } ++ ++ int xx = everyOtherQuad ? x : x + 1; ++ int yy = everyOtherPair ? y : y + 1; ++ int zz = everyOther ? z : z + 1; ++ ++ //I transferred the code from method_38106 to here, so I could call continue halfway through ++ long seed = LinearCongruentialGenerator.next(this.biomeZoomSeed, xx); ++ seed = LinearCongruentialGenerator.next(seed, yy); ++ seed = LinearCongruentialGenerator.next(seed, zz); ++ seed = LinearCongruentialGenerator.next(seed, xx); ++ seed = LinearCongruentialGenerator.next(seed, yy); ++ seed = LinearCongruentialGenerator.next(seed, zz); ++ double offsetX = getFiddle(seed); ++ double sqrX = Mth.square(quartXX + offsetX); ++ if (biomeX != 0 && smallestDist < sqrX + maxQuartYY + maxQuartZZ) continue; //skip the rest of the loop ++ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); ++ double offsetY = getFiddle(seed); ++ double sqrY = Mth.square(quartYY + offsetY); ++ if (biomeX != 0 && smallestDist < sqrX + sqrY + maxQuartZZ) continue; // skip the rest of the loop ++ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); ++ double offsetZ = getFiddle(seed); ++ double biomeDist = sqrX + sqrY + Mth.square(quartZZ + offsetZ); ++ ++ if (smallestDist > biomeDist) { ++ smallestX = biomeX; ++ smallestDist = biomeDist; + } + } + +- int w = (o & 4) == 0 ? l : l + 1; +- int x = (o & 2) == 0 ? m : m + 1; +- int y = (o & 1) == 0 ? n : n + 1; +- return this.noiseBiomeSource.getNoiseBiome(w, x, y); ++ return(this.noiseBiomeSource.getNoiseBiome( ++ (smallestX & 4) == 0 ? x : x + 1, ++ (smallestX & 2) == 0 ? y : y + 1, ++ (smallestX & 1) == 0 ? z : z + 1 ++ )); + } ++ // Mirai end + + public Holder getNoiseBiomeAtPosition(double x, double y, double z) { + int i = QuartPos.fromBlock(Mth.floor(x)); diff --git a/patches/server/0063-Skip-enchanting-table-book-tick.patch b/patches/server/0063-Skip-enchanting-table-book-tick.patch new file mode 100644 index 0000000..62e91d5 --- /dev/null +++ b/patches/server/0063-Skip-enchanting-table-book-tick.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Sat, 8 Jan 2022 22:00:46 +0100 +Subject: [PATCH] Skip enchanting table book tick + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java +index f177c88d2a122a34b5c601fb8c0d15fb16925f6b..b3eef862248574ee4b864107e1eb69d2418ada8c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java +@@ -49,6 +49,9 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable + } + + public static void bookAnimationTick(Level world, BlockPos pos, BlockState state, EnchantmentTableBlockEntity blockEntity) { ++ // Mirai start ++ return; ++ /* + blockEntity.oOpen = blockEntity.open; + blockEntity.oRot = blockEntity.rot; + Player player = world.getNearestPlayer((double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, 3.0D, false); +@@ -102,6 +105,8 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable + h = Mth.clamp(h, -0.2F, 0.2F); + blockEntity.flipA += (h - blockEntity.flipA) * 0.9F; + blockEntity.flip += blockEntity.flipA; ++ */ ++ // Mirai end + } + + @Override diff --git a/patches/server/0064-Skip-cloning-loot-parameters.patch b/patches/server/0064-Skip-cloning-loot-parameters.patch new file mode 100644 index 0000000..90d0390 --- /dev/null +++ b/patches/server/0064-Skip-cloning-loot-parameters.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 4 Mar 2022 19:57:07 +0100 +Subject: [PATCH] Skip cloning loot parameters + + +diff --git a/src/main/java/net/minecraft/advancements/Advancement.java b/src/main/java/net/minecraft/advancements/Advancement.java +index c534055f90b98942b9d7d152fd502cf5387c995b..9e46acf66df7a41eb80d7e41871c3b3caa66f55e 100644 +--- a/src/main/java/net/minecraft/advancements/Advancement.java ++++ b/src/main/java/net/minecraft/advancements/Advancement.java +@@ -46,7 +46,7 @@ public class Advancement { + public Advancement(ResourceLocation id, @Nullable Advancement parent, @Nullable DisplayInfo display, AdvancementRewards rewards, Map criteria, String[][] requirements) { + this.id = id; + this.display = display; +- this.criteria = ImmutableMap.copyOf(criteria); ++ this.criteria = java.util.Collections.unmodifiableMap(criteria); // Mirai - Skip cloning loot parameters + this.parent = parent; + this.rewards = rewards; + this.requirements = requirements; diff --git a/patches/server/0065-lithium-precompute-shape-arrays.patch b/patches/server/0065-lithium-precompute-shape-arrays.patch new file mode 100644 index 0000000..0494495 --- /dev/null +++ b/patches/server/0065-lithium-precompute-shape-arrays.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jellysquid3 +Date: Sat, 1 Jan 2022 03:59:58 -0500 +Subject: [PATCH] lithium: precompute shape arrays + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index 92789a27aa2978ac323d4cdafa82391998cf0ffa..842b67327523c512f95ad64ad23d3ee4102175ea 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -41,7 +41,7 @@ public enum Direction implements StringRepresentable { + private final Direction.Axis axis; + private final Direction.AxisDirection axisDirection; + private final Vec3i normal; +- private static final Direction[] VALUES = values(); ++ public static final Direction[] VALUES = values(); // JettPack + private static final Map BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(Direction::getName, (direction) -> { + return direction; + })); +diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java b/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java +index def6c6a91aa229c83adb89a3fd0eb8bfb80e8182..dc700c70b0f8a3f42716060fbb81b3c75e3647ee 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/CubePointRange.java +@@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; + + public class CubePointRange extends AbstractDoubleList { + private final int parts; ++ private double scale; // JettPack - lithium: shapes.precompute_shape_arrays + + CubePointRange(int sectionCount) { + if (sectionCount <= 0) { +@@ -11,10 +12,11 @@ public class CubePointRange extends AbstractDoubleList { + } else { + this.parts = sectionCount; + } ++ this.scale = 1.0D / sectionCount; // JettPack - lithium: shapes.precompute_shape_arrays + } + + public double getDouble(int i) { +- return (double)i / (double)this.parts; ++ return i * this.scale; // JettPack - lithium: shapes.precompute_shape_arrays + } + + public int size() { +diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +index 374da999d4fb544a5571ca888dd4cc60680d0ee0..ba4355ed2b5380ceab95d19908ec6b8fe7d2f12a 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +@@ -3,15 +3,25 @@ package net.minecraft.world.phys.shapes; + import it.unimi.dsi.fastutil.doubles.DoubleList; + import net.minecraft.core.Direction; + import net.minecraft.util.Mth; ++import net.minecraft.world.phys.shapes.CubePointRange; // JettPack + + public final class CubeVoxelShape extends VoxelShape { ++ private DoubleList[] list; // JettPack - lithium: shapes.precompute_shape_arrays ++ + protected CubeVoxelShape(DiscreteVoxelShape voxels) { + super(voxels); ++ // JettPack start - lithium: shapes.precompute_shape_arrays ++ this.list = new DoubleList[Direction.VALUES.length]; ++ ++ for (Direction.Axis axis : Direction.Axis.VALUES) { ++ this.list[axis.ordinal()] = new CubePointRange(voxels.getSize(axis)); ++ } ++ // JettPack end + } + + @Override + protected DoubleList getCoords(Direction.Axis axis) { +- return new CubePointRange(this.shape.getSize(axis)); ++ return this.list[axis.ordinal()]; // JettPack - lithium: shapes.precompute_shape_arrays + } + + @Override diff --git a/patches/server/0066-vmp-ingredient_matching.patch b/patches/server/0066-vmp-ingredient_matching.patch new file mode 100644 index 0000000..55f9a0e --- /dev/null +++ b/patches/server/0066-vmp-ingredient_matching.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sun, 21 Nov 2021 03:01:00 +0100 +Subject: [PATCH] vmp: ingredient_matching + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/VMP-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +index 895c0f1600139e340aa22a7c398978add56fa706..bbf7d112b8020567f2c1d02ce0b2c1a22b2279d1 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java ++++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +@@ -12,9 +12,12 @@ import it.unimi.dsi.fastutil.ints.IntList; + import java.util.Arrays; + import java.util.Collection; + import java.util.Collections; ++import java.util.HashSet; // Mirai + import java.util.Iterator; + import java.util.List; ++import java.util.Set; // Mirai + import java.util.function.Predicate; ++import java.util.stream.Collectors; // Mirai + import java.util.stream.Stream; + import java.util.stream.StreamSupport; + import javax.annotation.Nullable; +@@ -38,6 +41,8 @@ public final class Ingredient implements Predicate { + @Nullable + private IntList stackingIds; + public boolean exact; // CraftBukkit ++ private Set matchingItems = null; // Mirai ++ private boolean isEmptyMatch = false; // Mirai + + public Ingredient(Stream entries) { + this.values = (Ingredient.Value[]) entries.toArray((i) -> { +@@ -65,32 +70,25 @@ public final class Ingredient implements Predicate { + if (itemstack == null) { + return false; + } else { +- this.dissolve(); +- if (this.itemStacks.length == 0) { +- return itemstack.isEmpty(); +- } else { +- ItemStack[] aitemstack = this.itemStacks; +- int i = aitemstack.length; +- +- for (int j = 0; j < i; ++j) { +- ItemStack itemstack1 = aitemstack[j]; +- +- // CraftBukkit start +- if (this.exact) { +- if (itemstack1.getItem() == itemstack.getItem() && ItemStack.tagMatches(itemstack, itemstack1)) { +- return true; +- } +- +- continue; +- } +- // CraftBukkit end +- if (itemstack1.is(itemstack.getItem())) { +- return true; +- } +- } +- +- return false; ++ // Mirai start ++ /** ++ * @author ishland ++ * @reason optimize test() ++ */ ++ Set matchingItems = this.matchingItems; ++ boolean isEmptyMatch = this.isEmptyMatch; ++ if (matchingItems == null) { ++ matchingItems = this.matchingItems = Arrays.stream(this.values) ++ .flatMap(entry -> entry.getItems().stream()) ++ .filter(itemstack1 -> !itemstack1.isEmpty()) ++ .map(ItemStack::getItem) ++ .collect(Collectors.toCollection(HashSet::new)); ++ isEmptyMatch = this.isEmptyMatch = this.matchingItems.isEmpty(); ++ } ++ if (itemstack.isEmpty()) { ++ return isEmptyMatch; + } ++ return matchingItems.contains(itemstack.getItem()); + } + } + diff --git a/patches/server/0067-Fix-MC-121706.patch b/patches/server/0067-Fix-MC-121706.patch new file mode 100644 index 0000000..6eadc9f --- /dev/null +++ b/patches/server/0067-Fix-MC-121706.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Sun, 2 Jan 2022 20:37:30 +0100 +Subject: [PATCH] Fix MC-121706 + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java +index 048effbc4d25669801b467be61ee4b88dd4ea9b7..5b93358a24dab1dbe66f0a74c22f1977e3c60e50 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java +@@ -111,10 +111,10 @@ public class RangedBowAttackGoal extends Go + + this.mob.getMoveControl().strafe(this.strafingBackwards ? -0.5F : 0.5F, this.strafingClockwise ? 0.5F : -0.5F); + this.mob.lookAt(livingEntity, 30.0F, 30.0F); +- } else { +- this.mob.getLookControl().setLookAt(livingEntity, 30.0F, 30.0F); + } + ++ this.mob.getLookControl().setLookAt(livingEntity, 30.0F, 30.0F); // Mirai - fix MC-121706 ++ + if (this.mob.isUsingItem()) { + if (!bl && this.seeTime < -60) { + this.mob.stopUsingItem(); diff --git a/patches/server/0068-vmp-entity.iteration.patch b/patches/server/0068-vmp-entity.iteration.patch new file mode 100644 index 0000000..a5fddc1 --- /dev/null +++ b/patches/server/0068-vmp-entity.iteration.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sat, 1 Jan 2022 11:05:22 +0100 +Subject: [PATCH] vmp: entity.iteration + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/VMP-fabric (Yarn mappings) + +diff --git a/src/main/java/com/ishland/vmp/common/general/collections/ITypeFilterableList.java b/src/main/java/com/ishland/vmp/common/general/collections/ITypeFilterableList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..beaa0a60771b17b93e4074b272b503a2f6e4cf34 +--- /dev/null ++++ b/src/main/java/com/ishland/vmp/common/general/collections/ITypeFilterableList.java +@@ -0,0 +1,8 @@ ++package com.ishland.vmp.common.general.collections; ++ ++public interface ITypeFilterableList { ++ ++ Object[] getBackingArray(); ++ ++ ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySection.java b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +index 578e07407951688e7e8489919a4a3733028fc568..9b39920baea28fe85a0bb6ada0e00a466b3ddf31 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySection.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +@@ -8,6 +8,10 @@ import net.minecraft.util.ClassInstanceMultiMap; + import net.minecraft.util.VisibleForDebug; + import net.minecraft.world.phys.AABB; + import org.slf4j.Logger; ++import com.ishland.vmp.common.general.collections.ITypeFilterableList; // Mirai ++import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Mirai ++import net.minecraft.world.level.entity.EntityAccess; // Mirai ++import net.minecraft.world.level.entity.EntityTypeTest; // Mirai + + public class EntitySection { + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -45,27 +49,62 @@ public class EntitySection { + return this.storage.remove(entity); + } + ++ // Mirai start ++ /** ++ * @author ishland ++ * @reason use array for iteration & inline math ++ */ + public void getEntities(AABB box, Consumer action) { +- for(T entityAccess : this.storage) { +- if (entityAccess.getBoundingBox().intersects(box)) { +- action.accept(entityAccess); ++ if (this.storage instanceof ITypeFilterableList iTypeFilterableList) { // use array for iteration ++ for (Object _entityLike : iTypeFilterableList.getBackingArray()) { ++ if (_entityLike != null) { ++ @SuppressWarnings("unchecked") T entityAccess = (T) _entityLike; ++ AABB box1 = entityAccess.getBoundingBox(); ++ if (box1.minX < box.maxX && box1.maxX > box.minX && box1.minY < box.maxY && box1.maxY > box.minY && box1.minZ < box.maxZ && box1.maxZ > box.minZ) { // inline math ++ action.accept(entityAccess); ++ } ++ } ++ } ++ } else { // fallback ++ for (T entityAccess : this.storage) { ++ AABB box1 = entityAccess.getBoundingBox(); ++ if (box1.minX < box.maxX && box1.maxX > box.minX && box1.minY < box.maxY && box1.maxY > box.minY && box1.minZ < box.maxZ && box1.maxZ > box.minZ) { // inline math ++ action.accept(entityAccess); ++ } + } + } +- + } + ++ /** ++ * @author ishland ++ * @reason use array for iteration & inline math ++ */ + public void getEntities(EntityTypeTest type, AABB box, Consumer action) { + Collection collection = this.storage.find(type.getBaseClass()); + if (!collection.isEmpty()) { +- for(T entityAccess : collection) { +- U entityAccess2 = (U)((EntityAccess)type.tryCast(entityAccess)); +- if (entityAccess2 != null && entityAccess.getBoundingBox().intersects(box)) { +- action.accept(entityAccess2); // Paper - decompile fix ++ if (collection instanceof ObjectArrayList objectArrayList) { // use array for iteration ++ for (Object _entityLike : objectArrayList.elements()) { ++ if (_entityLike != null) { ++ T entityAccess = (T) _entityLike; ++ U entityAccess2 = type.tryCast(entityAccess); ++ final AABB boundingBox = entityAccess.getBoundingBox(); ++ if (entityAccess2 != null && boundingBox.minX < box.maxX && boundingBox.maxX > box.minX && boundingBox.minY < box.maxY && boundingBox.maxY > box.minY && boundingBox.minZ < box.maxZ && boundingBox.maxZ > box.minZ) { // inline math ++ action.accept(entityAccess2); ++ } ++ } ++ } ++ } else { // fallback ++ for(T entityAccess : collection) { ++ U entityAccess2 = type.tryCast(entityAccess); ++ AABB box1 = entityAccess.getBoundingBox(); ++ if (entityAccess2 != null && box1.minX < box.maxX && box1.maxX > box.minX && box1.minY < box.maxY && box1.maxY > box.minY && box1.minZ < box.maxZ && box1.maxZ > box.minZ) { // inline math ++ action.accept(entityAccess2); ++ } + } + } +- + } + } ++ // Mirai end + + public boolean isEmpty() { + return this.storage.isEmpty(); diff --git a/patches/server/0069-vmp-spawn_density_cap.patch b/patches/server/0069-vmp-spawn_density_cap.patch new file mode 100644 index 0000000..c3ea6f5 --- /dev/null +++ b/patches/server/0069-vmp-spawn_density_cap.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sat, 4 Dec 2021 12:09:59 +0100 +Subject: [PATCH] vmp: spawn_density_cap + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/VMP-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/LocalMobCapCalculator.java b/src/main/java/net/minecraft/world/level/LocalMobCapCalculator.java +index 05a5cd35dc96a7789c89a171526fe96c89b42f52..d9fa1389ae2591a6d7865bb0782e46ed8b36276a 100644 +--- a/src/main/java/net/minecraft/world/level/LocalMobCapCalculator.java ++++ b/src/main/java/net/minecraft/world/level/LocalMobCapCalculator.java +@@ -47,16 +47,25 @@ public class LocalMobCapCalculator { + } + + static class MobCounts { +- private final Object2IntMap counts = new Object2IntOpenHashMap<>(MobCategory.values().length); ++ // private final Object2IntMap counts = new Object2IntOpenHashMap<>(MobCategory.values().length); // Mirai ++ private final int[] spawnGroupDensities = new int[MobCategory.values().length]; // Mirai + ++ // Mirai start ++ /** ++ * @author ishland ++ * @reason opt: replace with array access ++ */ + public void add(MobCategory spawnGroup) { +- this.counts.computeInt(spawnGroup, (group, density) -> { +- return density == null ? 1 : density + 1; +- }); ++ this.spawnGroupDensities[spawnGroup.ordinal()] ++; + } + ++ /** ++ * @author ishland ++ * @reason opt: replace with array access ++ */ + public boolean canSpawn(MobCategory spawnGroup) { +- return this.counts.getOrDefault(spawnGroup, 0) < spawnGroup.getMaxInstancesPerChunk(); ++ return this.spawnGroupDensities[spawnGroup.ordinal()] < spawnGroup.getMaxInstancesPerChunk(); + } ++ // Mirai end + } + } diff --git a/patches/server/0070-Configurable-server-metrics.patch b/patches/server/0070-Configurable-server-metrics.patch new file mode 100644 index 0000000..5e2bd22 --- /dev/null +++ b/patches/server/0070-Configurable-server-metrics.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Mon, 3 Jan 2022 16:03:27 +0100 +Subject: [PATCH] Configurable server metrics + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7a7d0566611aafafba30b7b25c2f1f3e78b054fa..7333e60f404f6f85a2bcb1f8f031a180314abd32 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -123,7 +123,7 @@ public class PaperConfig { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue()); + } + +- if (!metricsStarted) { ++ if (!metricsStarted && wtf.etil.mirai.MiraiConfig.serverMetrics) { // Mirai - configurable server metrics + Metrics.PaperMetrics.startMetrics(); + metricsStarted = true; + } +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index a1d88b8cadf6c193deb69bb577936ebd69e63b2a..21c4a287c8fc550be36fc5b60bdc458d71a9d25d 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -230,4 +230,9 @@ public class MiraiConfig { + entitiesCanUsePortals = getBoolean("entities-can-use-portals", entitiesCanUsePortals); + } + ++ public static boolean serverMetrics = true; ++ private static void allowServerMetrics() { ++ serverMetrics = getBoolean("enable-server-metrics", serverMetrics); ++ } ++ + } +\ No newline at end of file diff --git a/patches/server/0071-Configurable-FastMath.round.patch b/patches/server/0071-Configurable-FastMath.round.patch new file mode 100644 index 0000000..ffe35d9 --- /dev/null +++ b/patches/server/0071-Configurable-FastMath.round.patch @@ -0,0 +1,374 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: FX - PR0CESS +Date: Wed, 22 Dec 2021 18:39:21 +0100 +Subject: [PATCH] Configurable FastMath.round + +Original code by fxmorin, licensed under MIT +You can find the original code on https://github.com/fxmorin/carpet-fixes + +diff --git a/src/main/java/carpetfixes/helpers/FastMath.java b/src/main/java/carpetfixes/helpers/FastMath.java +new file mode 100644 +index 0000000000000000000000000000000000000000..da81a142d2b072f846f9905a0d5b8e293763284a +--- /dev/null ++++ b/src/main/java/carpetfixes/helpers/FastMath.java +@@ -0,0 +1,28 @@ ++package carpetfixes.helpers; ++ ++public class FastMath { ++ ++ /** ++ * @author FX - PR0CESS ++ * ~1.25x faster than {@link Math#round(float)} ++ */ ++ public static int round(float a) { ++ if (wtf.etil.mirai.MiraiConfig.riskyMathRoundOpt) { ++ return a > 0F ? (int)(a + .5F) : (int)(a - .5F); ++ } else { ++ return Math.round(a); ++ } ++ } ++ ++ /** ++ * @author FX - PR0CESS ++ * ~1.28x faster than {@link Math#round(double)} ++ */ ++ public static long round(double a) { ++ if (wtf.etil.mirai.MiraiConfig.riskyMathRoundOpt) { ++ return a > 0D ? (long)(a + .5D) : (long)(a - .5D); ++ } else { ++ return Math.round(a); ++ } ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index e85175910454fc08704ea01e4b852136a6c6c60f..4ac91d601dd7e37b3455360b3b41b73cd66624df 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -725,12 +725,12 @@ public class PaperCommand extends Command { + ++relitChunks[0]; + sender.getBukkitEntity().sendMessage( + ChatColor.BLUE + "Relit chunk " + ChatColor.DARK_AQUA + chunkPos + ChatColor.BLUE + +- ", progress: " + ChatColor.DARK_AQUA + (int)(Math.round(100.0 * (double)(relitChunks[0])/(double)pending[0])) + "%" ++ ", progress: " + ChatColor.DARK_AQUA + (int)(carpetfixes.helpers.FastMath.round(100.0 * (double)(relitChunks[0])/(double)pending[0])) + "%" + ); + }, + (int totalRelit) -> { + final long end = System.nanoTime(); +- final long diff = Math.round(1.0e-6*(end - start)); ++ final long diff = carpetfixes.helpers.FastMath.round(1.0e-6*(end - start)); + sender.getBukkitEntity().sendMessage( + ChatColor.BLUE + "Relit " + ChatColor.DARK_AQUA + totalRelit + ChatColor.BLUE + " chunks. Took " + + ChatColor.DARK_AQUA + diff + "ms" +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +index f9251183df72ddc56662fd3f02acf21641a2200c..ea1bad145e529ac36bf335cf4d5339b2d88b282f 100644 +--- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -81,6 +81,6 @@ public class RAMDetails extends JList { + } + + private static String format(double tps) { +- return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); ++ return ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( carpetfixes.helpers.FastMath.round( tps * 100.0 ) / 100.0, 20.0 ); + } + } +diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java +index c3e54da4ab6440811aab2f9dd1e218802ac13285..2a38ea32f2d4a01be556fdf11254d5910c54f997 100644 +--- a/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMGraph.java +@@ -128,7 +128,7 @@ public class RAMGraph extends JComponent { + graphics.setColor(data.getLineColor()); + graphics.fillOval(m.x - 2, 100 - used - 2, 5, 5); + setToolTipText(String.format("Used: %s mb (%s%%)
%s", +- Math.round(data.getUsedMem() / 1024F / 1024F), ++ carpetfixes.helpers.FastMath.round(data.getUsedMem() / 1024F / 1024F), + used, getTime(m.x))); + } + } +diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java +index 40447d00aefb5ffedb8a2ee87155a04088f0649f..31cc0a29e89e79f616b3ee329a5528961edaa2de 100644 +--- a/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java ++++ b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java +@@ -25,7 +25,7 @@ public class TPSCollector extends LiveCollector { + long[] times = MinecraftServer.getServer().tickTimes5s.getTimes(); + double mspt = ((double) Arrays.stream(times).sum() / (double) times.length) * 1.0E-6D; + +- this.report(TPS, Math.min(20D, Math.round(Bukkit.getServer().getTPS()[0] * 100d) / 100d)); +- this.report(MSPT, (double) Math.round(mspt * 100d) / 100d); ++ this.report(TPS, Math.min(20D, carpetfixes.helpers.FastMath.round(Bukkit.getServer().getTPS()[0] * 100d) / 100d)); ++ this.report(MSPT, (double) carpetfixes.helpers.FastMath.round(mspt * 100d) / 100d); + } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 282fc59c3280d5d8478cb9897073d9d73996420b..a4249262ca98980985d4c36009488e792121f5df 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2781,7 +2781,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 21.0 ) ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise ++ return (( tps > 21.0 ) ? "*" : "") + Math.min(carpetfixes.helpers.FastMath.round(tps * 100.0) / 100.0, 20.0); // only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise + } + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 1fdc19830bf4cf677df60c5760e02ae53abc85cc..57ec3ca7677fcc7390503c3002b78fb9b973226c 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1473,7 +1473,7 @@ public abstract class LivingEntity extends Entity { + if (this instanceof ServerPlayer) { + CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, source, f1, amount, flag); + if (f2 > 0.0F && f2 < 3.4028235E37F) { +- ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(f2 * 10.0F)); ++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, carpetfixes.helpers.FastMath.round(f2 * 10.0F)); + } + } + +@@ -1995,9 +1995,9 @@ public abstract class LivingEntity extends Entity { + + if (f3 > 0.0F && f3 < 3.4028235E37F) { + if (this instanceof ServerPlayer) { +- ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F)); ++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, carpetfixes.helpers.FastMath.round(f3 * 10.0F)); + } else if (source.getEntity() instanceof ServerPlayer) { +- ((ServerPlayer) source.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F)); ++ ((ServerPlayer) source.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, carpetfixes.helpers.FastMath.round(f3 * 10.0F)); + } + } + } +@@ -2107,9 +2107,9 @@ public abstract class LivingEntity extends Entity { + float f3 = (float) -event.getDamage(DamageModifier.RESISTANCE); + if (f3 > 0.0F && f3 < 3.4028235E37F) { + if (this instanceof ServerPlayer) { +- ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F)); ++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, carpetfixes.helpers.FastMath.round(f3 * 10.0F)); + } else if (damagesource.getEntity() instanceof ServerPlayer) { +- ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F)); ++ ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, carpetfixes.helpers.FastMath.round(f3 * 10.0F)); + } + } + } +@@ -2141,10 +2141,10 @@ public abstract class LivingEntity extends Entity { + float f2 = absorptionModifier; + + if (f2 > 0.0F && f2 < 3.4028235E37F && this instanceof net.minecraft.world.entity.player.Player) { +- ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F)); ++ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_ABSORBED, carpetfixes.helpers.FastMath.round(f2 * 10.0F)); + } + if (f2 > 0.0F && f2 < 3.4028235E37F && damagesource.getEntity() instanceof ServerPlayer) { +- ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f2 * 10.0F)); ++ ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_ABSORBED, carpetfixes.helpers.FastMath.round(f2 * 10.0F)); + } + + if (f > 0 || !human) { +@@ -2152,7 +2152,7 @@ public abstract class LivingEntity extends Entity { + // PAIL: Be sure to drag all this code from the EntityHuman subclass each update. + ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damagesource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent + if (f < 3.4028235E37F) { +- ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F)); ++ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, carpetfixes.helpers.FastMath.round(f * 10.0F)); + } + } + // CraftBukkit end +@@ -2174,7 +2174,7 @@ public abstract class LivingEntity extends Entity { + CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true); + f2 = (float) -event.getDamage(DamageModifier.BLOCKING); + if (f2 > 0.0F && f2 < 3.4028235E37F) { +- ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F)); ++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, carpetfixes.helpers.FastMath.round(originalDamage * 10.0F)); + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 5c16235115a9233bce1f5b30bb020d105bdca3d1..d33b62dab84beb3386a8738dd5dbcb96f0552351 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1041,7 +1041,7 @@ public abstract class Player extends LivingEntity { + float f2 = f1 - f; + + if (f2 > 0.0F && f2 < 3.4028235E37F) { +- this.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F)); ++ this.awardStat(Stats.DAMAGE_ABSORBED, carpetfixes.helpers.FastMath.round(f2 * 10.0F)); + } + + if (f != 0.0F) { +@@ -1051,7 +1051,7 @@ public abstract class Player extends LivingEntity { + this.setHealth(this.getHealth() - f); + this.getCombatTracker().recordDamage(damagesource, f3, f); + if (f < 3.4028235E37F) { +- this.awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F)); ++ this.awardStat(Stats.DAMAGE_TAKEN, carpetfixes.helpers.FastMath.round(f * 10.0F)); + } + + } +@@ -1377,7 +1377,7 @@ public abstract class Player extends LivingEntity { + if (target instanceof LivingEntity) { + float f5 = f3 - ((LivingEntity) target).getHealth(); + +- this.awardStat(Stats.DAMAGE_DEALT, Math.round(f5 * 10.0F)); ++ this.awardStat(Stats.DAMAGE_DEALT, carpetfixes.helpers.FastMath.round(f5 * 10.0F)); + if (j > 0) { + // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item + EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), target.getBukkitEntity(), j * 4); +@@ -1641,29 +1641,29 @@ public abstract class Player extends LivingEntity { + int i; + + if (this.isSwimming()) { +- i = Math.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); ++ i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); + if (i > 0) { + this.awardStat(Stats.SWIM_ONE_CM, i); + this.causeFoodExhaustion(level.spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.isEyeInFluid(FluidTags.WATER)) { +- i = Math.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); ++ i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); + if (i > 0) { + this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, i); + this.causeFoodExhaustion(level.spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.isInWater()) { +- i = Math.round((float) Math.sqrt(dx * dx + dz * dz) * 100.0F); ++ i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dz * dz) * 100.0F); + if (i > 0) { + this.awardStat(Stats.WALK_ON_WATER_ONE_CM, i); + this.causeFoodExhaustion(level.spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.onClimbable()) { + if (dy > 0.0D) { +- this.awardStat(Stats.CLIMB_ONE_CM, (int) Math.round(dy * 100.0D)); ++ this.awardStat(Stats.CLIMB_ONE_CM, (int) carpetfixes.helpers.FastMath.round(dy * 100.0D)); + } + } else if (this.onGround) { +- i = Math.round((float) Math.sqrt(dx * dx + dz * dz) * 100.0F); ++ i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dz * dz) * 100.0F); + if (i > 0) { + if (this.isSprinting()) { + this.awardStat(Stats.SPRINT_ONE_CM, i); +@@ -1677,10 +1677,10 @@ public abstract class Player extends LivingEntity { + } + } + } else if (this.isFallFlying()) { +- i = Math.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); ++ i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); + this.awardStat(Stats.AVIATE_ONE_CM, i); + } else { +- i = Math.round((float) Math.sqrt(dx * dx + dz * dz) * 100.0F); ++ i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dz * dz) * 100.0F); + if (i > 25) { + this.awardStat(Stats.FLY_ONE_CM, i); + } +@@ -1691,7 +1691,7 @@ public abstract class Player extends LivingEntity { + + public void checkRidingStatistics(double dx, double dy, double dz) { + if (this.isPassenger()) { +- int i = Math.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); ++ int i = carpetfixes.helpers.FastMath.round((float) Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); + + if (i > 0) { + Entity entity = this.getVehicle(); +@@ -1718,7 +1718,7 @@ public abstract class Player extends LivingEntity { + return false; + } else { + if (fallDistance >= 2.0F) { +- this.awardStat(Stats.FALL_ONE_CM, (int) Math.round((double) fallDistance * 100.0D)); ++ this.awardStat(Stats.FALL_ONE_CM, (int) carpetfixes.helpers.FastMath.round((double) fallDistance * 100.0D)); + } + + return super.causeFallDamage(fallDistance, damageMultiplier, damageSource); +diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +index df4152f08b553df8895bb9e9dc39d586501ce431..3f3d7921dbea8d9daff3b5c914cb1646a858495a 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +@@ -341,7 +341,7 @@ public class EnchantmentHelper { + } else { + level += 1 + random.nextInt(i / 4 + 1) + random.nextInt(i / 4 + 1); + float f = (random.nextFloat() + random.nextFloat() - 1.0F) * 0.15F; +- level = Mth.clamp(Math.round((float)level + (float)level * f), 1, Integer.MAX_VALUE); ++ level = Mth.clamp(carpetfixes.helpers.FastMath.round((float)level + (float)level * f), 1, Integer.MAX_VALUE); + List list2 = getAvailableEnchantmentResults(level, stack, treasureAllowed); + if (!list2.isEmpty()) { + WeightedRandom.getRandomItem(random, list2).ifPresent(list::add); +diff --git a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java +index e57c5242866165e589277bd0184098c7806538ba..7d55c56e7372bd80d8f33f76a94dc6c18dd365a7 100644 +--- a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java +@@ -61,7 +61,7 @@ public class DaylightDetectorBlock extends BaseEntityBlock { + float f1 = f < 3.1415927F ? 0.0F : 6.2831855F; + + f += (f1 - f) * 0.2F; +- i = Math.round((float) i * Mth.cos(f)); ++ i = carpetfixes.helpers.FastMath.round((float) i * Mth.cos(f)); + } + + i = Mth.clamp(i, (int) 0, (int) 15); +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java +index 31918fa2eb38e42a5ea5366e559f25ea9d7d59ae..f512f1e0c46b96f831fdea3073e57c6bfd9c69eb 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java +@@ -61,7 +61,7 @@ public class LootingEnchantFunction extends LootItemConditionalFunction { + + float f = (float) i * this.value.getFloat(context); + +- stack.grow(Math.round(f)); ++ stack.grow(carpetfixes.helpers.FastMath.round(f)); + if (this.hasLimit() && stack.getCount() > this.limit) { + stack.setCount(this.limit); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index 2e1fba82dc1fceab42628d944e4ef429ce46c0f9..7aeeb180334660c81fecf22fbe62b66d4bce8b93 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -59,8 +59,8 @@ public final class Shapes { + int j = 1 << i; + double d = min * (double)j; + double e = max * (double)j; +- boolean bl = Math.abs(d - (double)Math.round(d)) < 1.0E-7D * (double)j; +- boolean bl2 = Math.abs(e - (double)Math.round(e)) < 1.0E-7D * (double)j; ++ boolean bl = Math.abs(d - (double)carpetfixes.helpers.FastMath.round(d)) < 1.0E-7D * (double)j; ++ boolean bl2 = Math.abs(e - (double)carpetfixes.helpers.FastMath.round(e)) < 1.0E-7D * (double)j; + if (bl && bl2) { + return i; + } +diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java +index 0ecac76577eb440a0c3104ef4603acec826c474d..4813bd6de894d0566e8631d8c71915c38c1da0f8 100644 +--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java ++++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java +@@ -52,7 +52,7 @@ public class TicksPerSecondCommand extends Command + private static String format(double tps) // Paper - Made static + { + return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() +- + ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise ++ + ( ( tps > 21.0 ) ? "*" : "" ) + Math.min( carpetfixes.helpers.FastMath.round( tps * 100.0 ) / 100.0, 20.0 ); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise + } + + // Yatopia start - Last tick time API +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 21c4a287c8fc550be36fc5b60bdc458d71a9d25d..06e44d2fba48a217dc1e63a1901f6d3a45b305b9 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -235,4 +235,9 @@ public class MiraiConfig { + serverMetrics = getBoolean("enable-server-metrics", serverMetrics); + } + ++ public static boolean riskyMathRoundOpt = false; ++ private static void fastMathRound() { ++ riskyMathRoundOpt = getBoolean("use-risky-mathround-opt", riskyMathRoundOpt); ++ } ++ + } +\ No newline at end of file diff --git a/patches/server/0072-lithium-entity.fast_elytra_check.patch b/patches/server/0072-lithium-entity.fast_elytra_check.patch new file mode 100644 index 0000000..74eb063 --- /dev/null +++ b/patches/server/0072-lithium-entity.fast_elytra_check.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sat, 8 Jan 2022 04:32:41 +0100 +Subject: [PATCH] lithium: entity.fast_elytra_check + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 57ec3ca7677fcc7390503c3002b78fb9b973226c..add84a30460e80259b452f105f81394696c90120 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3390,6 +3390,8 @@ public abstract class LivingEntity extends Entity { + } + + private void updateFallFlying() { ++ if (!this.isFallFlying()) return; // Mirai ++ + boolean flag = this.getSharedFlag(7); + + if (flag && !this.onGround && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) { diff --git a/patches/server/0073-lithium-profiler.patch b/patches/server/0073-lithium-profiler.patch new file mode 100644 index 0000000..f75abda --- /dev/null +++ b/patches/server/0073-lithium-profiler.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sat, 8 Jan 2022 04:56:54 +0100 +Subject: [PATCH] lithium: profiler + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index da6156f1d61ca20fd39f35da36c64afe62b2731b..56b3cd94b6883ff210af96f4995798a46375b70b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -217,6 +217,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } + ++ // Mirai start ++ @Override ++ public ProfilerFiller getProfiler() { ++ return this.getServer().getProfiler(); ++ } ++ // Mirai end ++ + @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper + } diff --git a/patches/server/0074-lithium-entity.fast_hand_swing.patch b/patches/server/0074-lithium-entity.fast_hand_swing.patch new file mode 100644 index 0000000..9620460 --- /dev/null +++ b/patches/server/0074-lithium-entity.fast_hand_swing.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sun, 9 Jan 2022 06:03:28 +0100 +Subject: [PATCH] lithium: entity.fast_hand_swing + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index add84a30460e80259b452f105f81394696c90120..48337a93af6792ce40fded0cb64f2f59c1e7995f 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2431,6 +2431,8 @@ public abstract class LivingEntity extends Entity { + } + + protected void updateSwingTime() { ++ if (!this.swinging && this.swingTime == 0) return; // Mirai ++ + int i = this.getCurrentSwingDuration(); + + if (this.swinging) { diff --git a/patches/server/0075-c2me-optimization.math.patch b/patches/server/0075-c2me-optimization.math.patch new file mode 100644 index 0000000..307c10d --- /dev/null +++ b/patches/server/0075-c2me-optimization.math.patch @@ -0,0 +1,267 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Tue, 21 Sep 2021 10:37:34 +0200 +Subject: [PATCH] c2me: optimization.math + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/C2ME-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java b/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java +index d5439c68bb7d3b54e085170e294709c067721207..809dae109043f06fb9a2f1257825ef172b7ac21f 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java +@@ -11,6 +11,27 @@ public final class ImprovedNoise { + public final double yo; + public final double zo; + ++ // Mirai start - c2me: optimization.math ++ private static final double[] FLAT_SIMPLEX_GRAD = new double[]{ ++ 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, ++ }; ++ // Mirai end ++ + public ImprovedNoise(RandomSource random) { + this.xo = random.nextDouble() * 256.0D; + this.yo = random.nextDouble() * 256.0D; +@@ -34,34 +55,38 @@ public final class ImprovedNoise { + return this.noise(x, y, z, 0.0D, 0.0D); + } + ++ // Mirai start - c2me: optimization.math ++ /** ++ * @author ishland ++ * @reason optimize: remove frequent type conversions ++ */ + /** @deprecated */ + @Deprecated + public double noise(double x, double y, double z, double yScale, double yMax) { + double d = x + this.xo; + double e = y + this.yo; + double f = z + this.zo; +- int i = Mth.floor(d); +- int j = Mth.floor(e); +- int k = Mth.floor(f); +- double g = d - (double)i; +- double h = e - (double)j; +- double l = f - (double)k; +- double o; +- if (yScale != 0.0D) { ++ double i = Mth.floor(d); ++ double j = Mth.floor(e); ++ double k = Mth.floor(f); ++ double g = d - i; ++ double h = e - j; ++ double l = f - k; ++ double o = 0.0D; ++ if (yScale != 0.0) { + double m; +- if (yMax >= 0.0D && yMax < h) { ++ if (yMax >= 0.0 && yMax < h) { + m = yMax; + } else { + m = h; + } + +- o = (double)Mth.floor(m / yScale + (double)1.0E-7F) * yScale; +- } else { +- o = 0.0D; ++ o = Mth.floor(m / yScale + 1.0E-7F) * yScale; + } + +- return this.sampleAndLerp(i, j, k, g, h - o, l, h); ++ return this.sampleAndLerp((int) i, (int) j, (int) k, g, h - o, l, h); + } ++ // Mirai end + + public double noiseWithDerivative(double x, double y, double z, double[] ds) { + double d = x + this.xo; +@@ -84,26 +109,76 @@ public final class ImprovedNoise { + return this.p[hash & 255] & 255; + } + ++ // Mirai start - c2me: optimization.math ++ /** ++ * @author ishland ++ * @reason inline math & small optimization: remove frequent type conversions and redundant ops ++ */ + private double sampleAndLerp(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double fadeLocalX) { +- int i = this.p(sectionX); +- int j = this.p(sectionX + 1); +- int k = this.p(i + sectionY); +- int l = this.p(i + sectionY + 1); +- int m = this.p(j + sectionY); +- int n = this.p(j + sectionY + 1); +- double d = gradDot(this.p(k + sectionZ), localX, localY, localZ); +- double e = gradDot(this.p(m + sectionZ), localX - 1.0D, localY, localZ); +- double f = gradDot(this.p(l + sectionZ), localX, localY - 1.0D, localZ); +- double g = gradDot(this.p(n + sectionZ), localX - 1.0D, localY - 1.0D, localZ); +- double h = gradDot(this.p(k + sectionZ + 1), localX, localY, localZ - 1.0D); +- double o = gradDot(this.p(m + sectionZ + 1), localX - 1.0D, localY, localZ - 1.0D); +- double p = gradDot(this.p(l + sectionZ + 1), localX, localY - 1.0D, localZ - 1.0D); +- double q = gradDot(this.p(n + sectionZ + 1), localX - 1.0D, localY - 1.0D, localZ - 1.0D); +- double r = Mth.smoothstep(localX); +- double s = Mth.smoothstep(fadeLocalX); +- double t = Mth.smoothstep(localZ); +- return Mth.lerp3(r, s, t, d, e, f, g, h, o, p, q); ++ // TODO [VanillaCopy] but optimized ++ final int var0 = sectionX & 0xFF; ++ final int var1 = (sectionX + 1) & 0xFF; ++ final int var2 = this.p[var0] & 0xFF; ++ final int var3 = this.p[var1] & 0xFF; ++ final int var4 = (var2 + sectionY) & 0xFF; ++ final int var5 = (var3 + sectionY) & 0xFF; ++ final int var6 = (var2 + sectionY + 1) & 0xFF; ++ final int var7 = (var3 + sectionY + 1) & 0xFF; ++ final int var8 = this.p[var4] & 0xFF; ++ final int var9 = this.p[var5] & 0xFF; ++ final int var10 = this.p[var6] & 0xFF; ++ final int var11 = this.p[var7] & 0xFF; ++ ++ final int var12 = (var8 + sectionZ) & 0xFF; ++ final int var13 = (var9 + sectionZ) & 0xFF; ++ final int var14 = (var10 + sectionZ) & 0xFF; ++ final int var15 = (var11 + sectionZ) & 0xFF; ++ final int var16 = (var8 + sectionZ + 1) & 0xFF; ++ final int var17 = (var9 + sectionZ + 1) & 0xFF; ++ final int var18 = (var10 + sectionZ + 1) & 0xFF; ++ final int var19 = (var11 + sectionZ + 1) & 0xFF; ++ final int var20 = (this.p[var12] & 15) << 2; ++ final int var21 = (this.p[var13] & 15) << 2; ++ final int var22 = (this.p[var14] & 15) << 2; ++ final int var23 = (this.p[var15] & 15) << 2; ++ final int var24 = (this.p[var16] & 15) << 2; ++ final int var25 = (this.p[var17] & 15) << 2; ++ final int var26 = (this.p[var18] & 15) << 2; ++ final int var27 = (this.p[var19] & 15) << 2; ++ final double var60 = localX - 1.0; ++ final double var61 = localY - 1.0; ++ final double var62 = localZ - 1.0; ++ final double var87 = FLAT_SIMPLEX_GRAD[(var20) | 0] * localX + FLAT_SIMPLEX_GRAD[(var20) | 1] * localY + FLAT_SIMPLEX_GRAD[(var20) | 2] * localZ; ++ final double var88 = FLAT_SIMPLEX_GRAD[(var21) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var21) | 1] * localY + FLAT_SIMPLEX_GRAD[(var21) | 2] * localZ; ++ final double var89 = FLAT_SIMPLEX_GRAD[(var22) | 0] * localX + FLAT_SIMPLEX_GRAD[(var22) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var22) | 2] * localZ; ++ final double var90 = FLAT_SIMPLEX_GRAD[(var23) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var23) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var23) | 2] * localZ; ++ final double var91 = FLAT_SIMPLEX_GRAD[(var24) | 0] * localX + FLAT_SIMPLEX_GRAD[(var24) | 1] * localY + FLAT_SIMPLEX_GRAD[(var24) | 2] * var62; ++ final double var92 = FLAT_SIMPLEX_GRAD[(var25) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var25) | 1] * localY + FLAT_SIMPLEX_GRAD[(var25) | 2] * var62; ++ final double var93 = FLAT_SIMPLEX_GRAD[(var26) | 0] * localX + FLAT_SIMPLEX_GRAD[(var26) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var26) | 2] * var62; ++ final double var94 = FLAT_SIMPLEX_GRAD[(var27) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var27) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var27) | 2] * var62; ++ ++ final double var95 = localX * 6.0 - 15.0; ++ final double var96 = fadeLocalX * 6.0 - 15.0; ++ final double var97 = localZ * 6.0 - 15.0; ++ final double var98 = localX * var95 + 10.0; ++ final double var99 = fadeLocalX * var96 + 10.0; ++ final double var100 = localZ * var97 + 10.0; ++ final double var101 = localX * localX * localX * var98; ++ final double var102 = fadeLocalX * fadeLocalX * fadeLocalX * var99; ++ final double var103 = localZ * localZ * localZ * var100; ++ ++ final double var113 = var87 + var101 * (var88 - var87); ++ final double var114 = var93 + var101 * (var94 - var93); ++ final double var115 = var91 + var101 * (var92 - var91); ++ final double var116 = var89 + var101 * (var90 - var89); ++ final double var117 = var114 - var115; ++ final double var118 = var102 * (var116 - var113); ++ final double var119 = var102 * var117; ++ final double var120 = var113 + var118; ++ final double var121 = var115 + var119; ++ return var120 + (var103 * (var121 - var120)); + } ++ // Mirai end + + private double sampleWithDerivative(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double[] ds) { + int i = this.p(sectionX); +diff --git a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +index ab0d50625c2d603cdf577602f76f6afb41166ed8..6f006d9f4c92b55dc3e0cc87d6c02ab9006b13e4 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +@@ -25,6 +25,10 @@ public class PerlinNoise { + private final double lowestFreqValueFactor; + private final double lowestFreqInputFactor; + private final double maxValue; ++ // Mirai start - c2me: optimization.math ++ private int noiseLevelsCount = 0; ++ private double[] amplitudesArray = null; ++ // Mirai end + + /** @deprecated */ + @Deprecated +@@ -85,6 +89,10 @@ public class PerlinNoise { + int i = this.amplitudes.size(); + int j = -this.firstOctave; + this.noiseLevels = new ImprovedNoise[i]; ++ // Mirai start - c2me: optimization.math ++ this.noiseLevelsCount = this.noiseLevels.length; ++ this.amplitudesArray = this.amplitudes.toDoubleArray(); ++ // Mirai end + if (xoroshiro) { + PositionalRandomFactory positionalRandomFactory = random.forkPositional(); + +@@ -140,9 +148,33 @@ public class PerlinNoise { + random.consumeCount(262); + } + ++ // Mirai start - c2me: optimization.math ++ /** ++ * @author ishland ++ * @reason optimize for common cases ++ */ + public double getValue(double x, double y, double z) { +- return this.getValue(x, y, z, 0.0D, 0.0D, false); ++ double d = 0.0; ++ double e = this.lowestFreqInputFactor; ++ double f = this.lowestFreqValueFactor; ++ ++ for(int i = 0; i < this.noiseLevelsCount; ++i) { ++ ImprovedNoise improvedNoise = this.noiseLevels[i]; ++ if (improvedNoise != null) { ++ @SuppressWarnings("deprecation") ++ double g = improvedNoise.noise( ++ wrap(x * e), wrap(y * e), wrap(z * e), 0.0, 0.0 ++ ); ++ d += this.amplitudesArray[i] * g * f; ++ } ++ ++ e *= 2.0; ++ f /= 2.0; ++ } ++ ++ return d; + } ++ // Mirai end + + /** @deprecated */ + @Deprecated +@@ -190,9 +222,15 @@ public class PerlinNoise { + return this.noiseLevels[this.noiseLevels.length - 1 - octave]; + } + ++ // Mirai start - c2me: optimization.math ++ /** ++ * @author ishland ++ * @reason remove frequent type conversion ++ */ + public static double wrap(double value) { +- return value - (double)Mth.lfloor(value / 3.3554432E7D + 0.5D) * 3.3554432E7D; ++ return value - Mth.lfloor(value / 3.3554432E7 + 0.5) * 3.3554432E7; + } ++ // Mirai end + + protected int firstOctave() { + return this.firstOctave; diff --git a/patches/server/0076-lithium-entity.fast_powder_snow_check.patch b/patches/server/0076-lithium-entity.fast_powder_snow_check.patch new file mode 100644 index 0000000..98ae28a --- /dev/null +++ b/patches/server/0076-lithium-entity.fast_powder_snow_check.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sat, 8 Jan 2022 03:51:28 +0100 +Subject: [PATCH] lithium: entity.fast_powder_snow_check + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 48337a93af6792ce40fded0cb64f2f59c1e7995f..0298679a3003729e824ca5223aa3dddb88ae7fe8 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -567,11 +567,11 @@ public abstract class LivingEntity extends Entity { + } + + protected void tryAddFrost() { +- if (!this.getBlockStateOn().isAir()) { ++ //if (!this.getBlockStateOn().isAir()) { // Mirai + int i = this.getTicksFrozen(); + + if (i > 0) { +- AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); ++ AttributeInstance attributemodifiable = this.getBlockStateOn().isAir() ? null : this.getAttribute(Attributes.MOVEMENT_SPEED); // Mirai + + if (attributemodifiable == null) { + return; +@@ -581,7 +581,7 @@ public abstract class LivingEntity extends Entity { + + attributemodifiable.addTransientModifier(new AttributeModifier(LivingEntity.SPEED_MODIFIER_POWDER_SNOW_UUID, "Powder snow slow", (double) f, AttributeModifier.Operation.ADDITION)); + } +- } ++ //} // Mirai + + } + diff --git a/patches/server/0077-lithium-collections.attributes.patch b/patches/server/0077-lithium-collections.attributes.patch new file mode 100644 index 0000000..7448952 --- /dev/null +++ b/patches/server/0077-lithium-collections.attributes.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sun, 9 Jan 2022 17:53:11 +0100 +Subject: [PATCH] lithium: collections.attributes + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 5b8e3aa4414f9e13bb5c05cf6e4fd89cb84fa1a4..adfb766778dcc34a741f4877651e9638b2d832ae 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -16,11 +16,13 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.resources.ResourceLocation; + import org.slf4j.Logger; ++import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; // Mirai ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Mirai + + public class AttributeMap { + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Map attributes = Maps.newHashMap(); +- private final Set dirtyAttributes = Sets.newHashSet(); ++ private final Map attributes = new Reference2ReferenceOpenHashMap<>(0); // Mirai ++ private final Set dirtyAttributes = new ReferenceOpenHashSet<>(0); // Mirai + private final AttributeSupplier supplier; + private final java.util.function.Function createInstance; // Pufferfish + diff --git a/patches/server/0078-lithium-collections.entity_by_type.patch b/patches/server/0078-lithium-collections.entity_by_type.patch new file mode 100644 index 0000000..4c0e283 --- /dev/null +++ b/patches/server/0078-lithium-collections.entity_by_type.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Fri, 7 Jan 2022 06:43:30 +0100 +Subject: [PATCH] lithium: collections.entity_by_type + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +index faa9965d233e8a0da30a8f9cfd8430623a1b6c57..31837bba990bee50fbb1b9f8ce581d78ff9b3213 100644 +--- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.Iterators; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; ++import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; // JettPack + import java.util.AbstractCollection; + import java.util.Collection; + import java.util.Collections; +@@ -13,7 +14,7 @@ import java.util.Map; + import java.util.stream.Collectors; + + public class ClassInstanceMultiMap extends AbstractCollection { +- private final Map, List> byClass = Maps.newHashMap(); ++ private final Map, List> byClass = new Reference2ReferenceOpenHashMap<>(); // JettPack + private final Class baseClass; + private final List allInstances = Lists.newArrayList(); + +@@ -58,7 +59,7 @@ public class ClassInstanceMultiMap extends AbstractCollection { + if (!this.baseClass.isAssignableFrom(type)) { + throw new IllegalArgumentException("Don't know how to search for " + type); + } else { +- List list = this.byClass.computeIfAbsent(type, (typeClass) -> { ++ List list = this.byClass.computeIfAbsent(type, (typeClass) -> { // JettPack - decomp fix + return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList()); + }); + return Collections.unmodifiableCollection(list); diff --git a/patches/server/0079-lithium-collections.entity_filtering.patch b/patches/server/0079-lithium-collections.entity_filtering.patch new file mode 100644 index 0000000..6ae6ad4 --- /dev/null +++ b/patches/server/0079-lithium-collections.entity_filtering.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Sat, 8 Jan 2022 03:33:04 +0100 +Subject: [PATCH] lithium: collections.entity_filtering + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +index 31837bba990bee50fbb1b9f8ce581d78ff9b3213..6a2fb50eb785e4cd29aac285c360da557f59652a 100644 +--- a/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java ++++ b/src/main/java/net/minecraft/util/ClassInstanceMultiMap.java +@@ -56,15 +56,32 @@ public class ClassInstanceMultiMap extends AbstractCollection { + } + + public Collection find(Class type) { +- if (!this.baseClass.isAssignableFrom(type)) { +- throw new IllegalArgumentException("Don't know how to search for " + type); +- } else { +- List list = this.byClass.computeIfAbsent(type, (typeClass) -> { // JettPack - decomp fix +- return this.allInstances.stream().filter(typeClass::isInstance).collect(Collectors.toList()); +- }); +- return Collections.unmodifiableCollection(list); ++ // JettPack start ++ Collection collection = this.byClass.get(type); ++ ++ if (collection == null) { ++ collection = this.createAllOfType(type); + } ++ ++ return (Collection) Collections.unmodifiableCollection(collection); ++ // JettPack end ++ } ++ ++ // JettPack start ++ private Collection createAllOfType(Class type) { ++ List list = new java.util.ArrayList<>(); ++ ++ for (T allElement : this.allInstances) { ++ if (type.isInstance(allElement)) { ++ list.add(allElement); ++ } ++ } ++ ++ this.byClass.put(type, list); ++ ++ return list; + } ++ // JettPack end + + @Override + public Iterator iterator() { diff --git a/patches/server/0080-lithium-chunk.serialization.patch b/patches/server/0080-lithium-chunk.serialization.patch new file mode 100644 index 0000000..511f718 --- /dev/null +++ b/patches/server/0080-lithium-chunk.serialization.patch @@ -0,0 +1,460 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jellysquid3 +Date: Mon, 10 Jan 2022 15:27:58 -0500 +Subject: [PATCH] lithium: chunk.serialization + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/LithiumHashPalette.java b/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/LithiumHashPalette.java +new file mode 100644 +index 0000000000000000000000000000000000000000..16debe176798f316c122e8e7aef2b50ecb9883a6 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/world/chunk/LithiumHashPalette.java +@@ -0,0 +1,189 @@ ++package me.jellysquid.mods.lithium.common.world.chunk; ++ ++import com.google.common.collect.ImmutableList; ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.objects.Reference2IntMap; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import java.util.Arrays; ++import java.util.List; ++import java.util.function.Predicate; ++import net.minecraft.core.IdMap; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.world.level.chunk.Palette; ++import net.minecraft.world.level.chunk.PaletteResize; ++ ++import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR; ++ ++/** ++ * Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling ++ * {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing. ++ */ ++public class LithiumHashPalette implements Palette { ++ private static final int ABSENT_VALUE = -1; ++ ++ private final IdMap idList; ++ private final PaletteResize resizeHandler; ++ private final int indexBits; ++ ++ private final Reference2IntMap table; ++ private T[] entries; ++ private int size = 0; ++ ++ public LithiumHashPalette(IdMap idList, PaletteResize resizeHandler, int indexBits, T[] entries, Reference2IntMap table, int size) { ++ this.idList = idList; ++ this.resizeHandler = resizeHandler; ++ this.indexBits = indexBits; ++ this.entries = entries; ++ this.table = table; ++ this.size = size; ++ } ++ ++ public LithiumHashPalette(IdMap idList, int bits, PaletteResize resizeHandler, List list) { ++ this(idList, bits, resizeHandler); ++ ++ for (T t : list) { ++ this.addEntry(t); ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ public LithiumHashPalette(IdMap idList, int bits, PaletteResize resizeHandler) { ++ this.idList = idList; ++ this.indexBits = bits; ++ this.resizeHandler = resizeHandler; ++ ++ int capacity = 1 << bits; ++ ++ this.entries = (T[]) new Object[capacity]; ++ this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR); ++ this.table.defaultReturnValue(ABSENT_VALUE); ++ } ++ ++ @Override ++ public int idFor(T obj) { ++ int id = this.table.getInt(obj); ++ ++ if (id == ABSENT_VALUE) { ++ id = this.computeEntry(obj); ++ } ++ ++ return id; ++ } ++ ++ @Override ++ public boolean maybeHas(Predicate predicate) { ++ for (int i = 0; i < this.size; ++i) { ++ if (predicate.test(this.entries[i])) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ private int computeEntry(T obj) { ++ int id = this.addEntry(obj); ++ ++ if (id >= 1 << this.indexBits) { ++ if (this.resizeHandler == null) { ++ throw new IllegalStateException("Cannot grow"); ++ } else { ++ id = this.resizeHandler.onResize(this.indexBits + 1, obj); ++ } ++ } ++ ++ return id; ++ } ++ ++ private int addEntry(T obj) { ++ int nextId = this.size; ++ ++ if (nextId >= this.entries.length) { ++ this.resize(this.size); ++ } ++ ++ this.table.put(obj, nextId); ++ this.entries[nextId] = obj; ++ ++ this.size++; ++ ++ return nextId; ++ } ++ ++ private void resize(int neededCapacity) { ++ this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1)); ++ } ++ ++ @Override ++ public T valueFor(int id) { ++ T[] entries = this.entries; ++ ++ if (id >= 0 && id < entries.length) { ++ return entries[id]; ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public void read(FriendlyByteBuf buf) { ++ this.clear(); ++ ++ int entryCount = buf.readVarInt(); ++ ++ for (int i = 0; i < entryCount; ++i) { ++ this.addEntry(this.idList.byId(buf.readVarInt())); ++ } ++ } ++ ++ @Override ++ public void write(FriendlyByteBuf buf) { ++ int size = this.size; ++ buf.writeVarInt(size); ++ ++ for (int i = 0; i < size; ++i) { ++ buf.writeVarInt(this.idList.getId(this.valueFor(i))); ++ } ++ } ++ ++ @Override ++ public int getSerializedSize() { ++ int size = FriendlyByteBuf.getVarIntSize(this.size); ++ ++ for (int i = 0; i < this.size; ++i) { ++ size += FriendlyByteBuf.getVarIntSize(this.idList.getId(this.valueFor(i))); ++ } ++ ++ return size; ++ } ++ ++ @Override ++ public int getSize() { ++ return this.size; ++ } ++ ++ @Override ++ public Palette copy() { ++ return new LithiumHashPalette<>(this.idList, this.resizeHandler, this.indexBits, this.entries.clone(), new Reference2IntOpenHashMap<>(this.table), this.size); ++ } ++ ++ private void clear() { ++ Arrays.fill(this.entries, null); ++ this.table.clear(); ++ this.size = 0; ++ } ++ ++ public List getElements() { ++ ImmutableList.Builder builder = new ImmutableList.Builder<>(); ++ for (T entry : this.entries) { ++ if (entry != null) { ++ builder.add(entry); ++ } ++ } ++ return builder.build(); ++ } ++ ++ public static Palette create(int bits, IdMap idList, PaletteResize listener, List list) { ++ return new LithiumHashPalette<>(idList, bits, listener, list); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index a8fd497f1b88f4563bfb517e31a8b309ab78ca9a..9864653f0609856672304a6c8123e20d2233e692 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -1,6 +1,7 @@ + package net.minecraft.util; + + import java.util.function.IntConsumer; ++import net.minecraft.world.level.chunk.Palette; // JettPack + + public interface BitStorage { + int getAndSet(int index, int value); +@@ -31,4 +32,6 @@ public interface BitStorage { + + } + // Paper end ++ ++ void compact(Palette srcPalette, Palette dstPalette, short[] out); // JettPack - lithium: chunk.serialization + } +diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java +index 80938f50fe136d954708e109e590dc76ba04067f..85fc9427d57890b3e9422be1a32f2af4885e4dfb 100644 +--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java ++++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java +@@ -2,6 +2,7 @@ package net.minecraft.util; + + import java.util.function.IntConsumer; + import javax.annotation.Nullable; ++import net.minecraft.world.level.chunk.Palette; // JettPack + import org.apache.commons.lang3.Validate; + + public class SimpleBitStorage implements BitStorage { +@@ -201,4 +202,44 @@ public class SimpleBitStorage implements BitStorage { + super(message); + } + } +-} ++ ++ // JettPack start - lithium: chunk.serialization ++ @Override ++ public void compact(Palette srcPalette, Palette dstPalette, short[] out) { ++ if (this.size >= Short.MAX_VALUE) { ++ throw new IllegalStateException("Array too large"); ++ } ++ ++ if (this.size != out.length) { ++ throw new IllegalStateException("Array size mismatch"); ++ } ++ ++ short[] mappings = new short[(int) (this.mask + 1)]; ++ ++ int idx = 0; ++ ++ for (long word : this.data) { ++ long bits = word; ++ ++ for (int elementIdx = 0; elementIdx < this.valuesPerLong; ++elementIdx) { ++ int value = (int) (bits & this.mask); ++ int remappedId = mappings[value]; ++ ++ if (remappedId == 0) { ++ remappedId = dstPalette.idFor(srcPalette.valueFor(value)) + 1; ++ mappings[value] = (short) remappedId; ++ } ++ ++ out[idx] = (short) (remappedId - 1); ++ bits >>= this.bits; ++ ++ ++idx; ++ ++ if (idx >= this.size) { ++ return; ++ } ++ } ++ } ++ } ++ // JettPack end ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java +index 225324615cc5d20b928714695f62adee91f43c5a..989e5affc9e249d303893bc2f7c4da275ef659e9 100644 +--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java ++++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java +@@ -2,6 +2,7 @@ package net.minecraft.util; + + import java.util.Arrays; + import java.util.function.IntConsumer; ++import net.minecraft.world.level.chunk.Palette; // JettPack + import org.apache.commons.lang3.Validate; + + public class ZeroBitStorage implements BitStorage { +@@ -72,4 +73,6 @@ public class ZeroBitStorage implements BitStorage { + public BitStorage copy() { + return this; + } ++ ++ @Override public void compact(Palette srcPalette, Palette dstPalette, short[] out) {} // JettPack + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java b/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java +index 888959febf9fc5f7d4f586a2b952b9759f871e05..538553dc46d2ec0c67c8b204f791ae73e39c58c4 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java +@@ -1,5 +1,5 @@ + package net.minecraft.world.level.chunk; + +-interface PaletteResize { ++public interface PaletteResize { // JettPack - make public + int onResize(int newBits, T object); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index a79ad93a8a56c5f001f6d71db2338b9062b429f1..456a8c4711ec065d5cf46c11e8b11c0c22738c46 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -22,8 +22,23 @@ import net.minecraft.util.Mth; + import net.minecraft.util.SimpleBitStorage; + import net.minecraft.util.ThreadingDetector; + import net.minecraft.util.ZeroBitStorage; ++import me.jellysquid.mods.lithium.common.world.chunk.LithiumHashPalette; // JettPack + + public class PalettedContainer implements PaletteResize { ++ // JettPack start - lithium: chunk.serialization ++ private static final ThreadLocal CACHED_ARRAY_4096 = ThreadLocal.withInitial(() -> new short[4096]); ++ private static final ThreadLocal CACHED_ARRAY_64 = ThreadLocal.withInitial(() -> new short[64]); ++ private Optional asOptional(long[] data) { ++ return Optional.of(Arrays.stream(data)); ++ } ++ private short[] getOrCreate(int size) { ++ return switch (size) { ++ case 64 -> CACHED_ARRAY_64.get(); ++ case 4096 -> CACHED_ARRAY_4096.get(); ++ default -> new short[size]; ++ }; ++ } ++ // JettPack end + private static final int MIN_PALETTE_BITS = 0; + private final PaletteResize dummyPaletteResize = (newSize, added) -> { + return 0; +@@ -278,30 +293,54 @@ public class PalettedContainer implements PaletteResize { + private synchronized PalettedContainer.DiscData write(IdMap idList, PalettedContainer.Strategy provider) { // Paper - synchronize + this.acquire(); + +- PalettedContainer.DiscData var12; ++ // JettPack start - lithium: chunk.serialization ++ Optional data = Optional.empty(); ++ List elements = null; + try { +- HashMapPalette hashMapPalette = new HashMapPalette<>(idList, this.data.storage.getBits(), this.dummyPaletteResize); +- int i = provider.size(); +- int[] is = new int[i]; +- this.data.storage.unpack(is); +- swapPalette(is, (id) -> { +- return hashMapPalette.idFor(this.data.palette.valueFor(id)); +- }); +- int j = provider.calculateBitsForSerialization(idList, hashMapPalette.getSize()); +- Optional optional; +- if (j != 0) { +- SimpleBitStorage simpleBitStorage = new SimpleBitStorage(j, i, is); +- optional = Optional.of(Arrays.stream(simpleBitStorage.getRaw())); +- } else { +- optional = Optional.empty(); ++ // The palette that will be serialized ++ LithiumHashPalette hashPalette = null; ++ ++ final Palette palette = this.data.palette(); ++ final BitStorage storage = this.data.storage(); ++ if (storage instanceof ZeroBitStorage || palette.getSize() == 1) { ++ // If the palette only contains one entry, don't attempt to repack it. ++ elements = List.of(palette.valueFor(0)); ++ } else if (palette instanceof LithiumHashPalette lithiumHashPalette) { ++ hashPalette = lithiumHashPalette; + } + +- var12 = new PalettedContainer.DiscData<>(hashMapPalette.getEntries(), optional); ++ if (elements == null) { ++ LithiumHashPalette compactedPalette = new LithiumHashPalette<>(idList, storage.getBits(), this.dummyPaletteResize); ++ short[] array = this.getOrCreate(provider.size()); ++ ++ storage.compact(this.data.palette(), compactedPalette, array); ++ ++ // If the palette didn't change during compaction, do a simple copy of the data array ++ if (hashPalette != null && hashPalette.getSize() == compactedPalette.getSize() && storage.getBits() == provider.calculateBitsForSerialization(idList, hashPalette.getSize())) { // paletteSize can de-sync from palette - see https://github.com/CaffeineMC/lithium-fabric/issues/279 ++ data = this.asOptional(storage.getRaw().clone()); ++ elements = hashPalette.getElements(); ++ } else { ++ int bits = provider.calculateBitsForSerialization(idList, compactedPalette.getSize()); ++ if (bits != 0) { ++ // Re-pack the integer array as the palette has changed size ++ SimpleBitStorage copy = new SimpleBitStorage(bits, array.length); ++ for (int i = 0; i < array.length; ++i) { ++ copy.set(i, array[i]); ++ } ++ ++ // We don't need to clone the data array as we are the sole owner of it ++ data = this.asOptional(copy.getRaw()); ++ } ++ ++ elements = compactedPalette.getElements(); ++ } ++ } + } finally { + this.release(); + } + +- return var12; ++ return new PalettedContainer.DiscData<>(elements, data); ++ // JettPack end + } + + private static void swapPalette(int[] is, IntUnaryOperator intUnaryOperator) { +@@ -333,17 +372,37 @@ public class PalettedContainer implements PaletteResize { + } + + public void count(PalettedContainer.CountConsumer counter) { +- if (this.data.palette.getSize() == 1) { +- counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); +- } else { +- Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); +- this.data.storage.getAll((key) -> { +- int2IntOpenHashMap.addTo(key, 1); +- }); +- int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { +- counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); +- }); ++ // JettPack start - lithium: chunk.serialization ++ int len = this.data.palette().getSize(); ++ ++ // Do not allocate huge arrays if we're using a large palette ++ if (len > 4096) { ++ // VanillaCopy ++ if (this.data.palette.getSize() == 1) { ++ counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); ++ } else { ++ Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); ++ this.data.storage.getAll((key) -> { ++ int2IntOpenHashMap.addTo(key, 1); ++ }); ++ int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { ++ counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); ++ }); ++ } ++ } ++ ++ short[] counts = new short[len]; ++ ++ this.data.storage().getAll(i -> counts[i]++); ++ ++ for (int i = 0; i < counts.length; i++) { ++ T obj = this.data.palette().valueFor(i); ++ ++ if (obj != null) { ++ counter.accept(obj, counts[i]); ++ } + } ++ // JettPack end + } + + static record Configuration(Palette.Factory factory, int bits) { diff --git a/patches/server/0081-lithium-world.inline_height.patch b/patches/server/0081-lithium-world.inline_height.patch new file mode 100644 index 0000000..03b2ac0 --- /dev/null +++ b/patches/server/0081-lithium-world.inline_height.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Mon, 10 Jan 2022 22:28:46 -0500 +Subject: [PATCH] lithium: world.inline_height + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index ec727ff5318f90e6241dc423a3156504c6e20c1d..38f2d97c2ffb6911c5020274b0a853ddc7d1589d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -76,6 +76,54 @@ public class LevelChunk extends ChunkAccess { + return ""; + } + }; ++ ++ // JettPack start - lithium: world.inline_height ++ @Override ++ public int getMaxBuildHeight() { ++ return this.level.getMaxBuildHeight(); ++ } ++ ++ @Override ++ public int getSectionsCount() { ++ return this.level.getSectionsCount(); ++ } ++ ++ @Override ++ public int getMinSection() { ++ return this.level.getMinSection(); ++ } ++ ++ @Override ++ public int getMaxSection() { ++ return this.level.getMaxSection(); ++ } ++ ++ @Override ++ public boolean isOutsideBuildHeight(BlockPos pos) { ++ return this.level.isOutsideBuildHeight(pos); ++ } ++ ++ @Override ++ public boolean isOutsideBuildHeight(int y) { ++ return this.level.isOutsideBuildHeight(y); ++ } ++ ++ @Override ++ public int getSectionIndex(int y) { ++ return this.level.getSectionIndex(y); ++ } ++ ++ @Override ++ public int getSectionIndexFromSectionY(int coord) { ++ return this.level.getSectionIndexFromSectionY(coord); ++ } ++ ++ @Override ++ public int getSectionYFromSectionIndex(int index) { ++ return this.level.getSectionYFromSectionIndex(index); ++ } ++ // JettPack end ++ + private final Map tickersInLevel; + public boolean loaded; + private boolean clientLightReady; diff --git a/patches/server/0082-Configurable-criterion-triggers.patch b/patches/server/0082-Configurable-criterion-triggers.patch new file mode 100644 index 0000000..257374b --- /dev/null +++ b/patches/server/0082-Configurable-criterion-triggers.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mykyta Komarnytskyy +Date: Sat, 24 Oct 2020 21:08:17 -0500 +Subject: [PATCH] Configurable criterion triggers + +This patch adds toggles for three criterion triggers that are called every tick. These can be very unnecessary, and especially in the case of CriterionTriggerEnterBlock, quite heavy. + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index bf42644d2053de4e3c7b9e531b18230b07f094c5..c32cba5dbbfb31ecdf6a8762390f4a6473ebf237 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -606,7 +606,7 @@ public class ServerPlayer extends Player { + + @Override + protected void onInsideBlock(BlockState state) { +- CriteriaTriggers.ENTER_BLOCK.trigger(this, state); ++ if (wtf.etil.mirai.MiraiConfig.criterionTriggerEnterBlock) CriteriaTriggers.ENTER_BLOCK.trigger(this, state); // Mirai + } + + @Override +@@ -652,7 +652,7 @@ public class ServerPlayer extends Player { + } + } + +- CriteriaTriggers.TICK.trigger(this); ++ if (wtf.etil.mirai.MiraiConfig.criterionTriggerTick) CriteriaTriggers.TICK.trigger(this); // Mirai + if (this.levitationStartPos != null) { + CriteriaTriggers.LEVITATION.trigger(this, this.levitationStartPos, this.tickCount - this.levitationStartTime); + } +@@ -728,9 +728,7 @@ public class ServerPlayer extends Player { + this.connection.send(new ClientboundSetExperiencePacket(this.experienceProgress, this.totalExperience, this.experienceLevel)); + } + +- if (this.tickCount % 20 == 0) { +- CriteriaTriggers.LOCATION.trigger(this); +- } ++ if (wtf.etil.mirai.MiraiConfig.criterionTriggerLocation && this.tickCount % 20 == 0) CriteriaTriggers.LOCATION.trigger(this); // Mirai + + // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border + if (this.oldLevel == -1) { +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 06e44d2fba48a217dc1e63a1901f6d3a45b305b9..c289ae5a720204b678b9563b3c54ad672b862107 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -240,4 +240,13 @@ public class MiraiConfig { + riskyMathRoundOpt = getBoolean("use-risky-mathround-opt", riskyMathRoundOpt); + } + ++ public static boolean criterionTriggerEnterBlock = true; ++ public static boolean criterionTriggerTick = true; ++ public static boolean criterionTriggerLocation = true; ++ private static void criterionTriggers() { ++ criterionTriggerEnterBlock = getBoolean("criterion-trigger.enter-block", criterionTriggerEnterBlock); ++ criterionTriggerTick = getBoolean("criterion-trigger.tick", criterionTriggerTick); ++ criterionTriggerLocation = getBoolean("criterion-trigger.location", criterionTriggerLocation); ++ } ++ + } +\ No newline at end of file diff --git a/patches/server/0083-Set-item-stuck-sleep-to-15-ticks.patch b/patches/server/0083-Set-item-stuck-sleep-to-15-ticks.patch new file mode 100644 index 0000000..18b2037 --- /dev/null +++ b/patches/server/0083-Set-item-stuck-sleep-to-15-ticks.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: tr7zw +Date: Fri, 31 Jul 2020 21:48:14 -0500 +Subject: [PATCH] Set item stuck sleep to 15 ticks + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index dae989bf664b9ab29d13825d5fce32e19852259e..222077807922288973273985bb459264d800bca6 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -128,7 +128,7 @@ public class ItemEntity extends Entity { + + if (this.level.isClientSide) { + this.noPhysics = false; +- } else { ++ } else if (!this.onGround || this.noPhysics || this.tickCount % 15 == 0) { // Mirai + this.noPhysics = !this.level.noCollision(this, this.getBoundingBox().deflate(1.0E-7D)); + if (this.noPhysics) { + this.moveTowardsClosestSpace(this.getX(), (this.getBoundingBox().minY + this.getBoundingBox().maxY) / 2.0D, this.getZ()); diff --git a/patches/server/0084-Smarter-statistics-ticking.patch b/patches/server/0084-Smarter-statistics-ticking.patch new file mode 100644 index 0000000..189cb32 --- /dev/null +++ b/patches/server/0084-Smarter-statistics-ticking.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mykyta Komarnytskyy +Date: Sat, 24 Oct 2020 21:03:53 -0500 +Subject: [PATCH] Smarter statistics ticking + +In vanilla, statistics that count time spent for an action (i.e. time played or sneak time) are incremented every tick. This is retarded. With this patch and a configured interval of 20, the statistics are only ticked every 20th tick and are incremented by 20 ticks at a time. This means a lot less ticking with the same accurate counting. +With an interval of 20, this patch saves roughly 3ms per tick on a server w/ 80 players online. + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index d33b62dab84beb3386a8738dd5dbcb96f0552351..695dd38d775c2e3d053f6779f03c7f260314faf0 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -275,19 +275,21 @@ public abstract class Player extends LivingEntity { + this.moveCloak(); + if (!this.level.isClientSide) { + this.foodData.tick(this); +- this.awardStat(Stats.PLAY_TIME); +- this.awardStat(Stats.TOTAL_WORLD_TIME); +- if (this.isAlive()) { +- this.awardStat(Stats.TIME_SINCE_DEATH); +- } +- +- if (this.isDiscrete()) { +- this.awardStat(Stats.CROUCH_TIME); +- } +- +- if (!this.isSleeping()) { +- this.awardStat(Stats.TIME_SINCE_REST); ++ // Mirai start ++ if (tickCount % 20 == 0) { ++ this.awardStat(Stats.PLAY_TIME, 20); ++ this.awardStat(Stats.TOTAL_WORLD_TIME, 20); ++ if (this.isAlive()) { ++ this.awardStat(Stats.TIME_SINCE_DEATH, 20); ++ } ++ if (this.isDiscrete()) { ++ this.awardStat(Stats.CROUCH_TIME, 20); ++ } ++ if (!this.isSleeping()) { ++ this.awardStat(Stats.TIME_SINCE_REST, 20); ++ } + } ++ // Mirai end + } + + int i = 29999999; diff --git a/patches/server/0085-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch b/patches/server/0085-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch new file mode 100644 index 0000000..bea802e --- /dev/null +++ b/patches/server/0085-lithium-ai.nearby_entity_tracking-and-ai.nearby_enti.patch @@ -0,0 +1,1061 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Thu, 13 Jan 2022 00:47:37 -0500 +Subject: [PATCH] lithium: ai.nearby_entity_tracking and + ai.nearby_entity_tracking.goals + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..af1efa5f7abfcf9440818f7b13e97bb65a9b2323 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/EntityTrackerEngine.java +@@ -0,0 +1,55 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker; ++ ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import java.util.List; ++import net.minecraft.world.Container; ++import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.level.entity.EntityAccess; ++ ++/** ++ * Helps to track in which entity sections entities of a certain type moved, appeared or disappeared by providing int ++ * masks for all Entity classes. ++ * Helps to track the entities within a world and provide notifications to listeners when a tracked entity enters or leaves a ++ * watched area. This removes the necessity to constantly poll the world for nearby entities each tick and generally ++ * provides a sizable boost to performance of hoppers. ++ */ ++public abstract class EntityTrackerEngine { ++ public static final List> MOVEMENT_NOTIFYING_ENTITY_CLASSES; ++ public static volatile Reference2IntOpenHashMap> CLASS_2_NOTIFY_MASK; ++ public static final int NUM_MOVEMENT_NOTIFYING_CLASSES; ++ ++ static { ++ MOVEMENT_NOTIFYING_ENTITY_CLASSES = List.of(ItemEntity.class, Container.class); ++ ++ CLASS_2_NOTIFY_MASK = new Reference2IntOpenHashMap<>(); ++ CLASS_2_NOTIFY_MASK.defaultReturnValue(-1); ++ NUM_MOVEMENT_NOTIFYING_CLASSES = MOVEMENT_NOTIFYING_ENTITY_CLASSES.size(); ++ } ++ ++ public static int getNotificationMask(Class entityClass) { ++ int notificationMask = CLASS_2_NOTIFY_MASK.getInt(entityClass); ++ if (notificationMask == -1) { ++ notificationMask = calculateNotificationMask(entityClass); ++ } ++ return notificationMask; ++ } ++ ++ private static int calculateNotificationMask(Class entityClass) { ++ int mask = 0; ++ for (int i = 0; i < MOVEMENT_NOTIFYING_ENTITY_CLASSES.size(); i++) { ++ Class superclass = MOVEMENT_NOTIFYING_ENTITY_CLASSES.get(i); ++ if (superclass.isAssignableFrom(entityClass)) { ++ mask |= 1 << i; ++ } ++ } ++ ++ //progress can be lost here, but it can only cost performance ++ //copy on write followed by publication in volatile field guarantees visibility of the final state ++ Reference2IntOpenHashMap> copy = CLASS_2_NOTIFY_MASK.clone(); ++ copy.put(entityClass, mask); ++ CLASS_2_NOTIFY_MASK = copy; ++ ++ return mask; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2dd34c4028fba33f2449cde5f9228283a3cb69dc +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListener.java +@@ -0,0 +1,94 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import me.jellysquid.mods.lithium.common.util.tuples.Range6Int; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.util.ClassInstanceMultiMap; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.entity.EntityAccess; ++import net.minecraft.world.level.entity.EntitySection; ++import net.minecraft.world.level.entity.EntitySectionStorage; ++import net.minecraft.world.level.levelgen.structure.BoundingBox; ++ ++/** ++ * The main interface used to receive events from the ++ * {@link me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine} of a world. ++ */ ++public interface NearbyEntityListener { ++ Range6Int EMPTY_RANGE = new Range6Int(0, 0, 0, -1, -1, -1); ++ /** ++ * Calls the callbacks for the chunk coordinates that this listener is leaving and entering ++ */ ++ default void forEachChunkInRangeChange(EntitySectionStorage entityCache, SectionPos prevCenterPos, SectionPos newCenterPos) { ++ Range6Int chunkRange = this.getChunkRange(); ++ if (chunkRange == EMPTY_RANGE) { ++ return; ++ } ++ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ ++ BoundingBox after = newCenterPos == null ? null : new BoundingBox(newCenterPos.getX() - chunkRange.negativeX(), newCenterPos.getY() - chunkRange.negativeY(), newCenterPos.getZ() - chunkRange.negativeZ(), newCenterPos.getX() + chunkRange.positiveX(), newCenterPos.getY() + chunkRange.positiveY(), newCenterPos.getZ() + chunkRange.positiveZ()); ++ BoundingBox before = prevCenterPos == null ? null : new BoundingBox(prevCenterPos.getX() - chunkRange.negativeX(), prevCenterPos.getY() - chunkRange.negativeY(), prevCenterPos.getZ() - chunkRange.negativeZ(), prevCenterPos.getX() + chunkRange.positiveX(), prevCenterPos.getY() + chunkRange.positiveY(), prevCenterPos.getZ() + chunkRange.positiveZ()); ++ if (before != null) { ++ for (int x = before.minX(); x <= before.maxX(); x++) { ++ for (int y = before.minY(); y <= before.maxY(); y++) { ++ for (int z = before.minZ(); z <= before.maxZ(); z++) { ++ if (after == null || !after.isInside(pos.set(x, y, z))) { ++ long sectionPos = SectionPos.asLong(x, y, z); ++ EntitySection trackingSection = entityCache.getOrCreateSection(sectionPos); ++ trackingSection.removeListener(entityCache, this); ++ if (trackingSection.isEmpty()) { ++ entityCache.remove(sectionPos); ++ } ++ } ++ } ++ } ++ } ++ } ++ if (after != null) { ++ for (int x = after.minX(); x <= after.maxX(); x++) { ++ for (int y = after.minY(); y <= after.maxY(); y++) { ++ for (int z = after.minZ(); z <= after.maxZ(); z++) { ++ if (before == null || !before.isInside(pos.set(x, y, z))) { ++ entityCache.getOrCreateSection(SectionPos.asLong(x, y, z)).addListener(this); ++ } ++ } ++ } ++ } ++ } ++ } ++ Range6Int getChunkRange(); ++ ++ /** ++ * Called by the entity tracker when an entity enters the range of this listener. ++ */ ++ void onEntityEnteredRange(Entity entity); ++ ++ /** ++ * Called by the entity tracker when an entity leaves the range of this listener or is removed from the world. ++ */ ++ void onEntityLeftRange(Entity entity); ++ ++ default Class getEntityClass() { ++ return Entity.class; ++ } ++ ++ /** ++ * Method to add all entities in the iteration order of the chunk section. This order is relevant and necessary ++ * to keep vanilla parity. ++ * @param the type of the Entities in the collection ++ * @param entityTrackingSection the section the entities are in ++ * @param collection the collection of Entities that entered the range of this listener ++ */ ++ default void onSectionEnteredRange(Object entityTrackingSection, ClassInstanceMultiMap collection) { ++ for (Entity entity : collection.find(this.getEntityClass())) { ++ this.onEntityEnteredRange(entity); ++ } ++ } ++ ++ default void onSectionLeftRange(Object entityTrackingSection, ClassInstanceMultiMap collection) { ++ for (Entity entity : collection.find(this.getEntityClass())) { ++ this.onEntityLeftRange(entity); ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1d7a656e683987b80916abaf1dcb10f77b9a8a4a +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityListenerMulti.java +@@ -0,0 +1,83 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import me.jellysquid.mods.lithium.common.util.tuples.Range6Int; ++import net.minecraft.world.entity.Entity; ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * Allows for multiple listeners on an entity to be grouped under one logical listener. No guarantees are made about the ++ * order of which each sub-listener will be notified. ++ */ ++public class NearbyEntityListenerMulti implements NearbyEntityListener { ++ private final List listeners = new ArrayList<>(4); ++ private Range6Int range = null; ++ ++ public void addListener(NearbyEntityListener listener) { ++ if (this.range != null) { ++ throw new IllegalStateException("Cannot add sublisteners after listening range was set!"); ++ } ++ this.listeners.add(listener); ++ } ++ ++ public void removeListener(NearbyEntityListener listener) { ++ this.listeners.remove(listener); ++ } ++ ++ @Override ++ public Range6Int getChunkRange() { ++ if (this.range != null) { ++ return this.range; ++ } ++ return this.calculateRange(); ++ } ++ private Range6Int calculateRange() { ++ if (this.listeners.isEmpty()) { ++ return this.range = EMPTY_RANGE; ++ } ++ int positiveX = -1; ++ int positiveY = -1; ++ int positiveZ = -1; ++ int negativeX = 0; ++ int negativeY = 0; ++ int negativeZ = 0; ++ ++ for (NearbyEntityListener listener : this.listeners) { ++ Range6Int chunkRange = listener.getChunkRange(); ++ positiveX = Math.max(chunkRange.positiveX(), positiveX); ++ positiveY = Math.max(chunkRange.positiveY(), positiveY); ++ positiveZ = Math.max(chunkRange.positiveZ(), positiveZ); ++ negativeX = Math.max(chunkRange.negativeX(), negativeX); ++ negativeY = Math.max(chunkRange.negativeY(), negativeY); ++ negativeZ = Math.max(chunkRange.negativeZ(), negativeZ); ++ ++ } ++ return this.range = new Range6Int(positiveX, positiveY, positiveZ, negativeX, negativeY, negativeZ); ++ } ++ ++ @Override ++ public void onEntityEnteredRange(Entity entity) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityEnteredRange(entity); ++ } ++ } ++ ++ @Override ++ public void onEntityLeftRange(Entity entity) { ++ for (NearbyEntityListener listener : this.listeners) { ++ listener.onEntityLeftRange(entity); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ StringBuilder sublisteners = new StringBuilder(); ++ String comma = ""; ++ for (NearbyEntityListener listener : this.listeners) { ++ sublisteners.append(comma).append(listener.toString()); ++ comma = ","; //trick to drop the first comma ++ } ++ ++ return super.toString() + " with sublisteners: [" + sublisteners + "]"; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..08d0be81d25b8b35506a7c03942356d0f33c2d89 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/NearbyEntityTracker.java +@@ -0,0 +1,140 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; ++import me.jellysquid.mods.lithium.common.util.tuples.Range6Int; ++import net.minecraft.core.SectionPos; ++import net.minecraft.core.Vec3i; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.ai.targeting.TargetingConditions; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++ ++/** ++ * Maintains a collection of all entities within the range of this listener. This allows AI goals to quickly ++ * assess nearby entities which match the provided class. ++ */ ++public class NearbyEntityTracker implements NearbyEntityListener { ++ private final Class clazz; ++ private final LivingEntity self; ++ ++ private final Reference2LongOpenHashMap nearbyEntities = new Reference2LongOpenHashMap<>(0); ++ private long counter; ++ private final Range6Int chunkBoxRadius; ++ ++ public NearbyEntityTracker(Class clazz, LivingEntity self, Vec3i boxRadius) { ++ this.clazz = clazz; ++ this.self = self; ++ this.chunkBoxRadius = new Range6Int( ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getX()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getY()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getZ()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getX()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getY()), ++ 1 + SectionPos.blockToSectionCoord(boxRadius.getZ()) ++ ); ++ } ++ ++ @Override ++ public Class getEntityClass() { ++ return this.clazz; ++ } ++ ++ @Override ++ public Range6Int getChunkRange() { ++ return this.chunkBoxRadius; ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Override ++ public void onEntityEnteredRange(Entity entity) { ++ if (!this.clazz.isInstance(entity)) { ++ return; ++ } ++ ++ this.nearbyEntities.put((T) entity, this.counter++); ++ } ++ ++ @Override ++ public void onEntityLeftRange(Entity entity) { ++ if (this.nearbyEntities.isEmpty() || !this.clazz.isInstance(entity)) { ++ return; ++ } ++ ++ this.nearbyEntities.removeLong(entity); ++ } ++ ++ /** ++ * Gets the closest T (extends LivingEntity) to the center of this tracker that also intersects with the given box and meets the ++ * requirements of the targetPredicate. ++ * The result may be different from vanilla if there are multiple closest entities. ++ * ++ * @param box the box the entities have to intersect ++ * @param targetPredicate predicate the entity has to meet ++ * @param x ++ * @param y ++ * @param z ++ * @return the closest Entity that meets all requirements (distance, box intersection, predicate, type T) ++ */ ++ public T getClosestEntity(AABB box, TargetingConditions targetPredicate, double x, double y, double z) { ++ T nearest = null; ++ double nearestDistance = Double.POSITIVE_INFINITY; ++ ++ for (T entity : this.nearbyEntities.keySet()) { ++ double distance; ++ if ( ++ (box == null || box.intersects(entity.getBoundingBox())) && ++ (distance = entity.distanceToSqr(x, y, z)) <= nearestDistance && ++ targetPredicate.test(this.self, entity) ++ ) { ++ if (distance == nearestDistance) { ++ nearest = this.getFirst(nearest, entity); ++ } else { ++ nearest = entity; ++ } ++ nearestDistance = distance; ++ } ++ } ++ ++ return nearest; ++ } ++ ++ /** ++ * Gets the Entity that is processed first in vanilla. ++ * @param entity1 one Entity ++ * @param entity2 the other Entity ++ * @return the Entity that is first in vanilla ++ */ ++ private T getFirst(T entity1, T entity2) { ++ if (this.getEntityClass() == Player.class) { ++ //Get first in player list ++ List players = this.self.getCommandSenderWorld().players(); ++ return players.indexOf((Player)entity1) < players.indexOf((Player)entity2) ? entity1 : entity2; ++ } else { ++ //Get first sorted by chunk section pos as long, then sorted by first added to the chunk section ++ //First added to this tracker and first added to the chunk section is equivalent here, because ++ //this tracker always tracks complete sections and the entities are added in order ++ long pos1 = SectionPos.asLong(entity1.blockPosition()); ++ long pos2 = SectionPos.asLong(entity2.blockPosition()); ++ if (pos1 < pos2) { ++ return entity1; ++ } else if (pos2 < pos1) { ++ return entity2; ++ } else { ++ if (this.nearbyEntities.getLong(entity1) < this.nearbyEntities.getLong(entity2)) { ++ return entity1; ++ } else { ++ return entity2; ++ } ++ } ++ } ++ ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + " for entity class: " + this.clazz.getName() + ", around entity: " + this.self.toString() + " with NBT: " + this.self.saveWithoutId(new CompoundTag()); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/SectionedEntityMovementTracker.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/SectionedEntityMovementTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c5a4bd43eb3d900a237f659f8ad4e1a3be05eb97 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/tracker/nearby/SectionedEntityMovementTracker.java +@@ -0,0 +1,143 @@ ++package me.jellysquid.mods.lithium.common.entity.tracker.nearby; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; ++import me.jellysquid.mods.lithium.common.util.tuples.WorldSectionBox; ++import net.minecraft.core.SectionPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.entity.EntityAccess; ++import net.minecraft.world.level.entity.EntitySection; ++import net.minecraft.world.level.entity.EntitySectionStorage; ++import java.util.ArrayList; ++import net.minecraft.world.entity.Entity; ++ ++public abstract class SectionedEntityMovementTracker { ++ final WorldSectionBox trackedWorldSections; ++ final Class clazz; ++ private final int trackedClass; ++ ArrayList> sortedSections; ++ boolean[] sectionVisible; ++ private int timesRegistered; ++ private ArrayList sectionChangeCounters; ++ ++ private long maxChangeTime; ++ ++ public SectionedEntityMovementTracker(WorldSectionBox interactionChunks, Class clazz) { ++ this.clazz = clazz; ++ this.trackedWorldSections = interactionChunks; ++ this.trackedClass = EntityTrackerEngine.MOVEMENT_NOTIFYING_ENTITY_CLASSES.indexOf(clazz); ++ assert this.trackedClass != -1; ++ } ++ ++ @Override ++ public int hashCode() { ++ return HashCommon.mix(this.trackedWorldSections.hashCode()) ^ HashCommon.mix(this.trackedClass) ^ this.getClass().hashCode(); ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ return obj.getClass() == this.getClass() && ++ this.clazz == ((SectionedEntityMovementTracker) obj).clazz && ++ this.trackedWorldSections.equals(((SectionedEntityMovementTracker) obj).trackedWorldSections); ++ } ++ ++ /** ++ * Method to quickly check whether any relevant entities moved inside the relevant entity sections after ++ * the last interaction attempt. ++ * ++ * @param lastCheckedTime time of the last interaction attempt ++ * @return whether any relevant entity moved in the tracked area ++ */ ++ public boolean isUnchangedSince(long lastCheckedTime) { ++ if (lastCheckedTime <= this.maxChangeTime) { ++ return false; ++ } ++ ArrayList sectionChangeCounters = this.sectionChangeCounters; ++ int trackedClass = this.trackedClass; ++ //noinspection ForLoopReplaceableByForEach ++ for (int i = 0, numCounters = sectionChangeCounters.size(); i < numCounters; i++) { ++ // >= instead of > is required here, as changes may occur in the same tick but after the last check ++ long sectionChangeTime = sectionChangeCounters.get(i)[trackedClass]; ++ if (lastCheckedTime <= sectionChangeTime) { ++ this.setChanged(sectionChangeTime); ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ public void register(ServerLevel world) { ++ assert world == this.trackedWorldSections.world(); ++ ++ if (this.timesRegistered == 0) { ++ //noinspection unchecked ++ EntitySectionStorage cache = world.entityManager.getCache(); ++ ++ this.sectionChangeCounters = new ArrayList<>(); ++ WorldSectionBox trackedSections = this.trackedWorldSections; ++ int size = trackedSections.numSections(); ++ assert size > 0; ++ this.sortedSections = new ArrayList<>(size); ++ this.sectionVisible = new boolean[size]; ++ ++ //vanilla iteration order in SectionedEntityCache is xzy ++ //WorldSectionBox upper coordinates are exclusive ++ for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) { ++ for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) { ++ for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) { ++ EntitySection section = cache.getOrCreateSection(SectionPos.asLong(x, y, z)); ++ this.sortedSections.add(section); ++ section.addListener(this); ++ } ++ } ++ } ++ this.setChanged(world.getGameTime()); ++ } ++ ++ this.timesRegistered++; ++ } ++ ++ public void unRegister(ServerLevel world) { ++ assert world == this.trackedWorldSections.world(); ++ if (--this.timesRegistered > 0) { ++ return; ++ } ++ assert this.timesRegistered == 0; ++ //noinspection unchecked ++ EntitySectionStorage cache = world.entityManager.getCache(); ++ cache.remove(this); ++ ++ ArrayList> sections = this.sortedSections; ++ for (int i = sections.size() - 1; i >= 0; i--) { ++ EntitySection section = sections.get(i); ++ section.removeListener(cache, this); ++ } ++ this.setChanged(world.getGameTime()); ++ } ++ ++ /** ++ * Register an entity section to this listener, so this listener can look for changes in the section. ++ */ ++ public void onSectionEnteredRange(EntitySection section) { ++ this.setChanged(this.trackedWorldSections.world().getGameTime()); ++ //noinspection SuspiciousMethodCalls ++ this.sectionVisible[this.sortedSections.lastIndexOf(section)] = true; ++ this.sectionChangeCounters.add(section.getMovementTimestampArray()); ++ } ++ ++ public void onSectionLeftRange(EntitySection section) { ++ this.setChanged(this.trackedWorldSections.world().getGameTime()); ++ //noinspection SuspiciousMethodCalls ++ this.sectionVisible[this.sortedSections.indexOf(section)] = false; ++ this.sectionChangeCounters.remove(section.getMovementTimestampArray()); ++ } ++ ++ /** ++ * Method that marks that new entities might have appeared or moved in the tracked chunk sections. ++ */ ++ private void setChanged(long atTime) { ++ if (atTime > this.maxChangeTime) { ++ this.maxChangeTime = atTime; ++ } ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/Range6Int.java b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/Range6Int.java +new file mode 100644 +index 0000000000000000000000000000000000000000..673d4b02a4baf779efa64c1d7b4fc284ec0887f9 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/Range6Int.java +@@ -0,0 +1,4 @@ ++package me.jellysquid.mods.lithium.common.util.tuples; ++ ++public record Range6Int(int negativeX, int negativeY, int negativeZ, int positiveX, int positiveY, int positiveZ) { ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/WorldSectionBox.java b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/WorldSectionBox.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ffc44c333f0dbaf07cc2c68ac390e0a7506a979e +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/tuples/WorldSectionBox.java +@@ -0,0 +1,24 @@ ++package me.jellysquid.mods.lithium.common.util.tuples; ++ ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.core.SectionPos; ++ ++//Y values use coordinates, not indices (y=0 -> chunkY=0) ++//upper bounds are EXCLUSIVE ++public record WorldSectionBox(Level world, int chunkX1, int chunkY1, int chunkZ1, int chunkX2, int chunkY2, ++ int chunkZ2) { ++ public static WorldSectionBox entityAccessBox(Level world, AABB box) { ++ int minX = SectionPos.posToSectionCoord((double)box.minX - 2.0D); ++ int minY = SectionPos.posToSectionCoord((double)box.minY - 2.0D); ++ int minZ = SectionPos.posToSectionCoord((double)box.minZ - 2.0D); ++ int maxX = SectionPos.posToSectionCoord((double)box.maxX + 2.0D) + 1; ++ int maxY = SectionPos.posToSectionCoord((double)box.maxY + 2.0D) + 1; ++ int maxZ = SectionPos.posToSectionCoord((double)box.maxZ + 2.0D) + 1; ++ return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ); ++ } ++ ++ public int numSections() { ++ return (this.chunkX2 - this.chunkX1) * (this.chunkY2 - this.chunkY1) * (this.chunkZ2 - this.chunkZ1); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f585b23c4f958184c4388c5c832dac1eaf7ae788..9358f38b10256e2f37f24edfce1e7699e8307dfe 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -150,9 +150,29 @@ import org.bukkit.event.entity.EntityPoseChangeEvent; + import org.bukkit.event.player.PlayerTeleportEvent; + import org.bukkit.plugin.PluginManager; + // CraftBukkit end ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListener; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListenerMulti; ++// JettPack end + + public abstract class Entity implements Nameable, EntityAccess, CommandSource { + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private NearbyEntityListenerMulti nearbyTracker = null; ++ ++ @Nullable ++ public NearbyEntityListenerMulti getListener() { ++ return this.nearbyTracker; ++ } ++ ++ public void addListener(NearbyEntityListener listener) { ++ if (this.nearbyTracker == null) { ++ this.nearbyTracker = new NearbyEntityListenerMulti(); ++ } ++ this.nearbyTracker.addListener(listener); ++ } ++ // JettPack end ++ + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; + public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +index ebf19357d7e3a5e4d02ac685fa70d118a8e40eb4..a2ad44bfe3a371a663e41c084f40e9a71f278f1c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +@@ -11,6 +11,12 @@ import net.minecraft.world.entity.ai.targeting.TargetingConditions; + import net.minecraft.world.entity.ai.util.DefaultRandomPos; + import net.minecraft.world.level.pathfinder.Path; + import net.minecraft.world.phys.Vec3; ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; ++import net.minecraft.world.entity.EntityDimensions; ++import net.minecraft.util.Mth; ++import net.minecraft.core.Vec3i; ++// JettPack end + + public class AvoidEntityGoal extends Goal { + protected final PathfinderMob mob; +@@ -26,6 +32,7 @@ public class AvoidEntityGoal extends Goal { + protected final Predicate avoidPredicate; + protected final Predicate predicateOnAvoidEntity; + private final TargetingConditions avoidEntityTargeting; ++ private NearbyEntityTracker nearbyTracker; // JettPack + + public AvoidEntityGoal(PathfinderMob mob, Class fleeFromType, float distance, double slowSpeed, double fastSpeed) { + this(mob, fleeFromType, (livingEntity) -> { +@@ -44,6 +51,13 @@ public class AvoidEntityGoal extends Goal { + this.pathNav = mob.getNavigation(); + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + this.avoidEntityTargeting = TargetingConditions.forCombat().range((double)distance).selector(inclusionSelector.and(extraInclusionSelector)); ++ // JettPack start - lithium: ai.nearby_entity_tracking.goals ++ EntityDimensions dimensions = this.mob.getType().getDimensions(); ++ double adjustedRange = dimensions.width * 0.5D + this.maxDist + 2D; ++ int horizontalRange = Mth.ceil(adjustedRange); ++ this.nearbyTracker = new NearbyEntityTracker<>(fleeFromType, mob, new Vec3i(horizontalRange, Mth.ceil(dimensions.height + 3 + 2), horizontalRange)); ++ mob.addListener(this.nearbyTracker); ++ // JettPack end + } + + public AvoidEntityGoal(PathfinderMob fleeingEntity, Class classToFleeFrom, float fleeDistance, double fleeSlowSpeed, double fleeFastSpeed, Predicate inclusionSelector) { +@@ -54,9 +68,7 @@ public class AvoidEntityGoal extends Goal { + + @Override + public boolean canUse() { +- this.toAvoid = this.mob.level.getNearestEntity(this.mob.level.getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate((double)this.maxDist, 3.0D, (double)this.maxDist), (livingEntity) -> { +- return true; +- }), this.avoidEntityTargeting, this.mob, this.mob.getX(), this.mob.getY(), this.mob.getZ()); ++ this.toAvoid = this.nearbyTracker.getClosestEntity(this.mob.getBoundingBox().inflate(this.maxDist, 3.0D, this.maxDist), this.avoidEntityTargeting, this.mob.getX(), this.mob.getY(), this.mob.getZ()); // JettPack - lithium: ai.nearby_entity_tracking.goals + if (this.toAvoid == null) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +index bae3db488371909c82946d6da294cda4e5a19cb5..76a9b7d823c563946e84a3e9c70ababe4476b5f4 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +@@ -8,6 +8,12 @@ import net.minecraft.world.entity.LivingEntity; + import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.ai.targeting.TargetingConditions; + import net.minecraft.world.entity.player.Player; ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityTracker; ++import net.minecraft.core.Vec3i; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.EntityDimensions; ++// JettPack end + + public class LookAtPlayerGoal extends Goal { + public static final float DEFAULT_PROBABILITY = 0.02F; +@@ -20,6 +26,7 @@ public class LookAtPlayerGoal extends Goal { + private final boolean onlyHorizontal; + protected final Class lookAtType; + protected final TargetingConditions lookAtContext; ++ private NearbyEntityTracker nearbyTracker; // JettPack + + public LookAtPlayerGoal(Mob mob, Class targetType, float range) { + this(mob, targetType, range, 0.02F); +@@ -43,6 +50,14 @@ public class LookAtPlayerGoal extends Goal { + } else { + this.lookAtContext = TargetingConditions.forNonCombat().range((double)range); + } ++ // JettPack start - lithium: ai.nearby_entity_tracking.goals ++ EntityDimensions dimensions = this.mob.getType().getDimensions(); ++ double adjustedRange = dimensions.width * 0.5D + this.lookDistance + 2D; ++ int horizontalRange = Mth.ceil(adjustedRange); ++ this.nearbyTracker = new NearbyEntityTracker<>(targetType, mob, new Vec3i(horizontalRange, Mth.ceil(dimensions.height + 3 + 2), horizontalRange)); ++ ++ mob.addListener(this.nearbyTracker); ++ // JettPack end + + } + +@@ -56,11 +71,9 @@ public class LookAtPlayerGoal extends Goal { + } + + if (this.lookAtType == Player.class) { +- this.lookAt = this.mob.level.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ this.lookAt = this.nearbyTracker.getClosestEntity(null, this.lookAtContext, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); // JettPack - lithium: ai.nearby_entity_tracking.goals + } else { +- this.lookAt = this.mob.level.getNearestEntity(this.mob.level.getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate((double)this.lookDistance, 3.0D, (double)this.lookDistance), (livingEntity) -> { +- return true; +- }), this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ this.lookAt = this.nearbyTracker.getClosestEntity(this.mob.getBoundingBox().inflate(this.lookDistance, 3.0D, this.lookDistance), this.lookAtContext, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); // JettPack - lithium: ai.nearby_entity_tracking.goals + } + + return this.lookAt != null; +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySection.java b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +index 9b39920baea28fe85a0bb6ada0e00a466b3ddf31..ee744cf4d5a794565decb46a7b1c7147f447270e 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySection.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +@@ -12,6 +12,13 @@ import com.ishland.vmp.common.general.collections.ITypeFilterableList; // Mirai + import it.unimi.dsi.fastutil.objects.ObjectArrayList; // Mirai + import net.minecraft.world.level.entity.EntityAccess; // Mirai + import net.minecraft.world.level.entity.EntityTypeTest; // Mirai ++// JettPack start ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListener; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.SectionedEntityMovementTracker; ++import net.minecraft.world.entity.Entity; ++// JettPack end + + public class EntitySection { + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -22,6 +29,70 @@ public class EntitySection { + public int inventoryEntityCount; + // Paper end + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private final ReferenceOpenHashSet nearbyEntityListeners = new ReferenceOpenHashSet<>(0); ++ private final ReferenceOpenHashSet> sectionVisibilityListeners = new ReferenceOpenHashSet<>(0); ++ private final long[] lastEntityMovementByType = new long[EntityTrackerEngine.NUM_MOVEMENT_NOTIFYING_CLASSES]; ++ private long pos; ++ ++ public void addListener(NearbyEntityListener listener) { ++ this.nearbyEntityListeners.add(listener); ++ if (this.chunkStatus.isAccessible()) { ++ listener.onSectionEnteredRange(this, this.storage); ++ } ++ } ++ ++ public void removeListener(EntitySectionStorage sectionedEntityCache, NearbyEntityListener listener) { ++ boolean removed = this.nearbyEntityListeners.remove(listener); ++ if (this.chunkStatus.isAccessible() && removed) { ++ listener.onSectionLeftRange(this, this.storage); ++ } ++ if (this.isEmpty()) { ++ sectionedEntityCache.remove(this.pos); ++ } ++ } ++ ++ public void addListener(SectionedEntityMovementTracker listener) { ++ this.sectionVisibilityListeners.add(listener); ++ if (this.chunkStatus.isAccessible()) { ++ listener.onSectionEnteredRange(this); ++ } ++ } ++ ++ public void removeListener(EntitySectionStorage sectionedEntityCache, SectionedEntityMovementTracker listener) { ++ boolean removed = this.sectionVisibilityListeners.remove(listener); ++ if (this.chunkStatus.isAccessible() && removed) { ++ listener.onSectionLeftRange(this); ++ } ++ if (this.isEmpty()) { ++ sectionedEntityCache.remove(this.pos); ++ } ++ } ++ ++ public void updateMovementTimestamps(int notificationMask, long time) { ++ long[] lastEntityMovementByType = this.lastEntityMovementByType; ++ int size = lastEntityMovementByType.length; ++ int mask; ++ for (int i = Integer.numberOfTrailingZeros(notificationMask); i < size; ) { ++ lastEntityMovementByType[i] = time; ++ mask = 0xffff_fffe << i; ++ i = Integer.numberOfTrailingZeros(notificationMask & mask); ++ } ++ } ++ ++ public long[] getMovementTimestampArray() { ++ return this.lastEntityMovementByType; ++ } ++ ++ public void setPos(long chunkSectionPos) { ++ this.pos = chunkSectionPos; ++ } ++ ++ public long getPos() { ++ return this.pos; ++ } ++ // JettPack end ++ + public EntitySection(Class entityClass, Visibility status) { + this.chunkStatus = status; + this.storage = new ClassInstanceMultiMap<>(entityClass); +@@ -36,6 +107,16 @@ public class EntitySection { + } + // Paper end + this.storage.add(entity); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ if (!this.chunkStatus.isAccessible() || this.nearbyEntityListeners.isEmpty()) { ++ return; ++ } ++ if (entity instanceof Entity entity1) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onEntityEnteredRange(entity1); ++ } ++ } ++ // JettPack end + } + + public boolean remove(T entity) { +@@ -46,6 +127,13 @@ public class EntitySection { + this.inventoryEntityCount--; + } + // Paper end ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ if (this.chunkStatus.isAccessible() && !this.nearbyEntityListeners.isEmpty() && entity instanceof Entity entity1) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onEntityLeftRange(entity1); ++ } ++ } ++ // JettPack end + return this.storage.remove(entity); + } + +@@ -107,6 +195,7 @@ public class EntitySection { + // Mirai end + + public boolean isEmpty() { ++ if (!this.nearbyEntityListeners.isEmpty() || !this.sectionVisibilityListeners.isEmpty()) return false; // JettPack + return this.storage.isEmpty(); + } + +@@ -119,6 +208,33 @@ public class EntitySection { + } + + public Visibility updateChunkStatus(Visibility status) { ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ if (this.chunkStatus.isAccessible() != status.isAccessible()) { ++ if (!status.isAccessible()) { ++ if (!this.nearbyEntityListeners.isEmpty()) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onSectionLeftRange(this, this.storage); ++ } ++ } ++ if (!this.sectionVisibilityListeners.isEmpty()) { ++ for (SectionedEntityMovementTracker listener : this.sectionVisibilityListeners) { ++ listener.onSectionLeftRange(this); ++ } ++ } ++ } else { ++ if (!this.nearbyEntityListeners.isEmpty()) { ++ for (NearbyEntityListener nearbyEntityListener : this.nearbyEntityListeners) { ++ nearbyEntityListener.onSectionEnteredRange(this, this.storage); ++ } ++ } ++ if (!this.sectionVisibilityListeners.isEmpty()) { ++ for (SectionedEntityMovementTracker listener : this.sectionVisibilityListeners) { ++ listener.onSectionEnteredRange(this); ++ } ++ } ++ } ++ } ++ // JettPack end + Visibility visibility = this.chunkStatus; + this.chunkStatus = status; + return visibility; +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +index ba8aef74c0c23a39b0e4387916a904e14d41a25a..5c2ce7fe796f80d9c4f634e4e14967f5c0765f75 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +@@ -20,6 +20,10 @@ import net.minecraft.core.SectionPos; + import net.minecraft.util.VisibleForDebug; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.phys.AABB; ++// JettPack start ++import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.SectionedEntityMovementTracker; ++// JettPack end + + public class EntitySectionStorage { + private final Class entityClass; +@@ -93,6 +97,20 @@ public class EntitySectionStorage { + } + // JettPack end + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private final Object2ReferenceOpenHashMap, SectionedEntityMovementTracker> sectionEntityMovementTrackers = new Object2ReferenceOpenHashMap<>(); ++ ++ public void remove(SectionedEntityMovementTracker tracker) { ++ this.sectionEntityMovementTrackers.remove(tracker); ++ } ++ ++ public > S deduplicate(S tracker) { ++ //noinspection unchecked ++ S storedTracker = (S) this.sectionEntityMovementTrackers.putIfAbsent(tracker, tracker); ++ return storedTracker == null ? tracker : storedTracker; ++ } ++ // JettPack end ++ + public LongStream getExistingSectionPositionsInChunk(long chunkPos) { + int i = ChunkPos.getX(chunkPos); + int j = ChunkPos.getZ(chunkPos); +@@ -120,7 +138,11 @@ public class EntitySectionStorage { + } + + public EntitySection getOrCreateSection(long sectionPos) { +- return this.sections.computeIfAbsent(sectionPos, this::createSection); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ EntitySection section = this.sections.computeIfAbsent(sectionPos, this::createSection); ++ section.setPos(sectionPos); ++ return section; ++ // JettPack end + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index 649df137ac36a62e53b42b7675c606e9e57795b2..20e9f1e2910091a5b0ef025ccd5341fa71cb57ea 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -35,6 +35,10 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.chunk.storage.EntityStorage; + import org.bukkit.craftbukkit.event.CraftEventFactory; + // CraftBukkit end ++// JettPack start ++import me.jellysquid.mods.lithium.common.entity.tracker.EntityTrackerEngine; ++import me.jellysquid.mods.lithium.common.entity.tracker.nearby.NearbyEntityListenerMulti; ++// JettPack end + + public class PersistentEntitySectionManager implements AutoCloseable { + +@@ -61,6 +65,12 @@ public class PersistentEntitySectionManager implements A + this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage); + } + ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ public EntitySectionStorage getCache() { ++ return this.sectionStorage; ++ } ++ // JettPack end ++ + // CraftBukkit start - add method to get all entities in chunk + public List getEntities(ChunkPos chunkCoordIntPair) { + return this.sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList()); +@@ -177,6 +187,16 @@ public class PersistentEntitySectionManager implements A + entitysection.add(entity); + this.entitySliceManager.addEntity((Entity)entity); // Paper + entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection)); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ NearbyEntityListenerMulti listener = ((Entity)entity).getListener(); ++ if (listener != null) { ++ listener.forEachChunkInRangeChange( ++ this.sectionStorage, ++ null, ++ SectionPos.of(entity.blockPosition()) ++ ); ++ } ++ // JettPack end + if (!existing) { + this.callbacks.onCreated(entity); + } +@@ -519,12 +539,25 @@ public class PersistentEntitySectionManager implements A + private final T entity; + private long currentSectionKey; + private EntitySection currentSection; ++ private int notificationMask; // JettPack + + Callback(EntityAccess entityaccess, long i, EntitySection entitysection) { + this.entity = (T) entityaccess; // CraftBukkit - decompile error + this.currentSectionKey = i; + this.currentSection = entitysection; ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ this.notificationMask = EntityTrackerEngine.getNotificationMask(this.entity.getClass()); ++ this.notifyMovementListeners(); ++ // JettPack end ++ } ++ ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ private void notifyMovementListeners() { ++ if (this.notificationMask != 0) { ++ this.currentSection.updateMovementTimestamps(this.notificationMask, ((Entity) this.entity).getCommandSenderWorld().getGameTime()); ++ } + } ++ // JettPack end + + @Override + public void onMove() { +@@ -566,7 +599,20 @@ public class PersistentEntitySectionManager implements A + } + // Paper end + this.updateStatus(visibility, entitysection.getStatus()); ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ NearbyEntityListenerMulti listener = ((Entity)this.entity).getListener(); ++ if (listener != null) ++ { ++ listener.forEachChunkInRangeChange( ++ PersistentEntitySectionManager.this.entitySliceManager.world.entityManager.getCache(), ++ SectionPos.of(this.currentSectionKey), ++ SectionPos.of(newSectionPos) ++ ); ++ } ++ this.notifyMovementListeners(); ++ // JettPack end + } ++ this.notifyMovementListeners(); // JettPack + + } + +@@ -599,6 +645,18 @@ public class PersistentEntitySectionManager implements A + @Override + public void onRemove(Entity.RemovalReason reason) { + org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper ++ // JettPack start - lithium: ai.nearby_entity_tracking ++ NearbyEntityListenerMulti listener = ((Entity)this.entity).getListener(); ++ if (listener != null) { ++ //noinspection unchecked ++ listener.forEachChunkInRangeChange( ++ PersistentEntitySectionManager.this.entitySliceManager.world.entityManager.getCache(), ++ SectionPos.of(this.currentSectionKey), ++ null ++ ); ++ } ++ this.notifyMovementListeners(); ++ // JettPack end + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); + } diff --git a/patches/server/0086-some-entity-micro-opts.patch b/patches/server/0086-some-entity-micro-opts.patch new file mode 100644 index 0000000..fa94627 --- /dev/null +++ b/patches/server/0086-some-entity-micro-opts.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Fri, 14 Jan 2022 12:00:42 -0500 +Subject: [PATCH] some entity micro opts + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 9358f38b10256e2f37f24edfce1e7699e8307dfe..5ca352d786b28630040034f6612a2cb6297058a8 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1802,10 +1802,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + } + ++ // JettPack start - allow passing BlockPos to getBrightness + public float getBrightness() { +- return this.level.hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getBrightness(new BlockPos(this.getX(), this.getEyeY(), this.getZ())) : 0.0F; ++ return this.getBrightness(new BlockPos(this.getX(), this.getEyeY(), this.getZ())); + } + ++ public float getBrightness(BlockPos pos) { ++ return this.level.hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getBrightness(pos) : 0.0F; ++ } ++ // JettPack end ++ + public void absMoveTo(double x, double y, double z, float yaw, float pitch) { + this.absMoveTo(x, y, z); + this.setYRot(yaw % 360.0F); +@@ -4222,6 +4228,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + // Paper end - block invalid positions + ++ // JettPack start ++ public final int getPositionHashCode() { ++ return this.position.hashCode(); ++ } ++ // JettPack end ++ + public final void setPosRaw(double x, double y, double z) { + // Paper start + this.setPosRaw(x, y, z, false); +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 340589e02b316553acefee480f86cf53d5d17560..e3dcb96f1291ad2f9161274499f4e6af6b836ae6 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1621,15 +1621,31 @@ public abstract class Mob extends LivingEntity { + + } + ++ // JettPack start - cache eye blockpos ++ private BlockPos cached_eye_blockpos; ++ private int cached_position_hashcode; ++ // JettPack end + public boolean isSunBurnTick() { + if (this.level.isDay() && !this.level.isClientSide) { +- float f = this.getBrightness(); +- BlockPos blockposition = new BlockPos(this.getX(), this.getEyeY(), this.getZ()); ++ // JettPack start - optimizations and cache eye blockpos ++ int positionHashCode = this.getPositionHashCode(); ++ if (this.cached_position_hashcode != positionHashCode) { ++ this.cached_eye_blockpos = new BlockPos(this.getX(), this.getEyeY(), this.getZ()); ++ this.cached_position_hashcode = positionHashCode; ++ } ++ ++ float f = this.getBrightness(cached_eye_blockpos); // Pass BlockPos to getBrightness ++ ++ // Check brightness first ++ if (f <= 0.5F) return false; ++ if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false; ++ + boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; + +- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level.canSeeSky(blockposition)) { ++ if (!flag && this.level.canSeeSky(this.cached_eye_blockpos)) { // JettPack - move brightness checks up + return true; + } ++ // JettPack end + } + + return false; diff --git a/patches/server/0087-lithium-cache-iterate-outwards.patch b/patches/server/0087-lithium-cache-iterate-outwards.patch new file mode 100644 index 0000000..94e2281 --- /dev/null +++ b/patches/server/0087-lithium-cache-iterate-outwards.patch @@ -0,0 +1,175 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Thu, 13 Jan 2022 15:38:29 -0500 +Subject: [PATCH] lithium: cache iterate outwards + +Original code by the PR below, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric/pull/123 (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a5d3aa309d3fdaab9e0fea2dfb91a080a3ac1193 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java +@@ -0,0 +1,71 @@ ++package me.jellysquid.mods.lithium.common.cached_blockpos_iteration; ++ ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongList; ++import java.util.Iterator; ++import java.util.Random; ++import java.util.concurrent.ConcurrentHashMap; ++import net.minecraft.core.BlockPos; ++ ++/** ++ * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 ++ */ ++public class IterateOutwardsCache { ++ //POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache ++ public static final BlockPos POS_ZERO = new BlockPos(0,0,0); ++ ++ ++ private final ConcurrentHashMap table; ++ private final int capacity; ++ private final Random random; ++ ++ public IterateOutwardsCache(int capacity) { ++ this.capacity = capacity; ++ this.table = new ConcurrentHashMap<>(31); ++ this.random = new Random(); ++ } ++ ++ private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { ++ // Add all positions to the cached list ++ for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) { ++ entry.add(pos.asLong()); ++ } ++ } ++ ++ public LongList getOrCompute(int xRange, int yRange, int zRange) { ++ long key = BlockPos.asLong(xRange, yRange, zRange); ++ ++ LongArrayList entry = this.table.get(key); ++ if (entry != null) { ++ return entry; ++ } ++ ++ // Cache miss: compute and store ++ entry = new LongArrayList(128); ++ ++ this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange); ++ ++ //decrease the array size, as of now it won't be modified anymore anyways ++ entry.trim(); ++ ++ //this might overwrite an entry as the same entry could have been computed and added during this thread's computation ++ //we do not use computeIfAbsent, as it can delay other threads for too long ++ Object previousEntry = this.table.put(key, entry); ++ ++ ++ if (previousEntry == null && this.table.size() > this.capacity) { ++ //prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded ++ final Iterator iterator = this.table.keySet().iterator(); ++ //prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting ++ for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) { ++ Long key2 = iterator.next(); ++ //random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers ++ if (this.random.nextInt(8) == 0 && key2 != key) { ++ iterator.remove(); ++ } ++ } ++ } ++ ++ return entry; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..493661ff3ac7247b68b7b02784b09b0eaf88fc52 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/LongList2BlockPosMutableIterable.java +@@ -0,0 +1,46 @@ ++package me.jellysquid.mods.lithium.common.cached_blockpos_iteration; ++ ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongList; ++import java.util.Iterator; ++import net.minecraft.core.BlockPos; ++ ++/** ++ * @author 2No2Name ++ */ ++public class LongList2BlockPosMutableIterable implements Iterable { ++ ++ private final LongList positions; ++ private final int xOffset, yOffset, zOffset; ++ ++ public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) { ++ this.xOffset = offset.getX(); ++ this.yOffset = offset.getY(); ++ this.zOffset = offset.getZ(); ++ this.positions = posList; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new Iterator() { ++ ++ private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator(); ++ private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ ++ @Override ++ public boolean hasNext() { ++ return it.hasNext(); ++ } ++ ++ @Override ++ public net.minecraft.core.BlockPos next() { ++ long nextPos = this.it.nextLong(); ++ return this.pos.set( ++ LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos), ++ LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos), ++ LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos)); ++ } ++ }; ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 465b15e7ca2a83735bdd6510256636723486b1cb..63f5acd8b5aaf936b08d389c8f871bea8c5373da 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -18,6 +18,12 @@ import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang3.Validate; + import org.slf4j.Logger; ++// JettPack start ++import it.unimi.dsi.fastutil.longs.LongList; ++import me.jellysquid.mods.lithium.common.cached_blockpos_iteration.IterateOutwardsCache; ++import me.jellysquid.mods.lithium.common.cached_blockpos_iteration.LongList2BlockPosMutableIterable; ++import static me.jellysquid.mods.lithium.common.cached_blockpos_iteration.IterateOutwardsCache.POS_ZERO; ++// JettPack end + + @Immutable + public class BlockPos extends Vec3i { +@@ -284,7 +290,18 @@ public class BlockPos extends Vec3i { + }; + } + ++ // JettPack start - lithium: cached iterate outwards ++ private static final IterateOutwardsCache ITERATE_OUTWARDS_CACHE = new IterateOutwardsCache(50); ++ private static final LongList HOGLIN_PIGLIN_CACHE = ITERATE_OUTWARDS_CACHE.getOrCompute(8, 4, 8); ++ // JettPack end ++ + public static Iterable withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) { ++ // JettPack start - lithium: cached iterate outwards ++ if (center != POS_ZERO) { ++ final LongList positions = rangeX == 8 && rangeY == 4 && rangeZ == 8 ? HOGLIN_PIGLIN_CACHE : ITERATE_OUTWARDS_CACHE.getOrCompute(rangeX, rangeY, rangeZ); ++ return new LongList2BlockPosMutableIterable(center, positions); ++ } ++ // JettPack end + int i = rangeX + rangeY + rangeZ; + // Paper start - rename variables to fix conflict with anonymous class (remap fix) + int centerX = center.getX(); diff --git a/patches/server/0088-lithium-ai.raid.patch b/patches/server/0088-lithium-ai.raid.patch new file mode 100644 index 0000000..4cbcb0a --- /dev/null +++ b/patches/server/0088-lithium-ai.raid.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jellysquid3 +Date: Tue, 18 Jan 2022 10:37:18 -0500 +Subject: [PATCH] lithium: ai.raid + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java +index f50abfd058e3adf1cf8c0f4468a60bc04e8541e7..a078f637ed005dfad5c2c2f601e4ba5964ba7f8d 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raid.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java +@@ -269,7 +269,16 @@ public class Raid { + this.status = Raid.RaidStatus.STOPPED; + } + ++ private boolean isBarDirty; // JettPack + public void tick() { ++ // JettPack start - lithium: ai.raid ++ if (this.isBarDirty) { ++ this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0F, 1.0F)); ++ ++ this.isBarDirty = false; ++ } ++ // JettPack end ++ + if (!this.isStopped()) { + if (this.status == Raid.RaidStatus.ONGOING) { + boolean flag = this.active; +@@ -625,7 +634,7 @@ public class Raid { + } + + public void updateBossbar() { +- this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0F, 1.0F)); ++ this.isBarDirty = true; // JettPack - lithium: ai.raid + } + + public float getHealthOfLivingRaiders() { +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java +index c5611465b1f9228a994c462c093cec94d7d08c2f..dfffbf47b58e16c9b5b750ccdb9717eacf414a64 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java +@@ -46,8 +46,9 @@ import net.minecraft.world.phys.Vec3; + public abstract class Raider extends PatrollingMonster { + + protected static final EntityDataAccessor IS_CELEBRATING = SynchedEntityData.defineId(Raider.class, EntityDataSerializers.BOOLEAN); ++ public static final ItemStack CACHED_OMINOUS_BANNER = Raid.getLeaderBannerInstance(); // JettPack - lithium: ai.raid + static final Predicate ALLOWED_ITEMS = (entityitem) -> { +- return !entityitem.hasPickUpDelay() && entityitem.isAlive() && ItemStack.matches(entityitem.getItem(), Raid.getLeaderBannerInstance()); ++ return !entityitem.hasPickUpDelay() && entityitem.isAlive() && ItemStack.matches(entityitem.getItem(), CACHED_OMINOUS_BANNER); // JettPack - lithium: ai.raid + }; + @Nullable + protected Raid raid; +@@ -149,7 +150,7 @@ public abstract class Raider extends PatrollingMonster { + } + } + +- if (!itemstack.isEmpty() && ItemStack.matches(itemstack, Raid.getLeaderBannerInstance()) && entityhuman != null) { ++ if (!itemstack.isEmpty() && ItemStack.matches(itemstack, CACHED_OMINOUS_BANNER) && entityhuman != null) { // JettPack - lithium: ai.raid + MobEffectInstance mobeffect = entityhuman.getEffect(MobEffects.BAD_OMEN); + byte b0 = 1; + int i; +@@ -304,6 +305,7 @@ public abstract class Raider extends PatrollingMonster { + public class ObtainRaidLeaderBannerGoal extends Goal { + + private final T mob; ++ private static final ItemStack CACHED_OMINOUS_BANNER = Raid.getLeaderBannerInstance(); // JettPack + + public ObtainRaidLeaderBannerGoal(T entityraider) { // CraftBukkit - decompile error + this.mob = entityraider; +@@ -315,7 +317,7 @@ public abstract class Raider extends PatrollingMonster { + if (!this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + Raid raid = this.mob.getCurrentRaid(); + +- if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) { ++ if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), CACHED_OMINOUS_BANNER)) { // JettPack - lithium: ai.raid + Raider entityraider = raid.getLeader(this.mob.getWave()); + + if (entityraider == null || !entityraider.isAlive()) { diff --git a/patches/server/0089-Dont-eat-blocks-in-non-ticking-chunks.patch b/patches/server/0089-Dont-eat-blocks-in-non-ticking-chunks.patch new file mode 100644 index 0000000..3aa10a0 --- /dev/null +++ b/patches/server/0089-Dont-eat-blocks-in-non-ticking-chunks.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 15 Jan 2022 06:23:04 -0600 +Subject: [PATCH] Dont eat blocks in non ticking chunks + +Original code by PurpurMC, licensed under MIT +You can find the original code on https://github.com/PurpurMC/Purpur + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0ab662448c24ba1d3c697f8096f03c88dd622e77..f69ed1e35d141909a2f1117233a22f289602a0ec 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1778,7 +1778,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); + } + +- final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { ++ public final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { // Purpur - package -> public + // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance + // tested and confirmed via System.nanoTime() + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index 99044ebe2c37ce951566e3463ddbfaeec46d2b3c..17cf979d88980746abe17fd3baf76fd106c18823 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -32,6 +32,12 @@ public class EatBlockGoal extends Goal { + + @Override + public boolean canUse() { ++ // Purpur start ++ net.minecraft.world.level.chunk.LevelChunk chunk = this.mob.level.getChunkIfLoaded(this.mob.blockPosition); ++ if (chunk == null || chunk.playerChunk == null || !((net.minecraft.server.level.ServerLevel) this.mob.level).getChunkSource().chunkMap.anyPlayerCloseEnoughForSpawning(chunk.playerChunk, this.mob.chunkPosition(), false)) { ++ return false; ++ } ++ // Purpur end + if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) { + return false; + } else { diff --git a/patches/server/0090-Fast-speed-check.patch b/patches/server/0090-Fast-speed-check.patch new file mode 100644 index 0000000..b96e3e9 --- /dev/null +++ b/patches/server/0090-Fast-speed-check.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jacob +Date: Wed, 19 Jan 2022 16:55:38 -0700 +Subject: [PATCH] Fast speed check + +Original code by NFT-Worlds, licensed under GNU General Public License v3.0 +You can find the original code on https://github.com/NFT-Worlds/Server + +etil2jz's note: +NFT-Worlds is related to Stellar devs, known for countless paid forks mostly taking open source patches, +doing questionable/buggy ones, and claiming breathtaking performance improvements. Never ever pay for +any of those Spigot forks! + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5ca352d786b28630040034f6612a2cb6297058a8..a3465acd25061d2b0833e8cae00403579e647b0b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1158,7 +1158,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + this.tryCheckInsideBlocks(); +- float f2 = this.getBlockSpeedFactor(); ++ float f2; ++ if (wtf.etil.mirai.MiraiConfig.fastSpeedCheck) { ++ if (this.getDeltaMovement().x == 0 && this.getDeltaMovement().z == 0) { ++ f2 = 1; ++ } else { ++ f2 = this.getBlockSpeedFactor(); ++ } ++ } else { ++ f2 = this.getBlockSpeedFactor(); ++ } + + this.setDeltaMovement(this.getDeltaMovement().multiply((double) f2, 1.0D, (double) f2)); + // Paper start - remove expensive streams from here +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index c289ae5a720204b678b9563b3c54ad672b862107..d304bc013ec2bd119d1af25163c694b8d09807f1 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -249,4 +249,9 @@ public class MiraiConfig { + criterionTriggerLocation = getBoolean("criterion-trigger.location", criterionTriggerLocation); + } + ++ public static boolean fastSpeedCheck = true; ++ private static void speedCheck() { ++ fastSpeedCheck = getBoolean("fast-speed-check", fastSpeedCheck); ++ } ++ + } +\ No newline at end of file diff --git a/patches/server/0091-lithium-block.moving_block_shapes.patch b/patches/server/0091-lithium-block.moving_block_shapes.patch new file mode 100644 index 0000000..4b80ce0 --- /dev/null +++ b/patches/server/0091-lithium-block.moving_block_shapes.patch @@ -0,0 +1,164 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Fri, 21 Jan 2022 08:41:34 -0500 +Subject: [PATCH] lithium: block.moving_block_shapes + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index 298feb6a8819efbbaf537aacc620524ae83270ea..81d23312488c519148c2ff501d2190e27749728e 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -52,6 +52,74 @@ public class PistonMovingBlockEntity extends BlockEntity { + this.extending = extending; + this.isSourcePiston = source; + } ++ // JettPack start - lithium: block.moving_block_shapes ++ private static final VoxelShape[] PISTON_BASE_WITH_MOVING_HEAD_SHAPES = precomputePistonBaseWithMovingHeadShapes(); ++ ++ /** ++ * We cache the offset and simplified VoxelShapes that are otherwise constructed on every call of getCollisionShape. ++ * For each offset direction and distance (6 directions, 2 distances each, and no direction with 0 distance) we ++ * store the offset and simplified VoxelShapes in the original VoxelShape when they are accessed the first time. ++ * We use safe publication, because both the Render and Server thread are using the cache. ++ * ++ * @param blockShape the original shape, must not be modified after passing it as an argument to this method ++ * @param offset the offset distance ++ * @param direction the offset direction ++ * @return blockShape offset and simplified ++ */ ++ private static VoxelShape getOffsetAndSimplified(VoxelShape blockShape, float offset, Direction direction) { ++ VoxelShape offsetSimplifiedShape = blockShape.getOffsetSimplifiedShape(offset, direction); ++ if (offsetSimplifiedShape == null) { ++ //create the offset shape and store it for later use ++ offsetSimplifiedShape = blockShape.move(direction.getStepX() * offset, direction.getStepY() * offset, direction.getStepZ() * offset).optimize(); ++ blockShape.setShape(offset, direction, offsetSimplifiedShape); ++ } ++ return offsetSimplifiedShape; ++ } ++ ++ /** ++ * Precompute all 18 possible configurations for the merged piston base and head shape. ++ * ++ * @return The array of the merged VoxelShapes, indexed by {@link PistonBlockEntityMixin#getIndexForMergedShape(float, Direction)} ++ */ ++ private static VoxelShape[] precomputePistonBaseWithMovingHeadShapes() { ++ float[] offsets = {0f, 0.5f, 1f}; ++ Direction[] directions = Direction.values(); ++ ++ VoxelShape[] mergedShapes = new VoxelShape[offsets.length * directions.length]; ++ ++ for (Direction facing : directions) { ++ VoxelShape baseShape = Blocks.PISTON.defaultBlockState().setValue(PistonBaseBlock.EXTENDED, true) ++ .setValue(PistonBaseBlock.FACING, facing).getCollisionShape(null, null); ++ for (float offset : offsets) { ++ //this cache is only required for the merged piston head + base shape. ++ //this shape is only used when !this.extending ++ //here: isShort = this.extending != 1.0F - this.progress < 0.25F can be simplified to: ++ //isShort = f < 0.25F , because f = getAmountExtended(this.progress) can be simplified to f == 1.0F - this.progress ++ //therefore isShort is dependent on the offset: ++ boolean isShort = offset < 0.25f; ++ ++ VoxelShape headShape = (Blocks.PISTON_HEAD.defaultBlockState().setValue(PistonHeadBlock.FACING, facing)) ++ .setValue(PistonHeadBlock.SHORT, isShort).getCollisionShape(null, null); ++ ++ VoxelShape offsetHead = headShape.move(facing.getStepX() * offset, ++ facing.getStepY() * offset, ++ facing.getStepZ() * offset); ++ mergedShapes[getIndexForMergedShape(offset, facing)] = Shapes.or(baseShape, offsetHead); ++ } ++ ++ } ++ ++ return mergedShapes; ++ } ++ ++ private static int getIndexForMergedShape(float offset, Direction direction) { ++ if (offset != 0f && offset != 0.5f && offset != 1f) { ++ return -1; ++ } ++ //shape of offset 0 is still dependent on the direction, due to piston head and base being directional blocks ++ return (int) (2 * offset) + (3 * direction.get3DDataValue()); ++ } ++ // JettPack end + + @Override + public CompoundTag getUpdateTag() { +@@ -351,10 +419,27 @@ public class PistonMovingBlockEntity extends BlockEntity { + } + + float f = this.getExtendedProgress(this.progress); ++ // JettPack start - lithium: block.moving_block_shapes ++ if (this.extending || !this.isSourcePiston || !(this.movedState.getBlock() instanceof PistonBaseBlock)) { ++ //here voxelShape2.isEmpty() is guaranteed, vanilla code would call union() which calls simplify() ++ VoxelShape blockShape = blockState.getCollisionShape(world, pos); ++ ++ //we cache the simplified shapes, as the simplify() method costs a lot of CPU time and allocates several objects ++ VoxelShape offsetAndSimplified = getOffsetAndSimplified(blockShape, Math.abs(f), f < 0f ? this.direction.getOpposite() : this.direction); ++ return offsetAndSimplified; ++ } else { ++ //retracting piston heads have to act like their base as well, as the base block is replaced with the moving block ++ //f >= 0f is guaranteed (assuming no other mod interferes) ++ int index = getIndexForMergedShape(f, this.direction); ++ return PISTON_BASE_WITH_MOVING_HEAD_SHAPES[index]; ++ } ++ /* + double d = (double)((float)this.direction.getStepX() * f); + double e = (double)((float)this.direction.getStepY() * f); + double g = (double)((float)this.direction.getStepZ() * f); + return Shapes.or(voxelShape, blockState.getCollisionShape(world, pos).move(d, e, g)); ++ */ ++ // JettPack end + } + } + +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +index fc001a374e6f557862726a5bab1bebbeede744fb..eefdd4c2c84d94d3a603f1b35686a78ca17b4e9d 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -26,6 +26,44 @@ public abstract class VoxelShape { + } + // Paper end + ++ // JettPack start - lithium: block.moving_block_shapes ++ private volatile VoxelShape[] offsetAndSimplified; ++ ++ public void setShape(float offset, Direction direction, VoxelShape offsetShape) { ++ if (offsetShape == null) { ++ throw new IllegalArgumentException("offsetShape must not be null!"); ++ } ++ int index = getIndexForOffsetSimplifiedShapes(offset, direction); ++ VoxelShape[] offsetAndSimplifiedShapes = this.offsetAndSimplified; ++ if (offsetAndSimplifiedShapes == null) { ++ offsetAndSimplifiedShapes = new VoxelShape[1 + 2 * 6]; ++ } else { ++ offsetAndSimplifiedShapes = offsetAndSimplifiedShapes.clone(); ++ } ++ offsetAndSimplifiedShapes[index] = offsetShape; ++ this.offsetAndSimplified = offsetAndSimplifiedShapes; ++ } ++ ++ public VoxelShape getOffsetSimplifiedShape(float offset, Direction direction) { ++ VoxelShape[] offsetAndSimplified = this.offsetAndSimplified; ++ if (offsetAndSimplified == null) { ++ return null; ++ } ++ int index = getIndexForOffsetSimplifiedShapes(offset, direction); ++ return offsetAndSimplified[index]; ++ } ++ ++ private static int getIndexForOffsetSimplifiedShapes(float offset, Direction direction) { ++ if (offset != 0f && offset != 0.5f && offset != 1f) { ++ throw new IllegalArgumentException("offset must be one of {0f, 0.5f, 1f}"); ++ } ++ if (offset == 0f) { ++ return 0; //can treat offsetting by 0 in all directions the same ++ } ++ return (int) (2 * offset) + 2 * direction.get3DDataValue(); ++ } ++ // JettPack end ++ + protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected + this.shape = voxels; + } diff --git a/patches/server/0092-lithium-shapes.blockstate_cache.patch b/patches/server/0092-lithium-shapes.blockstate_cache.patch new file mode 100644 index 0000000..8c1e05a --- /dev/null +++ b/patches/server/0092-lithium-shapes.blockstate_cache.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gegy +Date: Fri, 21 Jan 2022 10:19:06 -0500 +Subject: [PATCH] lithium: shapes.blockstate_cache + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/collections/Object2BooleanCacheTable.java b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/Object2BooleanCacheTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a833ec84b2053c805dd33821cc08939c1cb79e96 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/collections/Object2BooleanCacheTable.java +@@ -0,0 +1,59 @@ ++package me.jellysquid.mods.lithium.common.util.collections; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import java.util.function.Predicate; ++import net.minecraft.util.Mth; ++ ++/** ++ * A lossy hashtable implementation that stores a mapping between an object and a boolean. ++ *

++ * Any hash collisions will result in an overwrite: this is safe because the correct value can always be recomputed, ++ * given that the given operator is deterministic. ++ *

++ * This implementation is safe to use from multiple threads ++ */ ++public final class Object2BooleanCacheTable { ++ private final int mask; ++ ++ private final Node[] nodes; ++ ++ private final Predicate operator; ++ ++ @SuppressWarnings("unchecked") ++ public Object2BooleanCacheTable(int capacity, Predicate operator) { ++ int capacity1 = Mth.smallestEncompassingPowerOfTwo(capacity); ++ this.mask = capacity1 - 1; ++ ++ this.nodes = (Node[]) new Node[capacity1]; ++ ++ this.operator = operator; ++ } ++ ++ private static int hash(T key) { ++ return HashCommon.mix(key.hashCode()); ++ } ++ ++ public boolean get(T key) { ++ int idx = hash(key) & this.mask; ++ ++ Node node = this.nodes[idx]; ++ if (node != null && key.equals(node.key)) { ++ return node.value; ++ } ++ ++ boolean test = this.operator.test(key); ++ this.nodes[idx] = new Node<>(key, test); ++ ++ return test; ++ } ++ ++ static class Node { ++ final T key; ++ final boolean value; ++ ++ Node(T key, boolean value) { ++ this.key = key; ++ this.value = value; ++ } ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index f5f40589f53ac4696507699037df1ae4738e1637..974008385dd812924b9ab211114daa023f3a66e6 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -62,17 +62,19 @@ import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.slf4j.Logger; ++import me.jellysquid.mods.lithium.common.util.collections.Object2BooleanCacheTable; // JettPack + + public class Block extends BlockBehaviour implements ItemLike { + + private static final Logger LOGGER = LogUtils.getLogger(); + private final Holder.Reference builtInRegistryHolder; + public static final IdMapper BLOCK_STATE_REGISTRY = new IdMapper<>(); +- private static final LoadingCache SHAPE_FULL_BLOCK_CACHE = CacheBuilder.newBuilder().maximumSize(512L).weakKeys().build(new CacheLoader() { +- public Boolean load(VoxelShape voxelshape) { +- return !Shapes.joinIsNotEmpty(Shapes.block(), voxelshape, BooleanOp.NOT_SAME); +- } +- }); ++ // JettPack start - lithium: shapes.blockstate_cache ++ private static final Object2BooleanCacheTable FULL_CUBE_CACHE = new Object2BooleanCacheTable<>( ++ 512, ++ shape -> !Shapes.joinIsNotEmpty(Shapes.block(), shape, BooleanOp.NOT_SAME) ++ ); ++ // JettPack end + public static final int UPDATE_NEIGHBORS = 1; + public static final int UPDATE_CLIENTS = 2; + public static final int UPDATE_INVISIBLE = 4; +@@ -279,7 +281,7 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + public static boolean isShapeFullBlock(VoxelShape shape) { +- return (Boolean) Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape); ++ return FULL_CUBE_CACHE.get(shape); // JettPack - lithium: shapes.blockstate_cache + } + + public boolean propagatesSkylightDown(BlockState state, BlockGetter world, BlockPos pos) { diff --git a/patches/server/0093-lithium-gen.patch b/patches/server/0093-lithium-gen.patch new file mode 100644 index 0000000..0c21048 --- /dev/null +++ b/patches/server/0093-lithium-gen.patch @@ -0,0 +1,243 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SuperCoder7979 <25208576+SuperCoder7979@users.noreply.github.com> +Date: Fri, 22 Jan 2021 16:38:19 -0500 +Subject: [PATCH] lithium: gen + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/util/Pos.java b/src/main/java/me/jellysquid/mods/lithium/common/util/Pos.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c99eff34c1be07508c88fe9525c3ae1a087fdef7 +--- /dev/null ++++ b/src/main/java/me/jellysquid/mods/lithium/common/util/Pos.java +@@ -0,0 +1,92 @@ ++package me.jellysquid.mods.lithium.common.util; ++ ++import net.minecraft.core.SectionPos; ++import net.minecraft.world.level.LevelHeightAccessor; ++ ++public class Pos { ++ ++ public static class BlockCoord { ++ public static int getYSize(LevelHeightAccessor view) { ++ return view.getHeight(); ++ } ++ public static int getMinY(LevelHeightAccessor view) { ++ return view.getMinBuildHeight(); ++ } ++ public static int getMaxYInclusive(LevelHeightAccessor view) { ++ return view.getMaxBuildHeight() - 1; ++ } ++ public static int getMaxYExclusive(LevelHeightAccessor view) { ++ return view.getMaxBuildHeight(); ++ } ++ ++ public static int getMaxInSectionCoord(int sectionCoord) { ++ return 15 + getMinInSectionCoord(sectionCoord); ++ } ++ ++ public static int getMaxYInSectionIndex(LevelHeightAccessor view, int sectionIndex){ ++ return getMaxInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex)); ++ } ++ ++ public static int getMinInSectionCoord(int sectionCoord) { ++ return SectionPos.sectionToBlockCoord(sectionCoord); ++ } ++ ++ public static int getMinYInSectionIndex(LevelHeightAccessor view, int sectionIndex) { ++ return getMinInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex)); ++ } ++ } ++ ++ public static class ChunkCoord { ++ public static int fromBlockCoord(int blockCoord) { ++ return SectionPos.blockToSectionCoord(blockCoord); ++ } ++ ++ public static int fromBlockSize(int i) { ++ return i >> 4; //same method as fromBlockCoord, just be clear about coord/size semantic difference ++ } ++ } ++ ++ public static class SectionYCoord { ++ public static int getNumYSections(LevelHeightAccessor view) { ++ return view.getSectionsCount(); ++ } ++ public static int getMinYSection(LevelHeightAccessor view) { ++ return view.getMinSection(); ++ } ++ public static int getMaxYSectionInclusive(LevelHeightAccessor view) { ++ return view.getMaxSection() - 1; ++ } ++ public static int getMaxYSectionExclusive(LevelHeightAccessor view) { ++ return view.getMaxSection(); ++ } ++ ++ public static int fromSectionIndex(LevelHeightAccessor view, int sectionCoord) { ++ return sectionCoord + SectionYCoord.getMinYSection(view); ++ } ++ public static int fromBlockCoord(int blockCoord) { ++ return SectionPos.blockToSectionCoord(blockCoord); ++ } ++ } ++ ++ public static class SectionYIndex { ++ public static int getNumYSections(LevelHeightAccessor view) { ++ return view.getSectionsCount(); ++ } ++ public static int getMinYSectionIndex(LevelHeightAccessor view) { ++ return 0; ++ } ++ public static int getMaxYSectionIndexInclusive(LevelHeightAccessor view) { ++ return view.getSectionsCount() - 1; ++ } ++ public static int getMaxYSectionIndexExclusive(LevelHeightAccessor view) { ++ return view.getSectionsCount(); ++ } ++ ++ public static int fromSectionCoord(LevelHeightAccessor view, int sectionCoord) { ++ return sectionCoord - SectionYCoord.getMinYSection(view); ++ } ++ public static int fromBlockCoord(LevelHeightAccessor view, int blockCoord) { ++ return fromSectionCoord(view, SectionPos.blockToSectionCoord(blockCoord)); ++ } ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 1e656438d9ee89b97c660f3b3ec671be6546c6b7..183add42ff506ed734847e416c85b4c006c4272c 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -51,6 +51,7 @@ import net.minecraft.world.phys.AABB; + import net.minecraft.world.ticks.LevelTickAccess; + import net.minecraft.world.ticks.WorldGenTickAccess; + import org.slf4j.Logger; ++import me.jellysquid.mods.lithium.common.util.Pos; // Mirai - lithium: gen + + public class WorldGenRegion implements WorldGenLevel { + +@@ -78,6 +79,8 @@ public class WorldGenRegion implements WorldGenLevel { + @Nullable + private Supplier currentlyGenerating; + private final AtomicLong subTickCount = new AtomicLong(); ++ private ChunkAccess[] chunksArr; // Mirai - lithium: gen ++ private int minChunkX, minChunkZ; // Mirai - lithium: gen + + public WorldGenRegion(ServerLevel world, List chunks, ChunkStatus status, int placementRadius) { + this.generatingStatus = status; +@@ -100,6 +103,12 @@ public class WorldGenRegion implements WorldGenLevel { + this.lastPos = ((ChunkAccess) chunks.get(chunks.size() - 1)).getPos(); + this.structureFeatureManager = world.structureFeatureManager().forWorldGenRegion(this); + } ++ // Mirai start - lithium: gen ++ this.minChunkX = this.firstPos.x; ++ this.minChunkZ = this.firstPos.z; ++ ++ this.chunksArr = chunks.toArray(new ChunkAccess[0]); ++ // Mirai end + } + + public ChunkPos getCenter() { +@@ -111,11 +120,33 @@ public class WorldGenRegion implements WorldGenLevel { + this.currentlyGenerating = structureName; + } + ++ // Mirai start - lithium: gen ++ /** ++ * @reason Use the chunk array for faster access ++ * @author SuperCoder7979, 2No2Name ++ */ + @Override + public ChunkAccess getChunk(int chunkX, int chunkZ) { +- return this.getChunk(chunkX, chunkZ, ChunkStatus.EMPTY); ++ int x = chunkX - this.minChunkX; ++ int z = chunkZ - this.minChunkZ; ++ int w = this.size; ++ ++ if (x >= 0 && z >= 0 && x < w && z < w) { ++ return this.chunksArr[x + z * w]; ++ } else { ++ throw new NullPointerException("No chunk exists at " + new ChunkPos(chunkX, chunkZ)); ++ } + } + ++ /** ++ * Use our chunk fetch function ++ */ ++ public ChunkAccess getChunk(BlockPos pos) { ++ // Skip checking chunk.getStatus().isAtLeast(ChunkStatus.EMPTY) here, because it is always true ++ return this.getChunk(Pos.ChunkCoord.fromBlockCoord(pos.getX()), Pos.ChunkCoord.fromBlockCoord(pos.getZ())); ++ } ++ // Mirai end ++ + @Nullable + @Override + public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { +@@ -171,10 +202,24 @@ public class WorldGenRegion implements WorldGenLevel { + } + // Paper end + ++ // Mirai start - lithium: gen ++ /** ++ * @reason Avoid pointer de-referencing, make method easier to inline ++ * @author JellySquid ++ */ + @Override + public BlockState getBlockState(BlockPos pos) { +- return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos); ++ int x = (Pos.ChunkCoord.fromBlockCoord(pos.getX())) - this.minChunkX; ++ int z = (Pos.ChunkCoord.fromBlockCoord(pos.getZ())) - this.minChunkZ; ++ int w = this.size; ++ ++ if (x >= 0 && z >= 0 && x < w && z < w) { ++ return this.chunksArr[x + z * w].getBlockState(pos); ++ } else { ++ throw new NullPointerException("No chunk exists at " + new ChunkPos(pos)); ++ } + } ++ // Mirai end + + @Override + public FluidState getFluidState(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index 51864ad3fbc874332da63254097a1b8048c46ce4..07d476650cba7ff3bf7575ee0c35fe9a523cf2ae 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -75,6 +75,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + private final Climate.Sampler sampler; + private final SurfaceSystem surfaceSystem; + private final Aquifer.FluidPicker globalFluidPicker; ++ private int cachedSeaLevel; // Mirai - lithium: gen + + public NoiseBasedChunkGenerator(Registry noiseRegistry, Registry structuresRegistry, BiomeSource biomeSource, long seed, Holder settings) { + this(noiseRegistry, structuresRegistry, biomeSource, biomeSource, seed, settings); +@@ -101,6 +102,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + return l < Math.min(-54, j) ? aquifer_b : aquifer_b1; + }; + this.surfaceSystem = new SurfaceSystem(structuresRegistry, this.defaultBlock, j, seed, generatorsettingbase.getRandomSource()); ++ this.cachedSeaLevel = this.settings.value().seaLevel(); // Mirai - lithium: gen + } + + @Override +@@ -419,10 +421,19 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + return ((NoiseGeneratorSettings) this.settings.value()).noiseSettings().height(); + } + ++ // Mirai start - lithium: gen ++ /** ++ * Use cached sea level instead of retrieving from the registry every time. ++ * This method is called for every block in the chunk so this will save a lot of registry lookups. ++ * ++ * @author SuperCoder79 ++ * @reason avoid registry lookup ++ */ + @Override + public int getSeaLevel() { +- return ((NoiseGeneratorSettings) this.settings.value()).seaLevel(); ++ return this.cachedSeaLevel; + } ++ // Mirai end + + @Override + public int getMinY() { diff --git a/patches/server/0094-c2me-reduce_allocs.patch b/patches/server/0094-c2me-reduce_allocs.patch new file mode 100644 index 0000000..fce6254 --- /dev/null +++ b/patches/server/0094-c2me-reduce_allocs.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Mon, 24 Jan 2022 10:56:10 -0500 +Subject: [PATCH] c2me: reduce_allocs + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/C2ME-fabric (Yarn mappings) + +diff --git a/src/main/java/com/ishland/c2me/common/optimization/reduce_allocs/ObjectCachingUtils.java b/src/main/java/com/ishland/c2me/common/optimization/reduce_allocs/ObjectCachingUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b9c3ed63421a48aa4ba341aef7042fc953147bb +--- /dev/null ++++ b/src/main/java/com/ishland/c2me/common/optimization/reduce_allocs/ObjectCachingUtils.java +@@ -0,0 +1,23 @@ ++package com.ishland.c2me.common.optimization.reduce_allocs; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++import java.util.BitSet; ++import java.util.function.IntFunction; ++ ++public class ObjectCachingUtils { ++ ++ private static final IntFunction bitSetConstructor = BitSet::new; ++ ++ public static ThreadLocal> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new); ++ ++ private ObjectCachingUtils() { ++ } ++ ++ public static BitSet getCachedOrNewBitSet(int bits) { ++ final BitSet bitSet = BITSETS.get().computeIfAbsent(bits, bitSetConstructor); ++ bitSet.clear(); ++ return bitSet; ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java +index 6f2b20050641b25ce5c540497b76ce4645a06598..579cd7d59a8eb395efa55163afb82dd393dc245b 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java +@@ -54,7 +54,7 @@ public class OreFeature extends Feature { + + protected boolean doPlace(WorldGenLevel world, Random random, OreConfiguration config, double startX, double endX, double startZ, double endZ, double startY, double endY, int x, int y, int z, int horizontalSize, int verticalSize) { + int i = 0; +- BitSet bitSet = new BitSet(horizontalSize * verticalSize * horizontalSize); ++ BitSet bitSet = com.ishland.c2me.common.optimization.reduce_allocs.ObjectCachingUtils.getCachedOrNewBitSet(horizontalSize * verticalSize * horizontalSize); // JettPack - C2ME: reduce_allocs + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + int j = config.size; + double[] ds = new double[j * 4]; diff --git a/patches/server/0095-PaperPR-Stop-large-look-changes-from-crashing-the-se.patch b/patches/server/0095-PaperPR-Stop-large-look-changes-from-crashing-the-se.patch new file mode 100644 index 0000000..65903b6 --- /dev/null +++ b/patches/server/0095-PaperPR-Stop-large-look-changes-from-crashing-the-se.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MWHunter +Date: Tue, 8 Feb 2022 22:03:15 -0600 +Subject: [PATCH] PaperPR Stop large look changes from crashing the server + +Taken from https://github.com/PaperMC/Paper/pull/7459 + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 0298679a3003729e824ca5223aa3dddb88ae7fe8..e40df9451d3a763a8aead1cc0bc070415027290d 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2974,37 +2974,12 @@ public abstract class LivingEntity extends Entity { + this.level.getProfiler().pop(); + this.level.getProfiler().push("rangeChecks"); + +- while (this.getYRot() - this.yRotO < -180.0F) { +- this.yRotO -= 360.0F; +- } +- +- while (this.getYRot() - this.yRotO >= 180.0F) { +- this.yRotO += 360.0F; +- } +- +- while (this.yBodyRot - this.yBodyRotO < -180.0F) { +- this.yBodyRotO -= 360.0F; +- } +- +- while (this.yBodyRot - this.yBodyRotO >= 180.0F) { +- this.yBodyRotO += 360.0F; +- } +- +- while (this.getXRot() - this.xRotO < -180.0F) { +- this.xRotO -= 360.0F; +- } +- +- while (this.getXRot() - this.xRotO >= 180.0F) { +- this.xRotO += 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO < -180.0F) { +- this.yHeadRotO -= 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO >= 180.0F) { +- this.yHeadRotO += 360.0F; +- } ++ // Paper start - Stop large pitch and yaw changes from crashing the server ++ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; ++ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F; ++ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; ++ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; ++ // Paper end + + this.level.getProfiler().pop(); + this.animStep += f2; diff --git a/patches/server/0096-PaperPR-Add-more-collision-code-skipping-logic.patch b/patches/server/0096-PaperPR-Add-more-collision-code-skipping-logic.patch new file mode 100644 index 0000000..0adadc6 --- /dev/null +++ b/patches/server/0096-PaperPR-Add-more-collision-code-skipping-logic.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sat, 19 Mar 2022 16:27:30 +0100 +Subject: [PATCH] PaperPR Add more collision code skipping logic + +Taken from https://github.com/PaperMC/Paper/pull/7581 + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e40df9451d3a763a8aead1cc0bc070415027290d..fd615b5ed55d108fb685397080802a2660cb7dc4 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3407,6 +3407,10 @@ public abstract class LivingEntity extends Entity { + + protected void pushEntities() { + // Paper start - don't run getEntities if we're not going to use its result ++ if (!isCollidable(level.paperConfig.fixClimbingBypassingCrammingRule)) return; ++ net.minecraft.world.scores.Team team = getTeam(); ++ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) return; ++ + int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); + if (i <= 0 && level.paperConfig.maxCollisionsPerEntity <= 0) { + return; diff --git a/patches/server/0097-vmp-use-linked-map-for-entity-trackers-for-faster-it.patch b/patches/server/0097-vmp-use-linked-map-for-entity-trackers-for-faster-it.patch new file mode 100644 index 0000000..24d8c5c --- /dev/null +++ b/patches/server/0097-vmp-use-linked-map-for-entity-trackers-for-faster-it.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Sat, 12 Mar 2022 16:03:35 +0100 +Subject: [PATCH] vmp: use linked map for entity trackers for faster iteration + +Copyright (c) 2021-2022 ishland + +Original code by RelativityMC, licensed under MIT +You can find the original code on https://github.com/RelativityMC/VMP-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index f69ed1e35d141909a2f1117233a22f289602a0ec..e9238c779bf5b7fd1d2c555941230df4184c4cb1 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -15,6 +15,7 @@ import com.mojang.datafixers.util.Either; + import com.mojang.logging.LogUtils; + import com.mojang.serialization.DataResult; + import com.mojang.serialization.JsonOps; ++import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; // Mirai - vmp: use linked map for entity trackers for faster iteration + import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; +@@ -415,7 +416,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.toDrop = new LongOpenHashSet(); + this.tickingGenerated = new AtomicInteger(); + this.playerMap = new PlayerMap(); +- this.entityMap = new Int2ObjectOpenHashMap(); ++ //this.entityMap = new Int2ObjectOpenHashMap(); ++ this.entityMap = new Int2ObjectLinkedOpenHashMap<>(); // Mirai - vmp: use linked map for entity trackers for faster iteration + this.chunkTypeCache = new Long2ByteOpenHashMap(); + this.chunkSaveCooldowns = new Long2LongOpenHashMap(); + this.structureManager = structureManager; diff --git a/patches/server/0098-lithium-ai.sensor.secondary_poi.patch b/patches/server/0098-lithium-ai.sensor.secondary_poi.patch new file mode 100644 index 0000000..6226f58 --- /dev/null +++ b/patches/server/0098-lithium-ai.sensor.secondary_poi.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2No2Name <2No2Name@web.de> +Date: Tue, 15 Mar 2022 07:14:26 +0100 +Subject: [PATCH] lithium: ai.sensor.secondary_poi + +Original code by CaffeineMC, licensed under GNU Lesser General Public License v3.0 +You can find the original code on https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +index bba08f488acffcce9ae26fe648eb49fe9a2aeec7..37e19ce6b8eccc0463f0b35404b83d1ff8fb4632 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +@@ -22,6 +22,12 @@ public class SecondaryPoiSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, Villager entity) { ++ // Mirai start - lithium: ai.sensor.secondary_poi ++ if (entity.getVillagerData().getProfession().getSecondaryPoi().isEmpty()) { ++ entity.getBrain().eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); ++ return; ++ } ++ // Mirai end + ResourceKey resourceKey = world.dimension(); + BlockPos blockPos = entity.blockPosition(); + List list = Lists.newArrayList(); diff --git a/patches/server/0099-Use-faster-random-implementation.patch b/patches/server/0099-Use-faster-random-implementation.patch new file mode 100644 index 0000000..8369036 --- /dev/null +++ b/patches/server/0099-Use-faster-random-implementation.patch @@ -0,0 +1,372 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sun, 3 Apr 2022 10:38:26 +0200 +Subject: [PATCH] Use faster random implementation + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 0321eac313212c717cf02889589bbc5f73c7d6ca..5b62c64b14efb69410c691fd9db0e943bb15432b 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -31,6 +31,7 @@ dependencies { + implementation("org.apache.logging.log4j:log4j-slf4j18-impl:2.17.1") // Paper + implementation("org.ow2.asm:asm:9.2") + implementation("org.ow2.asm:asm-commons:9.2") // Paper - ASM event executor generation ++ implementation("org.apache.commons:commons-rng-core:1.4") // Mirai - Use faster random implementation + runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3") + runtimeOnly("mysql:mysql-connector-java:8.0.27") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +index 20cfe7b9b7127ddeb97aa91d759fc17b4a548eaf..056f767c546e90cc6302aa0add7c2897d14d80e7 100644 +--- a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +@@ -13,7 +13,7 @@ import java.util.UUID; + + public class PaperLootableInventoryData { + +- private static final Random RANDOM = new Random(); ++ private static final Random RANDOM = new wtf.etil.mirai.server.util.FastRandom(); + + private long lastFill = -1; + private long nextRefill = -1; +diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java +index a5d3aa309d3fdaab9e0fea2dfb91a080a3ac1193..6e33c31aaf470d84707180e25a237832ea3d11fc 100644 +--- a/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java ++++ b/src/main/java/me/jellysquid/mods/lithium/common/cached_blockpos_iteration/IterateOutwardsCache.java +@@ -22,7 +22,7 @@ public class IterateOutwardsCache { + public IterateOutwardsCache(int capacity) { + this.capacity = capacity; + this.table = new ConcurrentHashMap<>(31); +- this.random = new Random(); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + } + + private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) { +diff --git a/src/main/java/net/minecraft/server/commands/SpreadPlayersCommand.java b/src/main/java/net/minecraft/server/commands/SpreadPlayersCommand.java +index edc5b0fde13cecf1da371972bd040d7ca38a690b..d41e64afb0f1895ae8b800a9c2b336ebd08410ff 100644 +--- a/src/main/java/net/minecraft/server/commands/SpreadPlayersCommand.java ++++ b/src/main/java/net/minecraft/server/commands/SpreadPlayersCommand.java +@@ -65,7 +65,7 @@ public class SpreadPlayersCommand { + if (maxY < j) { + throw SpreadPlayersCommand.ERROR_INVALID_MAX_HEIGHT.create(maxY, j); + } else { +- Random random = new Random(); ++ Random random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + double d0 = (double) (center.x - maxRange); + double d1 = (double) (center.y - maxRange); + double d2 = (double) (center.x + maxRange); +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 0866b340a11d4650fc00a40cdb949b6e44691f8c..8eed99c1d58111b391c9c119d5ee60ec54b4d1f2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -48,7 +48,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener + private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); + static final Logger LOGGER = LogUtils.getLogger(); + private static final int MAX_TICKS_BEFORE_LOGIN = 600; +- private static final Random RANDOM = new Random(); ++ private static final Random RANDOM = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + private final byte[] nonce = new byte[4]; + final MinecraftServer server; + public final Connection connection; +diff --git a/src/main/java/net/minecraft/util/Mth.java b/src/main/java/net/minecraft/util/Mth.java +index 23d4efac14b391a5f654013fc549c3274c160ffa..969abe4e3bad924517f5cf72fdb95896eb681994 100644 +--- a/src/main/java/net/minecraft/util/Mth.java ++++ b/src/main/java/net/minecraft/util/Mth.java +@@ -31,7 +31,7 @@ public class Mth { + } + + }); +- private static final Random RANDOM = new Random(); // Mirai - too lazy to edit correctly this line ++ private static final Random RANDOM = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + public static float[] getSinTable() { return SIN; } // Mirai + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + private static final double ONE_SIXTH = 0.16666666666666666D; +@@ -758,7 +758,7 @@ public class Mth { + } + + public static double wobble(double d) { +- return d + (2.0D * (new Random((long)floor(d * 3000.0D))).nextDouble() - 1.0D) * 1.0E-7D / 2.0D; ++ return d + (2.0D * (new wtf.etil.mirai.server.util.FastRandom((long)floor(d * 3000.0D))).nextDouble() - 1.0D) * 1.0E-7D / 2.0D; // Mirai - Use faster random implementation + } + + public static int roundToward(int value, int divisor) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a3465acd25061d2b0833e8cae00403579e647b0b..77bff90134f142fb968c8a285d7c70f8951b3e87 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -181,7 +181,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + // Paper start +- public static Random SHARED_RANDOM = new Random() { ++ public static Random SHARED_RANDOM = new wtf.etil.mirai.server.util.FastRandom() { // Mirai - Use faster random implementation + private boolean locked = false; + @Override + public synchronized void setSeed(long seed) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +index 9783110dec117504a82c985943ffb3eab91cada7..8a676e0ed8b25ad93bf619ea86bca4cca9595325 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +@@ -13,7 +13,7 @@ import java.util.stream.Stream; + + public class ShufflingList { + public final List> entries; // Paper - public +- private final Random random = new Random(); ++ private final Random random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + private final boolean isUnsafe; // Paper + + public ShufflingList() { +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +index 1dbe21a2bb6f7254d13842ead8084b801f881008..5c1dd8802fc161d90b6dbe73287d8669f76cb553 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +@@ -8,7 +8,7 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType; + import net.minecraft.world.entity.ai.targeting.TargetingConditions; + + public abstract class Sensor { +- private static final Random RANDOM = new Random(); ++ private static final Random RANDOM = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + private static final int DEFAULT_SCAN_RATE = 20; + protected static final int TARGETING_RANGE = 16; + private static final TargetingConditions TARGET_CONDITIONS = TargetingConditions.forNonCombat().range(16.0D); +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index 57f593e5f797d646ceb70fa61ec2e966cf4dabc1..62d6bcb3f47afae75d167b1ca6e70e256bcaf25c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -373,7 +373,7 @@ public class PiglinAi { + } + + private static boolean wantsToDance(LivingEntity piglin, LivingEntity target) { +- return target.getType() != EntityType.HOGLIN ? false : (new Random(piglin.level.getGameTime())).nextFloat() < 0.1F; ++ return target.getType() != EntityType.HOGLIN ? false : (new wtf.etil.mirai.server.util.FastRandom(piglin.level.getGameTime())).nextFloat() < 0.1F; // Mirai - Use faster random implementation + } + + protected static boolean wantsToPickup(Piglin piglin, ItemStack stack) { +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index 4e478c30714a8e4cd95f02e73615d166836d1e4b..6c942839a30f50c44acda5e4379f2088878c70a4 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -35,7 +35,7 @@ public class WanderingTraderSpawner implements CustomSpawner { + private static final int SPAWN_CHANCE_INCREASE = 25; + private static final int SPAWN_ONE_IN_X_CHANCE = 10; + private static final int NUMBER_OF_SPAWN_ATTEMPTS = 10; +- private final Random random = new Random(); ++ private final Random random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + private final ServerLevelData serverLevelData; + private int tickDelay; + private int spawnDelay; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 3789a0db398766f0fbc9e5ac5bf4228a0a0dac88..e8a6e619bf9c22792a938f8ebebe635466865ff6 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -79,7 +79,7 @@ public class FishingHook extends Projectile { + + private FishingHook(EntityType type, Level world, int luckOfTheSeaLevel, int lureLevel) { + super(type, world); +- this.syncronizedRandom = new Random(); ++ this.syncronizedRandom = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + this.openWater = true; + this.currentState = FishingHook.FishHookState.FLYING; + this.noCulling = true; +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java +index a078f637ed005dfad5c2c2f601e4ba5964ba7f8d..501378c380fa165bd34f48ad590354f452494217 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raid.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java +@@ -110,7 +110,7 @@ public class Raid { + + public Raid(int id, ServerLevel world, BlockPos pos) { + this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); +- this.random = new Random(); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + this.waveSpawnPos = Optional.empty(); + this.id = id; + this.level = world; +@@ -124,7 +124,7 @@ public class Raid { + + public Raid(ServerLevel world, CompoundTag nbt) { + this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); +- this.random = new Random(); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + this.waveSpawnPos = Optional.empty(); + this.level = world; + this.id = nbt.getInt("Id"); +diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +index ea7b670aa6308bbe9919afced02a9067da20f0ce..bbeba8aecc05c47c668c105149fb67609cfd5aa7 100644 +--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -72,7 +72,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.random = new Random(); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + this.enchantmentSeed = DataSlot.standalone(); + this.costs = new int[3]; + this.enchantClue = new int[]{-1, -1, -1}; +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 4bfebbb2e87464cd47a38a5da6275b2c662fa052..effae052c945ee49d2cbdf4a13b65670706357aa 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -41,7 +41,7 @@ public abstract class BaseSpawner { + public int maxNearbyEntities = 6; + public int requiredPlayerRange = 16; + public int spawnRange = 4; +- private final Random random = new Random(); ++ private final Random random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + private int tickDelay = 0; // Paper + + public BaseSpawner() {} +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 29f6c10e2c2626a9726d295acf12efea2b463cd3..e207a4c2de578274aa7ec8acdbccfbaf6feadae3 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -89,7 +89,7 @@ public class Explosion { + } + + public Explosion(Level world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Explosion.BlockInteraction destructionType) { +- this.random = new Random(); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + this.toBlow = Lists.newArrayList(); + this.hitPlayers = Maps.newHashMap(); + this.level = world; +diff --git a/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +index f74c5bb8e1ba42c77c59d481b871fd992483b128..5378e1062c6e551e0ee8ce5e28fe3e34bda43818 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +@@ -21,7 +21,7 @@ import org.bukkit.entity.HumanEntity; + + public class DispenserBlockEntity extends RandomizableContainerBlockEntity { + +- private static final Random RANDOM = new Random(); ++ private static final Random RANDOM = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + public static final int CONTAINER_SIZE = 9; + private NonNullList items; + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java +index b3eef862248574ee4b864107e1eb69d2418ada8c..105c35338fa14b4efcfcc6d3f7e43448017b91a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java +@@ -23,7 +23,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable + public float rot; + public float oRot; + public float tRot; +- private static final Random RANDOM = new Random(); ++ private static final Random RANDOM = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + private Component name; + + public EnchantmentTableBlockEntity(BlockPos pos, BlockState state) { +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java b/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java +index 3bc8d37590698c17eab876cffdda78bb1bf0e917..32a654e383e861d7f2d57f4806fbd5e15a708294 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java +@@ -127,7 +127,7 @@ public class LootContext { + + public LootContext.Builder withOptionalRandomSeed(long seed) { + if (seed != 0L) { +- this.random = new Random(seed); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(seed); // Mirai - Use faster random implementation + } + + return this; +@@ -137,7 +137,7 @@ public class LootContext { + if (seed == 0L) { + this.random = random; + } else { +- this.random = new Random(seed); ++ this.random = new wtf.etil.mirai.server.util.FastRandom(seed); // Mirai - Use faster random implementation + } + + return this; +@@ -201,7 +201,7 @@ public class LootContext { + } else { + Random random = this.random; + if (random == null) { +- random = new Random(); ++ random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + } + + MinecraftServer minecraftServer = this.level.getServer(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index 93fd9e87de3078f50431b5d80540d4335d7c79e5..8a90b13e53c1da0ede19c5f528f32936b74fd0a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -13,7 +13,7 @@ import org.bukkit.inventory.meta.FireworkMeta; + + public class CraftFirework extends CraftProjectile implements Firework { + +- private final Random random = new Random(); ++ private final Random random = new wtf.etil.mirai.server.util.FastRandom(); // Mirai - Use faster random implementation + //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. + + public CraftFirework(CraftServer server, FireworkRocketEntity entity) { +diff --git a/src/main/java/wtf/etil/mirai/server/util/FastRandom.java b/src/main/java/wtf/etil/mirai/server/util/FastRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..24527b27091930ad76a290f74b800c332d1abfad +--- /dev/null ++++ b/src/main/java/wtf/etil/mirai/server/util/FastRandom.java +@@ -0,0 +1,64 @@ ++package wtf.etil.mirai.server.util; ++ ++import org.apache.commons.rng.core.source64.XoRoShiRo128PlusPlus; ++ ++import java.util.Random; ++import java.util.SplittableRandom; ++ ++public class FastRandom extends Random { ++ ++ private XoRoShiRo128PlusPlus random; ++ ++ public FastRandom() { ++ super(); ++ SplittableRandom randomseed = new SplittableRandom(); ++ this.random = new XoRoShiRo128PlusPlus(randomseed.nextLong(), randomseed.nextLong()); ++ } ++ ++ public FastRandom(long seed) { ++ super(seed); ++ SplittableRandom randomseed = new SplittableRandom(seed); ++ this.random = new XoRoShiRo128PlusPlus(randomseed.nextLong(), randomseed.nextLong()); ++ } ++ ++ @Override ++ public boolean nextBoolean() { ++ return random.nextBoolean(); ++ } ++ ++ @Override ++ public int nextInt() { ++ return random.nextInt(); ++ } ++ ++ @Override ++ public float nextFloat() { ++ return (float) random.nextDouble(); ++ } ++ ++ @Override ++ public double nextDouble() { ++ return random.nextDouble(); ++ } ++ ++ @Override ++ public synchronized void setSeed(long seed) { ++ SplittableRandom randomseed = new SplittableRandom(seed); ++ this.random = new XoRoShiRo128PlusPlus(randomseed.nextLong(), randomseed.nextLong()); ++ } ++ ++ @Override ++ public void nextBytes(byte[] bytes) { ++ random.nextBytes(bytes); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ return random.nextInt(bound); ++ } ++ ++ @Override ++ public long nextLong() { ++ return random.nextLong(); ++ } ++} +\ No newline at end of file diff --git a/patches/server/0100-Fix-tick-function-tag-running-before-load.patch b/patches/server/0100-Fix-tick-function-tag-running-before-load.patch new file mode 100644 index 0000000..2318f47 --- /dev/null +++ b/patches/server/0100-Fix-tick-function-tag-running-before-load.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 8 Apr 2022 21:20:50 +0200 +Subject: [PATCH] Fix tick function tag running before load + + +diff --git a/src/main/java/net/minecraft/server/ServerFunctionManager.java b/src/main/java/net/minecraft/server/ServerFunctionManager.java +index 48205eb72c63fc22042ba4eef8bd1cf85ef61f8a..5dcf62cf4d7f59217048614e066229ac92c5b1d7 100644 +--- a/src/main/java/net/minecraft/server/ServerFunctionManager.java ++++ b/src/main/java/net/minecraft/server/ServerFunctionManager.java +@@ -48,13 +48,14 @@ public class ServerFunctionManager { + } + + public void tick() { +- this.executeTagFunctions(this.ticking, ServerFunctionManager.TICK_FUNCTION_TAG); ++ //this.executeTagFunctions(this.ticking, ServerFunctionManager.TICK_FUNCTION_TAG); // Mirai - moved down + if (this.postReload) { + this.postReload = false; + Collection collection = this.library.getTag(ServerFunctionManager.LOAD_FUNCTION_TAG).getValues(); + + this.executeTagFunctions(collection, ServerFunctionManager.LOAD_FUNCTION_TAG); + } ++ this.executeTagFunctions(this.ticking, ServerFunctionManager.TICK_FUNCTION_TAG); // Mirai - fix tick function tag running before load + + } + diff --git a/patches/server/0101-lithium-suffocation.patch b/patches/server/0101-lithium-suffocation.patch new file mode 100644 index 0000000..418050c --- /dev/null +++ b/patches/server/0101-lithium-suffocation.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 8 Apr 2022 22:21:48 +0200 +Subject: [PATCH] lithium: suffocation + +Author: 2No2Name <2No2Name@web.de> + +Original license: GNU Lesser General Public License v3.0 +Original project: https://github.com/CaffeineMC/lithium-fabric (Yarn mappings) + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 77bff90134f142fb968c8a285d7c70f8951b3e87..d868e8fa1525b82ebf30752d8c96111512096b3d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2514,39 +2514,64 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return !this.isRemoved(); + } + ++ // Mirai start - lithium: suffocation ++ /** ++ * @author 2No2Name ++ * @reason Avoid stream code, use optimized chunk section iteration order ++ */ + public boolean isInWall() { ++ // [VanillaCopy] The whole method functionality including bug below. Cannot use ChunkAwareBlockCollisionSweeper due to ignoring of oversized blocks + if (this.noPhysics) { + return false; +- } else { +- float f = this.dimensions.width * 0.8F; +- AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); +- +- BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos(); +- int minX = Mth.floor(axisalignedbb.minX); +- int minY = Mth.floor(axisalignedbb.minY); +- int minZ = Mth.floor(axisalignedbb.minZ); +- int maxX = Mth.floor(axisalignedbb.maxX); +- int maxY = Mth.floor(axisalignedbb.maxY); +- int maxZ = Mth.floor(axisalignedbb.maxZ); +- for (int fz = minZ; fz <= maxZ; ++fz) { +- for (int fx = minX; fx <= maxX; ++fx) { +- for (int fy = minY; fy <= maxY; ++fy) { +- net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); +- if (chunk == null) { +- continue; ++ } ++ Vec3 position = this.getEyePosition(); ++ double suffocationRadius = Math.abs((double) (this.dimensions.width * 0.8f) / 2.0); ++ ++ double suffocationMinX = position.x - suffocationRadius; ++ double suffocationMinY = position.y - 5.0E-7; ++ double suffocationMinZ = position.z - suffocationRadius; ++ double suffocationMaxX = position.x + suffocationRadius; ++ double suffocationMaxY = position.y + 5.0E-7; ++ double suffocationMaxZ = position.z + suffocationRadius; ++ int minX = Mth.floor(suffocationMinX); ++ int minY = Mth.floor(suffocationMinY); ++ int minZ = Mth.floor(suffocationMinZ); ++ int maxX = Mth.floor(suffocationMaxX); ++ int maxY = Mth.floor(suffocationMaxY); ++ int maxZ = Mth.floor(suffocationMaxZ); ++ ++ Level level = this.level; ++ //skip getting blocks when the entity is outside the world height ++ //also avoids infinite loop with entities below y = Integer.MIN_VALUE (some modded servers do that) ++ if (-64 > maxY || 320 < minY) { ++ return false; ++ } ++ ++ BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos(); ++ VoxelShape suffocationShape = null; ++ ++ for (int y = minY; y <= maxY; y++) { ++ for (int z = minZ; z <= maxZ; z++) { ++ for (int x = minX; x <= maxX; x++) { ++ blockposition.set(x, y, z); ++ BlockState iblockdata = level.getBlockState(blockposition); ++ if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition)) { ++ if (suffocationShape == null) { ++ suffocationShape = Shapes.create(new AABB(suffocationMinX, suffocationMinY, suffocationMinZ, suffocationMaxX, suffocationMaxY, suffocationMaxZ)); + } + +- BlockState iblockdata = chunk.getBlockStateFinal(fx, fy, fz); +- blockposition.set(fx, fy, fz); +- if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND)) { ++ if (Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition). ++ move(blockposition.getX(), blockposition.getY(), blockposition.getZ()), ++ suffocationShape, BooleanOp.AND)) { + return true; + } + } + } + } +- return false; + } ++ return false; + } ++ // Mirai end + + public InteractionResult interact(Player player, InteractionHand hand) { + return InteractionResult.PASS; diff --git a/patches/server/0102-Optimized-default-values-for-configs.patch b/patches/server/0102-Optimized-default-values-for-configs.patch new file mode 100644 index 0000000..fa519f4 --- /dev/null +++ b/patches/server/0102-Optimized-default-values-for-configs.patch @@ -0,0 +1,410 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sun, 8 May 2022 13:13:02 +0200 +Subject: [PATCH] Optimized default values for configs + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7333e60f404f6f85a2bcb1f8f031a180314abd32..fb631ae7580806385399944aaca54229db84484f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -251,7 +251,7 @@ public class PaperConfig { + timings = reallyEnableTimings; + // Pufferfish end + boolean verboseTimings = getBoolean("timings.verbose", true); +- TimingsManager.url = getString("timings.url", "https://timings.aikar.co/"); ++ TimingsManager.url = getString("timings.url", "https://timin.gs/"); // Mirai - optimized default values for configs + if (!TimingsManager.url.endsWith("/")) { + TimingsManager.url += "/"; + } +@@ -283,9 +283,9 @@ public class PaperConfig { + loadPermsBeforePlugins = getBoolean("settings.load-permissions-yml-before-plugins", true); + } + +- public static int regionFileCacheSize = 256; ++ public static int regionFileCacheSize = 512; // Mirai - optimized default values for configs + private static void regionFileCacheSize() { +- regionFileCacheSize = Math.max(getInt("settings.region-file-cache-size", 256), 4); ++ regionFileCacheSize = Math.max(getInt("settings.region-file-cache-size", 512), 4); // Mirai - optimized default values for configs + } + + public static boolean enablePlayerCollisions = true; +@@ -362,9 +362,9 @@ public class PaperConfig { + } + } + +- public static boolean useAlternativeLuckFormula = false; ++ public static boolean useAlternativeLuckFormula = true; // Mirai - optimized default values for configs + private static void useAlternativeLuckFormula() { +- useAlternativeLuckFormula = getBoolean("settings.use-alternative-luck-formula", false); ++ useAlternativeLuckFormula = getBoolean("settings.use-alternative-luck-formula", true); // Mirai - optimized default values for configs + if (useAlternativeLuckFormula) { + Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs"); + } +@@ -413,7 +413,7 @@ public class PaperConfig { + } + } + +- public static int maxBookPageSize = 2560; ++ public static int maxBookPageSize = 512; // Mirai - optimized default values for configs + public static double maxBookTotalSizeMultiplier = 0.98D; + private static void maxBookSize() { + maxBookPageSize = Math.min(8192, getInt("settings.book-size.page-max", maxBookPageSize)); +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index fba36a0fd7036ba516e14c8d3521b470b0df17d1..39e49aca16128b2ffe1cbde0ecbb47fa66fbdce8 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -153,7 +153,7 @@ public class PaperWorldConfig { + wanderingTraderSpawnChanceMax = getInt("wandering-trader.spawn-chance-max", wanderingTraderSpawnChanceMax); + } + +- public boolean fixClimbingBypassingCrammingRule = false; ++ public boolean fixClimbingBypassingCrammingRule = true; // Mirai - optimized default values for configs + private void fixClimbingBypassingCrammingRule() { + fixClimbingBypassingCrammingRule = getBoolean("fix-climbing-bypassing-cramming-rule", fixClimbingBypassingCrammingRule); + } +@@ -191,12 +191,26 @@ public class PaperWorldConfig { + + public Map, Integer> entityPerChunkSaveLimits = new HashMap<>(); + private void entityPerChunkSaveLimits() { +- getInt("entity-per-chunk-save-limit.experience_orb", -1); +- getInt("entity-per-chunk-save-limit.snowball", -1); +- getInt("entity-per-chunk-save-limit.ender_pearl", -1); +- getInt("entity-per-chunk-save-limit.arrow", -1); +- getInt("entity-per-chunk-save-limit.fireball", -1); +- getInt("entity-per-chunk-save-limit.small_fireball", -1); ++ // Mirai start - optimized default values for configs ++ getInt("entity-per-chunk-save-limit.experience_orb", 16); ++ getInt("entity-per-chunk-save-limit.snowball", 8); ++ getInt("entity-per-chunk-save-limit.ender_pearl", 8); ++ getInt("entity-per-chunk-save-limit.arrow", 16); ++ getInt("entity-per-chunk-save-limit.fireball", 8); ++ getInt("entity-per-chunk-save-limit.small_fireball", 8); ++ getInt("entity-per-chunk-save-limit.dragon_fireball", 3); ++ getInt("entity-per-chunk-save-limit.egg", 8); ++ getInt("entity-per-chunk-save-limit.eye_of_ender", 8); ++ getInt("entity-per-chunk-save-limit.firework_rocket", 8); ++ getInt("entity-per-chunk-save-limit.potion", 8); ++ getInt("entity-per-chunk-save-limit.llama_spit", 3); ++ getInt("entity-per-chunk-save-limit.shulker_bullet", 8); ++ getInt("entity-per-chunk-save-limit.spectral_arrow", 16); ++ getInt("entity-per-chunk-save-limit.experience_bottle", 3); ++ getInt("entity-per-chunk-save-limit.trident", 16); ++ getInt("entity-per-chunk-save-limit.wither_skull", 4); ++ getInt("entity-per-chunk-save-limit.area_effect_cloud", 8); ++ // Mirai end + + addEntityPerChunkSaveLimitsFromSection(config.getConfigurationSection("world-settings.default.entity-per-chunk-save-limit"), entityPerChunkSaveLimits); + addEntityPerChunkSaveLimitsFromSection(config.getConfigurationSection("world-settings." + worldName + ".entity-per-chunk-save-limit"), entityPerChunkSaveLimits); +@@ -237,9 +251,9 @@ public class PaperWorldConfig { + } + } + +- public int maxAutoSaveChunksPerTick = 24; ++ public int maxAutoSaveChunksPerTick = 12; // Mirai - optimized default values for configs + private void maxAutoSaveChunksPerTick() { +- maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); ++ maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 12); // Mirai - optimized default values for configs + } + + private boolean getBoolean(String path, boolean def) { +@@ -321,7 +335,7 @@ public class PaperWorldConfig { + + public boolean nerfedMobsShouldJump; + private void nerfedMobsShouldJump() { +- nerfedMobsShouldJump = getBoolean("spawner-nerfed-mobs-should-jump", false); ++ nerfedMobsShouldJump = getBoolean("spawner-nerfed-mobs-should-jump", true); // Mirai - optimized default values for configs + } + + public final Reference2IntMap softDespawnDistances = new Reference2IntOpenHashMap<>(MobCategory.values().length); +@@ -396,7 +410,7 @@ public class PaperWorldConfig { + + public boolean optimizeExplosions; + private void optimizeExplosions() { +- optimizeExplosions = getBoolean("optimize-explosions", false); ++ optimizeExplosions = getBoolean("optimize-explosions", true); // Mirai - optimized default values for configs + log("Optimize explosions: " + optimizeExplosions); + } + +@@ -417,7 +431,7 @@ public class PaperWorldConfig { + + public int mobSpawnerTickRate; + private void mobSpawnerTickRate() { +- mobSpawnerTickRate = getInt("mob-spawner-tick-rate", 1); ++ mobSpawnerTickRate = getInt("mob-spawner-tick-rate", 2); // Mirai - optimized default values for configs + } + + public Integer waterAnimalMaxSpawnHeight; +@@ -440,12 +454,12 @@ public class PaperWorldConfig { + + public int containerUpdateTickRate; + private void containerUpdateTickRate() { +- containerUpdateTickRate = getInt("container-update-tick-rate", 1); ++ containerUpdateTickRate = getInt("container-update-tick-rate", 3); // Mirai - optimized default values for configs + } + + public boolean disableChestCatDetection; + private void disableChestCatDetection() { +- disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", false); ++ disableChestCatDetection = getBoolean("game-mechanics.disable-chest-cat-detection", true); // Mirai - optimized default values for configs + } + + public boolean disablePlayerCrits; +@@ -487,14 +501,14 @@ public class PaperWorldConfig { + nonPlayerEntitiesOnScoreboards = getBoolean("allow-non-player-entities-on-scoreboards", false); + } + +- public int nonPlayerArrowDespawnRate = -1; +- public int creativeArrowDespawnRate = -1; ++ public int nonPlayerArrowDespawnRate = 20; // Mirai - optimized default values for configs ++ public int creativeArrowDespawnRate = 20; // Mirai - optimized default values for configs + private void nonPlayerArrowDespawnRate() { +- nonPlayerArrowDespawnRate = getInt("non-player-arrow-despawn-rate", -1); ++ nonPlayerArrowDespawnRate = getInt("non-player-arrow-despawn-rate", 20); // Mirai - optimized default values for configs + if (nonPlayerArrowDespawnRate == -1) { + nonPlayerArrowDespawnRate = spigotConfig.arrowDespawnRate; + } +- creativeArrowDespawnRate = getInt("creative-arrow-despawn-rate", -1); ++ creativeArrowDespawnRate = getInt("creative-arrow-despawn-rate", 20); // Mirai - optimized default values for configs + if (creativeArrowDespawnRate == -1) { + creativeArrowDespawnRate = spigotConfig.arrowDespawnRate; + } +@@ -520,7 +534,7 @@ public class PaperWorldConfig { + fixedInhabitedTime = getInt("fixed-chunk-inhabited-time", -1); + } + +- public int grassUpdateRate = 1; ++ public int grassUpdateRate = 4; // Mirai - optimized default values for configs + private void grassUpdateRate() { + grassUpdateRate = Math.max(0, getInt("grass-spread-tick-rate", grassUpdateRate)); + log("Grass Spread Tick Rate: " + grassUpdateRate); +@@ -591,7 +605,7 @@ public class PaperWorldConfig { + } + } + +- public int maxCollisionsPerEntity = 8; ++ public int maxCollisionsPerEntity = 2; // Mirai - optimized default values for configs + private void maxEntityCollision() { + maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", this.maxCollisionsPerEntity, false) ); + log( "Max Entity Collisions: " + maxCollisionsPerEntity ); +@@ -630,17 +644,17 @@ public class PaperWorldConfig { + scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true); + } + +- public boolean ironGolemsCanSpawnInAir = false; ++ public boolean ironGolemsCanSpawnInAir = true; // Mirai - optimized default values for configs + private void ironGolemsCanSpawnInAir() { + ironGolemsCanSpawnInAir = getBoolean("iron-golems-can-spawn-in-air", ironGolemsCanSpawnInAir); + } + +- public boolean armorStandEntityLookups = true; ++ public boolean armorStandEntityLookups = false; // Mirai - optimized default values for configs + private void armorStandEntityLookups() { +- armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", true); ++ armorStandEntityLookups = getBoolean("armor-stands-do-collision-entity-lookups", false); // Mirai - optimized default values for configs + } + +- public boolean armorStandTick = true; ++ public boolean armorStandTick = false; // Mirai - optimized default values for configs + private void armorStandTick() { + this.armorStandTick = this.getBoolean("armor-stands-tick", this.armorStandTick); + log("ArmorStand ticking is " + (this.armorStandTick ? "enabled" : "disabled") + " by default"); +@@ -652,9 +666,9 @@ public class PaperWorldConfig { + log("Water over lava flow speed: " + waterOverLavaFlowSpeed); + } + +- public boolean preventMovingIntoUnloadedChunks = false; ++ public boolean preventMovingIntoUnloadedChunks = true; // Mirai - optimized default values for configs + private void preventMovingIntoUnloadedChunks() { +- preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false); ++ preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", true); // Mirai - optimized default values for configs + } + + public enum DuplicateUUIDMode { +@@ -737,8 +751,8 @@ public class PaperWorldConfig { + } + + public boolean cooldownHopperWhenFull = true; +- public boolean disableHopperMoveEvents = false; +- public boolean hoppersIgnoreOccludingBlocks = false; ++ public boolean disableHopperMoveEvents = true; // Mirai - optimized default values for configs ++ public boolean hoppersIgnoreOccludingBlocks = true; // Mirai - optimized default values for configs + private void hopperOptimizations() { + cooldownHopperWhenFull = getBoolean("hopper.cooldown-when-full", cooldownHopperWhenFull); + log("Cooldown Hoppers when Full: " + (cooldownHopperWhenFull ? "enabled" : "disabled")); +@@ -911,7 +925,7 @@ public class PaperWorldConfig { + enderDragonsDeathAlwaysPlacesDragonEgg = getBoolean("ender-dragons-death-always-places-dragon-egg", enderDragonsDeathAlwaysPlacesDragonEgg); + } + +- public boolean updatePathfindingOnBlockUpdate = true; ++ public boolean updatePathfindingOnBlockUpdate = false; // Mirai - optimized default values for configs + private void setUpdatePathfindingOnBlockUpdate() { + updatePathfindingOnBlockUpdate = getBoolean("update-pathfinding-on-block-update", this.updatePathfindingOnBlockUpdate); + } +@@ -939,7 +953,7 @@ public class PaperWorldConfig { + + public long delayChunkUnloadsBy; + private void delayChunkUnloadsBy() { +- delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "10s")); ++ delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "5s")); // Mirai - optimized default values for configs + if (delayChunkUnloadsBy > 0) { + log("Delaying chunk unloads by " + delayChunkUnloadsBy + " seconds"); + delayChunkUnloadsBy *= 20; +@@ -989,8 +1003,15 @@ public class PaperWorldConfig { + private com.google.common.collect.Table sensorTickRates; + private com.google.common.collect.Table behaviorTickRates; + private void tickRates() { +- config.addDefault("world-settings.default.tick-rates.sensor.villager.secondarypoisensor", 40); +- config.addDefault("world-settings.default.tick-rates.behavior.villager.validatenearbypoi", -1); // Example ++ // Mirai start - optimized default values for configs ++ config.addDefault("world-settings.default.tick-rates.sensor.villager.secondarypoisensor", 80); ++ config.addDefault("world-settings.default.tick-rates.sensor.villager.nearestbedsensor", 80); ++ config.addDefault("world-settings.default.tick-rates.sensor.villager.villagerbabiessensor", 40); ++ config.addDefault("world-settings.default.tick-rates.sensor.villager.playersensor", 40); ++ config.addDefault("world-settings.default.tick-rates.sensor.villager.nearestlivingentitysensor", 40); ++ config.addDefault("world-settings.default.tick-rates.behavior.villager.validatenearbypoi", 60); ++ config.addDefault("world-settings.default.tick-rates.behavior.villager.acquirepoi", 120); ++ // Mirai end + log("Tick rates:"); + sensorTickRates = loadTickRates("sensor"); + behaviorTickRates = loadTickRates("behavior"); +diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java +index a125abc75ed2bbd905c8fdec442416a45264f531..e616e3ce602c876baa3f531f12506a08523ab921 100644 +--- a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java ++++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java +@@ -216,8 +216,8 @@ public class PufferfishConfig { + public static int maxProjectileLoadsPerTick; + public static int maxProjectileLoadsPerProjectile; + private static void projectileLoading() { +- maxProjectileLoadsPerTick = getInt("projectile.max-loads-per-tick", 10, "Controls how many chunks are allowed", "to be sync loaded by projectiles in a tick."); +- maxProjectileLoadsPerProjectile = getInt("projectile.max-loads-per-projectile", 10, "Controls how many chunks a projectile", "can load in its lifetime before it gets", "automatically removed."); ++ maxProjectileLoadsPerTick = getInt("projectile.max-loads-per-tick", 8, "Controls how many chunks are allowed", "to be sync loaded by projectiles in a tick."); // Mirai - optimized default values for configs ++ maxProjectileLoadsPerProjectile = getInt("projectile.max-loads-per-projectile", 8, "Controls how many chunks a projectile", "can load in its lifetime before it gets", "automatically removed."); // Mirai - optimized default values for configs + + setComment("projectile", "Optimizes projectile settings"); + } +@@ -235,10 +235,10 @@ public class PufferfishConfig { + "This value determines how far away an entity has to be", + "from the player to start being effected by DEAR."); + startDistanceSquared = startDistance * startDistance; +- maximumActivationPrio = getInt("dab.max-tick-freq", "activation-range.max-tick-freq", 20, ++ maximumActivationPrio = getInt("dab.max-tick-freq", "activation-range.max-tick-freq", 30, // Mirai - optimized default values for configs + "This value defines how often in ticks, the furthest entity", + "will get their pathfinders and behaviors ticked. 20 = 1s"); +- activationDistanceMod = getInt("dab.activation-dist-mod", "activation-range.activation-dist-mod", 8, ++ activationDistanceMod = getInt("dab.activation-dist-mod", "activation-range.activation-dist-mod", 7, // Mirai - optimized default values for configs + "This value defines how much distance modifies an entity's", + "tick frequency. freq = (distanceToPlayer^2) / (2^value)", + "If you want further away entities to tick less often, use 7.", +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index d509187ca63963fdd7f1a44d89d2aa1a1b1ce3bd..efe021c6a6bb63d0181c0bf2a256dab90b88726f 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -335,7 +335,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 ); // Mirai - optimized default values for configs + } + + public static double movedWronglyThreshold; +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index c405457a775df902540602dfb4cdcd6872d35e2a..8681f9af84a5813d45efaf2524f02155d13cf187 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -150,14 +150,14 @@ public class SpigotWorldConfig + public double itemMerge; + private void itemMerge() + { +- this.itemMerge = this.getDouble("merge-radius.item", 2.5 ); ++ this.itemMerge = this.getDouble("merge-radius.item", 3.5 ); // Mirai - optimized default values for configs + this.log( "Item Merge Radius: " + this.itemMerge ); + } + + public double expMerge; + private void expMerge() + { +- this.expMerge = this.getDouble("merge-radius.exp", 3.0 ); ++ this.expMerge = this.getDouble("merge-radius.exp", 4.0 ); // Mirai - optimized default values for configs + this.log( "Experience Merge Radius: " + this.expMerge ); + } + +@@ -207,31 +207,31 @@ public class SpigotWorldConfig + this.log( "Item Despawn Rate: " + this.itemDespawnRate ); + } + +- public int animalActivationRange = 32; +- public int monsterActivationRange = 32; ++ public int animalActivationRange = 16; // Mirai - optimized default values for configs ++ public int monsterActivationRange = 24; // Mirai - optimized default values for configs + public int raiderActivationRange = 48; +- public int miscActivationRange = 16; ++ public int miscActivationRange = 8; // Mirai - optimized default values for configs + // Paper start +- public int flyingMonsterActivationRange = 32; +- public int waterActivationRange = 16; +- public int villagerActivationRange = 32; +- public int wakeUpInactiveAnimals = 4; ++ public int flyingMonsterActivationRange = 48; // Mirai - optimized default values for configs ++ public int waterActivationRange = 8; // Mirai - optimized default values for configs ++ public int villagerActivationRange = 16; // Mirai - optimized default values for configs ++ public int wakeUpInactiveAnimals = 2; // Mirai - optimized default values for configs + public int wakeUpInactiveAnimalsEvery = 60*20; +- public int wakeUpInactiveAnimalsFor = 5*20; +- public int wakeUpInactiveMonsters = 8; ++ public int wakeUpInactiveAnimalsFor = 2*20; // Mirai - optimized default values for configs ++ public int wakeUpInactiveMonsters = 4; // Mirai - optimized default values for configs + public int wakeUpInactiveMonstersEvery = 20*20; +- public int wakeUpInactiveMonstersFor = 5*20; +- public int wakeUpInactiveVillagers = 4; ++ public int wakeUpInactiveMonstersFor = 3*20; // Mirai - optimized default values for configs ++ public int wakeUpInactiveVillagers = 1; // Mirai - optimized default values for configs + public int wakeUpInactiveVillagersEvery = 30*20; +- public int wakeUpInactiveVillagersFor = 5*20; +- public int wakeUpInactiveFlying = 8; ++ public int wakeUpInactiveVillagersFor = 20; // Mirai - optimized default values for configs ++ public int wakeUpInactiveFlying = 1; // Mirai - optimized default values for configs + public int wakeUpInactiveFlyingEvery = 10*20; +- public int wakeUpInactiveFlyingFor = 5*20; ++ public int wakeUpInactiveFlyingFor = 3*20; // Mirai - optimized default values for configs + public int villagersWorkImmunityAfter = 5*20; + public int villagersWorkImmunityFor = 20; + public boolean villagersActiveForPanic = true; + // Paper end +- public boolean tickInactiveVillagers = true; ++ public boolean tickInactiveVillagers = false; // Mirai - optimized default values for configs + public boolean ignoreSpectatorActivation = false; + private void activationRange() + { +@@ -305,7 +305,7 @@ public class SpigotWorldConfig + public int tridentDespawnRate; + private void arrowDespawnRate() + { +- this.arrowDespawnRate = this.getInt( "arrow-despawn-rate", 1200 ); ++ this.arrowDespawnRate = this.getInt( "arrow-despawn-rate", 300 ); // Mirai - optimized 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 ); + } +diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml +index 97239f7aadd761f6f709a6d6c913e577651a87df..3ed0852d0d891c3f0a9270e0c14473b3dab893f2 100644 +--- a/src/main/resources/configurations/bukkit.yml ++++ b/src/main/resources/configurations/bukkit.yml +@@ -34,11 +34,11 @@ chunk-gc: + period-in-ticks: 600 + 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/patches/server/0103-Set-multiple-Team-settings-with-a-single-packet.patch b/patches/server/0103-Set-multiple-Team-settings-with-a-single-packet.patch new file mode 100644 index 0000000..13fb108 --- /dev/null +++ b/patches/server/0103-Set-multiple-Team-settings-with-a-single-packet.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Thu, 7 Apr 2022 16:35:36 -0500 +Subject: [PATCH] Set multiple Team settings with a single packet + +Original license: MIT +Original project: https://github.com/Cryptite/Slice + +diff --git a/src/main/java/net/minecraft/world/scores/PlayerTeam.java b/src/main/java/net/minecraft/world/scores/PlayerTeam.java +index a0d7dcb20f161279d7fb88834df89be0187dcf7e..ad6c8b2bbd1cf2ae16120cb0b02749eaf3f2e032 100644 +--- a/src/main/java/net/minecraft/world/scores/PlayerTeam.java ++++ b/src/main/java/net/minecraft/world/scores/PlayerTeam.java +@@ -28,6 +28,7 @@ public class PlayerTeam extends Team { + private ChatFormatting color = ChatFormatting.RESET; + private Team.CollisionRule collisionRule = Team.CollisionRule.ALWAYS; + private final Style displayNameStyle; ++ private boolean updateTeamChangeInstantly = true; // Slice + + public PlayerTeam(Scoreboard scoreboard, String name) { + this.scoreboard = scoreboard; +@@ -36,6 +37,12 @@ public class PlayerTeam extends Team { + this.displayNameStyle = Style.EMPTY.withInsertion(name).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponent(name))); + } + ++ // Slice start ++ public void setUpdateTeamChangeInstantly(boolean updateTeamChangeInstantly) { ++ this.updateTeamChangeInstantly = updateTeamChangeInstantly; ++ } ++ // Slice end ++ + public Scoreboard getScoreboard() { + return this.scoreboard; + } +@@ -64,13 +71,13 @@ public class PlayerTeam extends Team { + throw new IllegalArgumentException("Name cannot be null"); + } else { + this.displayName = displayName; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + } + + public void setPlayerPrefix(@Nullable Component prefix) { + this.playerPrefix = prefix == null ? TextComponent.EMPTY : prefix; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + public Component getPlayerPrefix() { +@@ -79,7 +86,7 @@ public class PlayerTeam extends Team { + + public void setPlayerSuffix(@Nullable Component suffix) { + this.playerSuffix = suffix == null ? TextComponent.EMPTY : suffix; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + public Component getPlayerSuffix() { +@@ -113,7 +120,7 @@ public class PlayerTeam extends Team { + + public void setAllowFriendlyFire(boolean friendlyFire) { + this.allowFriendlyFire = friendlyFire; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + @Override +@@ -123,7 +130,7 @@ public class PlayerTeam extends Team { + + public void setSeeFriendlyInvisibles(boolean showFriendlyInvisible) { + this.seeFriendlyInvisibles = showFriendlyInvisible; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + @Override +@@ -138,12 +145,12 @@ public class PlayerTeam extends Team { + + public void setNameTagVisibility(Team.Visibility nameTagVisibilityRule) { + this.nameTagVisibility = nameTagVisibilityRule; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + public void setDeathMessageVisibility(Team.Visibility deathMessageVisibilityRule) { + this.deathMessageVisibility = deathMessageVisibilityRule; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + @Override +@@ -153,7 +160,7 @@ public class PlayerTeam extends Team { + + public void setCollisionRule(Team.CollisionRule collisionRule) { + this.collisionRule = collisionRule; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + public int packOptions() { +@@ -176,7 +183,7 @@ public class PlayerTeam extends Team { + + public void setColor(ChatFormatting color) { + this.color = color; +- this.scoreboard.onTeamChanged(this); ++ if (updateTeamChangeInstantly) this.scoreboard.onTeamChanged(this); // Slice + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index 67efb0d38ae369ff5254f7b1ec85d32d4eee8291..6e221425aa755132354a1d96c63d8b60a2bc291c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -347,6 +347,37 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + } + } + ++ // Slice start ++ @Override ++ public void teamOptions(net.kyori.adventure.text.Component displayName, ++ net.kyori.adventure.text.Component prefix, ++ net.kyori.adventure.text.Component suffix, ++ net.kyori.adventure.text.format.NamedTextColor color, ++ java.util.List> options) throws IllegalStateException { ++ checkState(); ++ team.setUpdateTeamChangeInstantly(false); ++ ++ if (displayName == null) displayName = net.kyori.adventure.text.Component.empty(); ++ team.setDisplayName(io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); ++ ++ if (prefix == null) prefix = net.kyori.adventure.text.Component.empty(); ++ team.setPlayerPrefix(io.papermc.paper.adventure.PaperAdventure.asVanilla(prefix)); ++ ++ if (suffix == null) suffix = net.kyori.adventure.text.Component.empty(); ++ team.setPlayerSuffix(io.papermc.paper.adventure.PaperAdventure.asVanilla(suffix)); ++ ++ if (color == null) color = net.kyori.adventure.text.format.NamedTextColor.WHITE; ++ team.setColor(io.papermc.paper.adventure.PaperAdventure.asVanilla(color)); ++ ++ for (org.apache.commons.lang3.tuple.Pair pair : options) { ++ setOption(pair.getLeft(), pair.getRight()); ++ } ++ ++ team.setUpdateTeamChangeInstantly(true); ++ team.getScoreboard().onTeamChanged(team); ++ } ++ // Slice end ++ + // Paper start + @Override + public void addEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { diff --git a/patches/server/0104-c2me-aquifer.patch b/patches/server/0104-c2me-aquifer.patch new file mode 100644 index 0000000..aa8a7b6 --- /dev/null +++ b/patches/server/0104-c2me-aquifer.patch @@ -0,0 +1,668 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Tue, 19 Apr 2022 23:20:08 +0200 +Subject: [PATCH] c2me: aquifer + +Author: ishland + +Original license: MIT +Original project: https://github.com/RelativityMC/C2ME-fabric (Yarn mappings) + +Copyright (c) 2021-2022 ishland + +diff --git a/src/main/java/com/ishland/c2me/opts/worldgen/general/common/random_instances/RandomUtils.java b/src/main/java/com/ishland/c2me/opts/worldgen/general/common/random_instances/RandomUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e01c7bec91cd415979c0f748e0acf1bcc99327e +--- /dev/null ++++ b/src/main/java/com/ishland/c2me/opts/worldgen/general/common/random_instances/RandomUtils.java +@@ -0,0 +1,51 @@ ++package com.ishland.c2me.opts.worldgen.general.common.random_instances; ++ ++import net.minecraft.util.Mth; ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++import net.minecraft.world.level.levelgen.PositionalRandomFactory; ++import net.minecraft.world.level.levelgen.RandomSource; ++import net.minecraft.world.level.levelgen.SingleThreadedRandomSource; ++import net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus; ++import net.minecraft.world.level.levelgen.XoroshiroRandomSource; ++ ++public class RandomUtils { ++ ++ 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 XoroshiroRandomSource.XoroshiroPositionalRandomFactory deriver1) { ++ final Xoroshiro128PlusPlus implementation = ((XoroshiroRandomSource) random).getRandomNumberGenerator(); ++ implementation.setSeedLo(Mth.getSeed(x, y, z) ^ deriver1.getSeedLo()); ++ implementation.setSeedHi(deriver1.getSeedHi()); ++ return; ++ } ++ if (deriver instanceof LegacyRandomSource.LegacyPositionalRandomFactory deriver1) { ++ final SingleThreadedRandomSource random1 = (SingleThreadedRandomSource) random; ++ random1.setSeed(Mth.getSeed(x, y, z) ^ deriver1.getSeed()); ++ return; ++ } ++ throw new IllegalArgumentException(); ++ } ++ ++ public static RandomSource getThreadLocalRandom(PositionalRandomFactory deriver) { ++ if (deriver instanceof XoroshiroRandomSource.XoroshiroPositionalRandomFactory) { ++ return xoroshiro.get(); ++ } ++ if (deriver instanceof LegacyRandomSource.LegacyPositionalRandomFactory) { ++ return simple.get(); ++ } ++ throw new IllegalArgumentException(); ++ } ++ ++ public static 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(); ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 63f5acd8b5aaf936b08d389c8f871bea8c5373da..51eb7043b469ac092197e83fb2eb5d32f4c3e36a 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -37,14 +37,14 @@ public class BlockPos extends Vec3i { + private static final Logger LOGGER = LogUtils.getLogger(); + public static final BlockPos ZERO = new BlockPos(0, 0, 0); + // Paper start - static constants +- private static final int PACKED_X_LENGTH = 26; +- private static final int PACKED_Z_LENGTH = 26; ++ public static final int PACKED_X_LENGTH = 26; // Mirai - private->public ++ public static final int PACKED_Z_LENGTH = 26; // Mirai - private->public + public static final int PACKED_Y_LENGTH = 12; + private static final long PACKED_X_MASK = 67108863; + private static final long PACKED_Y_MASK = 4095; + private static final long PACKED_Z_MASK = 67108863; +- private static final int Z_OFFSET = 12; +- private static final int X_OFFSET = 38; ++ public static final int Z_OFFSET = 12; // Mirai - private->public ++ public static final int X_OFFSET = 38; // Mirai - private->public + // Paper end + + public BlockPos(int x, int y, int z) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/Aquifer.java b/src/main/java/net/minecraft/world/level/levelgen/Aquifer.java +index ed31704a1e6b4acc245c1dd570ba9905e5d25c57..e01d1fb41504e59758b4080e7dd3f54fa0e0fc4d 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/Aquifer.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/Aquifer.java +@@ -11,6 +11,8 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.dimension.DimensionType; + import org.apache.commons.lang3.mutable.MutableDouble; + ++import static net.minecraft.core.BlockPos.PACKED_X_LENGTH; // Mirai - c2me: aquifer ++ + public interface Aquifer { + static Aquifer create(NoiseChunk chunkNoiseSampler, ChunkPos chunkPos, DensityFunction densityFunction, DensityFunction densityFunction2, DensityFunction densityFunction3, DensityFunction densityFunction4, PositionalRandomFactory randomDeriver, int minY, int height, Aquifer.FluidPicker fluidLevelSampler) { + return new Aquifer.NoiseBasedAquifer(chunkNoiseSampler, chunkPos, densityFunction, densityFunction2, densityFunction3, densityFunction4, randomDeriver, minY, height, fluidLevelSampler); +@@ -55,6 +57,17 @@ public interface Aquifer { + } + + public static class NoiseBasedAquifer implements Aquifer { ++ ++ // Mirai start - c2me: aquifer ++ private static final int WATER_LEVEL_MAGIC_1 = 64 - BlockPos.X_OFFSET - PACKED_X_LENGTH; ++ private static final int WATER_LEVEL_MAGIC_2 = 64 - PACKED_X_LENGTH; ++ private static final int WATER_LEVEL_MAGIC_3 = 64 - BlockPos.PACKED_Y_LENGTH; ++ private static final int WATER_LEVEL_MAGIC_4 = 64 - BlockPos.PACKED_Y_LENGTH; ++ private static final int WATER_LEVEL_MAGIC_5 = 64 - BlockPos.Z_OFFSET - BlockPos.PACKED_Z_LENGTH; ++ private static final int WATER_LEVEL_MAGIC_6 = 64 - BlockPos.PACKED_Z_LENGTH; ++ private RandomSource randomInstance; ++ // Mirai end ++ + private static final int X_RANGE = 10; + private static final int Y_RANGE = 9; + private static final int Z_RANGE = 10; +@@ -104,6 +117,7 @@ public interface Aquifer { + this.aquiferCache = new Aquifer.FluidStatus[m]; + this.aquiferLocationCache = new long[m]; + Arrays.fill(this.aquiferLocationCache, Long.MAX_VALUE); ++ this.randomInstance = com.ishland.c2me.opts.worldgen.general.common.random_instances.RandomUtils.getRandom(this.positionalRandomFactory); // Mirai - c2me: aquifer + } + + private int getIndex(int x, int y, int z) { +@@ -113,24 +127,29 @@ public interface Aquifer { + return (j * this.gridSizeZ + k) * this.gridSizeX + i; + } + ++ // Mirai start - c2me: aquifer ++ /** ++ * @author ishland ++ * @reason optimize ++ */ + @Nullable + @Override + public BlockState computeSubstance(DensityFunction.FunctionContext functionContext, double d) { +- int i = functionContext.blockX(); +- int j = functionContext.blockY(); +- int k = functionContext.blockZ(); +- if (d > 0.0D) { ++ final int blockX = functionContext.blockX(); ++ final int blockY = functionContext.blockY(); ++ final int blockZ = functionContext.blockZ(); ++ if (d > 0.0) { + this.shouldScheduleFluidUpdate = false; + return null; + } else { +- Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, j, k); +- if (fluidStatus.at(j).is(Blocks.LAVA)) { ++ Aquifer.FluidStatus fluidLevel = this.globalFluidPicker.computeFluid(blockX, blockY, blockZ); ++ if (fluidLevel.at(blockY).is(Blocks.LAVA)) { + this.shouldScheduleFluidUpdate = false; + return Blocks.LAVA.defaultBlockState(); + } else { +- int l = Math.floorDiv(i - 5, 16); +- int m = Math.floorDiv(j + 1, 12); +- int n = Math.floorDiv(k - 5, 16); ++ int l = Math.floorDiv(blockX - 5, 16); ++ int m = Math.floorDiv(blockY + 1, 12); ++ int n = Math.floorDiv(blockZ - 5, 16); + int o = Integer.MAX_VALUE; + int p = Integer.MAX_VALUE; + int q = Integer.MAX_VALUE; +@@ -138,90 +157,212 @@ public interface Aquifer { + long s = 0L; + long t = 0L; + +- for(int u = 0; u <= 1; ++u) { +- for(int v = -1; v <= 1; ++v) { +- for(int w = 0; w <= 1; ++w) { ++ for (int u = 0; u <= 1; ++u) { ++ for (int v = -1; v <= 1; ++v) { ++ for (int w = 0; w <= 1; ++w) { + int x = l + u; + int y = m + v; + int z = n + w; +- int aa = this.getIndex(x, y, z); ++ int aa = ((y - this.minGridY) * this.gridSizeZ + z - this.minGridZ) * this.gridSizeX + x - this.minGridX; + long ab = this.aquiferLocationCache[aa]; + long ac; + if (ab != Long.MAX_VALUE) { + ac = ab; + } else { +- RandomSource randomSource = this.positionalRandomFactory.at(x, y, z); +- ac = BlockPos.asLong(x * 16 + randomSource.nextInt(10), y * 12 + randomSource.nextInt(9), z * 16 + randomSource.nextInt(10)); ++ // C2ME - reuse random instance ++ com.ishland.c2me.opts.worldgen.general.common.random_instances.RandomUtils.derive(this.positionalRandomFactory, this.randomInstance, x, y, z); ++ final int i1 = randomInstance.nextInt(10); ++ final int i2 = randomInstance.nextInt(9); ++ final int i3 = randomInstance.nextInt(10); ++ ac = BlockPos.asLong(x * 16 + i1, y * 12 + i2, z * 16 + i3); + this.aquiferLocationCache[aa] = ac; + } + +- int ae = BlockPos.getX(ac) - i; +- int af = BlockPos.getY(ac) - j; +- int ag = BlockPos.getZ(ac) - k; +- int ah = ae * ae + af * af + ag * ag; +- if (o >= ah) { ++ int ad = (int) ((ac << WATER_LEVEL_MAGIC_1) >> WATER_LEVEL_MAGIC_2) - blockX; // C2ME - inline ++ int ae = (int) ((ac << WATER_LEVEL_MAGIC_3) >> WATER_LEVEL_MAGIC_4) - blockY; // C2ME - inline ++ int af = (int) ((ac << WATER_LEVEL_MAGIC_5) >> WATER_LEVEL_MAGIC_6) - blockZ; // C2ME - inline ++ int ag = ad * ad + ae * ae + af * af; ++ if (o >= ag) { + t = s; + s = r; + r = ac; + q = p; + p = o; +- o = ah; +- } else if (p >= ah) { ++ o = ag; ++ } else if (p >= ag) { + t = s; + s = ac; + q = p; +- p = ah; +- } else if (q >= ah) { ++ p = ag; ++ } else if (q >= ag) { + t = ac; +- q = ah; ++ q = ag; + } + } + } + } + +- Aquifer.FluidStatus fluidStatus2 = this.getAquiferStatus(r); +- double e = similarity(o, p); +- BlockState blockState = fluidStatus2.at(j); +- if (e <= 0.0D) { ++ Aquifer.FluidStatus fluidLevel2 = this.getAquiferStatus(r); ++ double e = 1.0 - Math.abs(p - o) / 25.0; // C2ME - inline ++ final BlockState fluidLevel2BlockState = fluidLevel2.at(blockY); ++ if (e <= 0.0) { + this.shouldScheduleFluidUpdate = e >= 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; ++ return fluidLevel2BlockState; + } else { +- MutableDouble mutableDouble = new MutableDouble(Double.NaN); +- Aquifer.FluidStatus fluidStatus3 = this.getAquiferStatus(s); +- double f = e * this.calculatePressure(functionContext, mutableDouble, fluidStatus2, fluidStatus3); +- if (d + f > 0.0D) { +- this.shouldScheduleFluidUpdate = false; +- return null; ++ final boolean fluidLevel2BlockStateOfWater = fluidLevel2BlockState.is(Blocks.WATER); ++ if (fluidLevel2BlockStateOfWater && this.globalFluidPicker.computeFluid(blockX, blockY - 1, blockZ).at(blockY - 1).is(Blocks.LAVA)) { ++ this.shouldScheduleFluidUpdate = true; ++ return fluidLevel2BlockState; + } else { +- Aquifer.FluidStatus fluidStatus4 = this.getAquiferStatus(t); +- double g = similarity(o, q); +- if (g > 0.0D) { +- double h = e * g * this.calculatePressure(functionContext, mutableDouble, fluidStatus2, fluidStatus4); +- if (d + h > 0.0D) { +- this.shouldScheduleFluidUpdate = false; +- return null; ++ double mutableDouble = Double.NaN; ++ Aquifer.FluidStatus fluidLevel3 = this.getAquiferStatus(s); ++ double result1; ++ final BlockState fluidLevel3BlockState = fluidLevel3.at(blockY); ++ final boolean fluidLevel2BlockStateOfLava = fluidLevel2BlockState.is(Blocks.LAVA); ++ final boolean fluidLevel3BlockStateOfLava = fluidLevel3BlockState.is(Blocks.LAVA); ++ final boolean fluidLevel3BlockStateOfWater = fluidLevel3BlockState.is(Blocks.WATER); ++ if ((!fluidLevel2BlockStateOfLava || !fluidLevel3BlockStateOfWater) && (!fluidLevel2BlockStateOfWater || !fluidLevel3BlockStateOfLava)) { ++ int j2 = Math.abs(fluidLevel2.fluidLevel - fluidLevel3.fluidLevel); ++ if (j2 == 0) { ++ result1 = 0.0; ++ } else { ++ double d2 = 0.5 * (fluidLevel2.fluidLevel + fluidLevel3.fluidLevel); ++ double e2 = blockY + 0.5 - d2; ++ double f2 = j2 / 2.0; ++ double o2 = f2 - Math.abs(e2); ++ double q2; ++ if (e2 > 0.0) { ++ double p2 = 0.0 + o2; ++ q2 = p2 > 0.0 ? p2 / 1.5 : p2 / 2.5; ++ } else { ++ double p2 = 3.0 + o2; ++ q2 = p2 > 0.0 ? p2 / 3.0 : p2 / 10.0; ++ } ++ ++ double r2; ++ if (!(q2 < -2.0) && !(q2 > 2.0)) { ++ double t2 = this.barrierNoise.compute(functionContext); ++ mutableDouble = t2; ++ r2 = t2; ++ } else { ++ r2 = 0.0; ++ } ++ ++ result1 = 2.0 * (r2 + q2); + } ++ } else { ++ result1 = 2.0; + } +- +- double ai = similarity(p, q); +- if (ai > 0.0D) { +- double aj = e * ai * this.calculatePressure(functionContext, mutableDouble, fluidStatus3, fluidStatus4); +- if (d + aj > 0.0D) { +- this.shouldScheduleFluidUpdate = false; +- return null; ++ double f = e * result1; ++ if (d + f > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return null; ++ } else { ++ Aquifer.FluidStatus fluidLevel4 = this.getAquiferStatus(t); ++ double g = 1.0 - (double) Math.abs(q - o) / 25.0; ++ final BlockState fluidLevel4BlockState = fluidLevel4.at(blockY); ++ final boolean fluidLevel4BlockStateOfWater = fluidLevel4BlockState.is(Blocks.WATER); ++ final boolean fluidLevel4BlockStateOfLava = fluidLevel4BlockState.is(Blocks.LAVA); ++ if (g > 0.0) { ++ double result; ++ if ((!fluidLevel2BlockStateOfLava || !fluidLevel4BlockStateOfWater) && (!fluidLevel2BlockStateOfWater || !fluidLevel4BlockStateOfLava)) { ++ int j1 = Math.abs(fluidLevel2.fluidLevel - fluidLevel4.fluidLevel); ++ if (j1 == 0) { ++ result = 0.0; ++ } else { ++ double d1 = 0.5 * (fluidLevel2.fluidLevel + fluidLevel4.fluidLevel); ++ double e1 = blockY + 0.5 - d1; ++ double f1 = j1 / 2.0; ++ double o1 = f1 - Math.abs(e1); ++ double q1; ++ if (e1 > 0.0) { ++ double p1 = 0.0 + o1; ++ q1 = p1 > 0.0 ? p1 / 1.5 : p1 / 2.5; ++ } else { ++ double p1 = 3.0 + o1; ++ q1 = p1 > 0.0 ? p1 / 3.0 : p1 / 10.0; ++ } ++ ++ double r1; ++ if (!(q1 < -2.0) && !(q1 > 2.0)) { ++ if (Double.isNaN(mutableDouble)) { ++ double t1 = this.barrierNoise.compute(functionContext); ++ mutableDouble = t1; ++ r1 = t1; ++ } else { ++ r1 = mutableDouble; ++ } ++ } else { ++ r1 = 0.0; ++ } ++ ++ result = 2.0 * (r1 + q1); ++ } ++ } else { ++ result = 2.0; ++ } ++ double h = e * g * result; ++ if (d + h > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return null; ++ } ++ } ++ ++ double h = 1.0 - (double) Math.abs(q - p) / 25.0; ++ if (h > 0.0) { ++ double result; ++ if ((!fluidLevel3BlockStateOfLava || !fluidLevel4BlockStateOfWater) && (!fluidLevel3BlockStateOfWater || !fluidLevel4BlockStateOfLava)) { ++ int j1 = Math.abs(fluidLevel3.fluidLevel - fluidLevel4.fluidLevel); ++ if (j1 == 0) { ++ result = 0.0; ++ } else { ++ double d1 = 0.5 * (fluidLevel3.fluidLevel + fluidLevel4.fluidLevel); ++ double e1 = blockY + 0.5 - d1; ++ double f1 = j1 / 2.0; ++ double o1 = f1 - Math.abs(e1); ++ double q1; ++ if (e1 > 0.0) { ++ double p1 = 0.0 + o1; ++ q1 = p1 > 0.0 ? p1 / 1.5 : p1 / 2.5; ++ } else { ++ double p1 = 3.0 + o1; ++ q1 = p1 > 0.0 ? p1 / 3.0 : p1 / 10.0; ++ } ++ ++ double r1; ++ if (!(q1 < -2.0) && !(q1 > 2.0)) { ++ if (Double.isNaN(mutableDouble)) { ++ double t1 = this.barrierNoise.compute(functionContext); ++ mutableDouble = t1; ++ r1 = t1; ++ } else { ++ r1 = mutableDouble; ++ } ++ } else { ++ r1 = 0.0; ++ } ++ ++ result = 2.0 * (r1 + q1); ++ } ++ } else { ++ result = 2.0; ++ } ++ double ah = e * h * result; ++ if (d + ah > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return null; ++ } + } +- } + +- this.shouldScheduleFluidUpdate = true; +- return blockState; ++ this.shouldScheduleFluidUpdate = true; ++ return fluidLevel2BlockState; ++ } + } + } + } + } + } ++ // Mirai end + + @Override + public boolean shouldScheduleFluidUpdate() { +@@ -303,51 +444,56 @@ public interface Aquifer { + return Math.floorDiv(z, 16); + } + ++ // Mirai start - c2me: aquifer ++ /** ++ * @author ishland ++ * @reason optimize ++ */ + private Aquifer.FluidStatus getAquiferStatus(long pos) { +- int i = BlockPos.getX(pos); +- int j = BlockPos.getY(pos); +- int k = BlockPos.getZ(pos); +- int l = this.gridX(i); +- int m = this.gridY(j); +- int n = this.gridZ(k); +- int o = this.getIndex(l, m, n); +- Aquifer.FluidStatus fluidStatus = this.aquiferCache[o]; +- if (fluidStatus != null) { +- return fluidStatus; ++ int i = (int) ((pos << WATER_LEVEL_MAGIC_1) >> WATER_LEVEL_MAGIC_2); // C2ME - inline ++ int j = (int) ((pos << WATER_LEVEL_MAGIC_3) >> WATER_LEVEL_MAGIC_4); // C2ME - inline ++ int k = (int) ((pos << WATER_LEVEL_MAGIC_5) >> WATER_LEVEL_MAGIC_6); // C2ME - inline ++ int l = Math.floorDiv(i, 16); // C2ME - inline ++ int m = Math.floorDiv(j, 12); // C2ME - inline ++ int n = Math.floorDiv(k, 16); // C2ME - inline ++ int o = ((m - this.minGridY) * this.gridSizeZ + n - this.minGridZ) * this.gridSizeX + l - this.minGridX; ++ Aquifer.FluidStatus fluidLevel = this.aquiferCache[o]; ++ if (fluidLevel != null) { ++ return fluidLevel; + } else { +- Aquifer.FluidStatus fluidStatus2 = this.computeFluid(i, j, k); +- this.aquiferCache[o] = fluidStatus2; +- return fluidStatus2; ++ Aquifer.FluidStatus fluidLevel2 = this.computeFluid(i, j, k); ++ this.aquiferCache[o] = fluidLevel2; ++ return fluidLevel2; + } + } + + private Aquifer.FluidStatus computeFluid(int i, int j, int k) { +- Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, j, k); ++ Aquifer.FluidStatus fluidLevel = this.globalFluidPicker.computeFluid(i, j, k); + int l = Integer.MAX_VALUE; + int m = j + 12; + int n = j - 12; + boolean bl = false; + +- for(int[] is : SURFACE_SAMPLING_OFFSETS_IN_CHUNKS) { +- int o = i + SectionPos.sectionToBlockCoord(is[0]); +- int p = k + SectionPos.sectionToBlockCoord(is[1]); ++ for (int[] is : SURFACE_SAMPLING_OFFSETS_IN_CHUNKS) { ++ int o = i + (is[0] << 4); // C2ME - inline ++ int p = k + (is[1] << 4); // C2ME - inline + int q = this.noiseChunk.preliminarySurfaceLevel(o, p); + int r = q + 8; + boolean bl2 = is[0] == 0 && is[1] == 0; + if (bl2 && n > r) { +- return fluidStatus; ++ return fluidLevel; + } + + boolean bl3 = m > r; +- if (bl3 || bl2) { +- Aquifer.FluidStatus fluidStatus2 = this.globalFluidPicker.computeFluid(o, r, p); +- if (!fluidStatus2.at(r).isAir()) { ++ if (bl2 || bl3) { ++ Aquifer.FluidStatus fluidLevel2 = this.globalFluidPicker.computeFluid(o, r, p); ++ if (!fluidLevel2.at(r).isAir()) { + if (bl2) { + bl = true; + } + + if (bl3) { +- return fluidStatus2; ++ return fluidLevel2; + } + } + } +@@ -356,43 +502,51 @@ public interface Aquifer { + } + + int s = l + 8 - j; +- int t = 64; +- double d = bl ? Mth.clampedMap((double)s, 0.0D, 64.0D, 1.0D, 0.0D) : 0.0D; +- double e = Mth.clamp(this.fluidLevelFloodednessNoise.compute(new DensityFunction.SinglePointContext(i, j, k)), -1.0D, 1.0D); +- double f = Mth.map(d, 1.0D, 0.0D, -0.3D, 0.8D); ++ double d = bl ? clampedLerpFromProgressInlined(s) : 0.0; ++ double e = Mth.clamp(this.fluidLevelFloodednessNoise.compute(new DensityFunction.SinglePointContext(i, j, k)), -1.0, 1.0); ++ double f = lerpFromProgressInlined(d, -0.3, 0.8); + if (e > f) { +- return fluidStatus; ++ return fluidLevel; + } else { +- double g = Mth.map(d, 1.0D, 0.0D, -0.8D, 0.4D); ++ double g = lerpFromProgressInlined(d, -0.8, 0.4); + if (e <= g) { +- return new Aquifer.FluidStatus(DimensionType.WAY_BELOW_MIN_Y, fluidStatus.fluidType); ++ return new Aquifer.FluidStatus(DimensionType.WAY_BELOW_MIN_Y, fluidLevel.fluidType); + } else { +- int u = 16; +- int v = 40; + int w = Math.floorDiv(i, 16); + int x = Math.floorDiv(j, 40); + int y = Math.floorDiv(k, 16); + int z = x * 40 + 20; +- int aa = 10; +- double h = this.fluidLevelSpreadNoise.compute(new DensityFunction.SinglePointContext(w, x, y)) * 10.0D; ++ double h = this.fluidLevelSpreadNoise.compute(new DensityFunction.SinglePointContext(w, x, y)) * 10.0; + int ab = Mth.quantize(h, 3); + int ac = z + ab; + int ad = Math.min(l, ac); + if (ac <= -10) { +- int ae = 64; +- int af = 40; + int ag = Math.floorDiv(i, 64); + int ah = Math.floorDiv(j, 40); + int ai = Math.floorDiv(k, 64); + double aj = this.lavaNoise.compute(new DensityFunction.SinglePointContext(ag, ah, ai)); +- if (Math.abs(aj) > 0.3D) { ++ if (Math.abs(aj) > 0.3) { + return new Aquifer.FluidStatus(ad, Blocks.LAVA.defaultBlockState()); + } + } + +- return new Aquifer.FluidStatus(ad, fluidStatus.fluidType); ++ return new Aquifer.FluidStatus(ad, fluidLevel.fluidType); + } + } + } ++ ++ private static double clampedLerpFromProgressInlined(double lerpValue) { ++ final double delta = lerpValue / 64.0; ++ if (delta < 0.0) { ++ return 1.0; ++ } else { ++ return delta > 1.0 ? 0.0 : 1.0 - delta; ++ } ++ } ++ ++ private static double lerpFromProgressInlined(double lerpValue, double start, double end) { ++ return start - (lerpValue - 1.0) * (end - start); ++ } ++ // Mirai end + } + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java +index 97c806aba9991a8663ed7b05c108c87f2b167853..2b65db81d39a406e080a7b956058f008d9dee4b3 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java +@@ -77,5 +77,11 @@ public class LegacyRandomSource implements BitRandomSource { + public void parityConfigString(StringBuilder info) { + info.append("LegacyPositionalRandomFactory{").append(this.seed).append("}"); + } ++ ++ // Mirai start - c2me: aquifer ++ public long getSeed() { ++ return seed; ++ } ++ // Mirai end + } + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/SingleThreadedRandomSource.java b/src/main/java/net/minecraft/world/level/levelgen/SingleThreadedRandomSource.java +index b2989c09928b74b93b1c356c4e9a77887fc589ad..2990e3bc266bcd814cfe0ea8eb865798e25aa169 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/SingleThreadedRandomSource.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/SingleThreadedRandomSource.java +@@ -39,4 +39,10 @@ public class SingleThreadedRandomSource implements BitRandomSource { + public double nextGaussian() { + return this.gaussianSource.nextGaussian(); + } ++ ++ // Mirai start - c2me: aquifer ++ public long getSeed() { ++ return seed; ++ } ++ // Mirai end + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/Xoroshiro128PlusPlus.java b/src/main/java/net/minecraft/world/level/levelgen/Xoroshiro128PlusPlus.java +index e96ac26b6788de1c372152c2a9e3ba11b1b2e132..ccc40303a46f0df98bde29e4453ac1d0d4d447e3 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/Xoroshiro128PlusPlus.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/Xoroshiro128PlusPlus.java +@@ -27,4 +27,22 @@ public class Xoroshiro128PlusPlus { + this.seedHi = Long.rotateLeft(m, 28); + return n; + } ++ ++ // Mirai start - c2me: aquifer ++ public long getSeedLo() { ++ return seedLo; ++ } ++ ++ public long getSeedHi() { ++ return seedHi; ++ } ++ ++ public void setSeedLo(long seedLo) { ++ this.seedLo = seedLo; ++ } ++ ++ public void setSeedHi(long seedHi) { ++ this.seedHi = seedHi; ++ } ++ // Mirai end + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java b/src/main/java/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +index b1b007adc22cdef8868202fa2e7265b50e74ab59..8fbe4f80fc4b6449019bf03bfaaa5c2e09ed9129 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +@@ -99,6 +99,12 @@ public class XoroshiroRandomSource implements RandomSource { + return this.randomNumberGenerator.nextLong() >>> 64 - bits; + } + ++ // Mirai start - c2me: aquifer ++ public Xoroshiro128PlusPlus getRandomNumberGenerator() { ++ return randomNumberGenerator; ++ } ++ // Mirai end ++ + public static class XoroshiroPositionalRandomFactory implements PositionalRandomFactory { + private static final HashFunction MD5_128 = Hashing.md5(); + private final long seedLo; +@@ -129,5 +135,15 @@ public class XoroshiroRandomSource implements RandomSource { + public void parityConfigString(StringBuilder info) { + info.append("seedLo: ").append(this.seedLo).append(", seedHi: ").append(this.seedHi); + } ++ ++ // Mirai start - c2me: aquifer ++ public long getSeedLo() { ++ return seedLo; ++ } ++ ++ public long getSeedHi() { ++ return seedHi; ++ } ++ // Mirai end + } + } diff --git a/patches/server/0105-c2me-worldgen-vanilla-bugs-fixes.patch b/patches/server/0105-c2me-worldgen-vanilla-bugs-fixes.patch new file mode 100644 index 0000000..33cd8cd --- /dev/null +++ b/patches/server/0105-c2me-worldgen-vanilla-bugs-fixes.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Wed, 20 Apr 2022 13:03:37 +0200 +Subject: [PATCH] c2me: worldgen vanilla bugs fixes + +Author: ishland + +Original license: MIT +Original project: https://github.com/RelativityMC/C2ME-fabric (Yarn mappings) + +Copyright (c) 2021-2022 ishland + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index 954e141e5617d6d52e2f3008c25fe9e2fe2f0f9a..f760f598e9fd4c2a72059334ff7ea5dbdee37696 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -26,6 +26,7 @@ import net.minecraft.world.level.levelgen.GenerationStep; + import net.minecraft.world.level.levelgen.Heightmap; + import net.minecraft.world.level.levelgen.blending.Blender; + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; ++import java.util.ArrayList; // Mirai - c2me: worldgen vanilla bugs fixes + + public class ChunkStatus { + +@@ -170,7 +171,7 @@ public class ChunkStatus { + }, (chunkstatus, worldserver, definedstructuremanager, lightenginethreaded, function, ichunkaccess) -> { + return (CompletableFuture) function.apply(ichunkaccess); + }); +- private static final List STATUS_BY_RANGE = ImmutableList.of(ChunkStatus.FULL, ChunkStatus.FEATURES, ChunkStatus.LIQUID_CARVERS, ChunkStatus.BIOMES, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, new ChunkStatus[0]); ++ private static List STATUS_BY_RANGE = ImmutableList.of(ChunkStatus.FULL, ChunkStatus.FEATURES, ChunkStatus.LIQUID_CARVERS, ChunkStatus.BIOMES, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, ChunkStatus.STRUCTURE_STARTS, new ChunkStatus[0]); // Mirai - remove final + private static final IntList RANGE_BY_STATUS = (IntList) Util.make(new IntArrayList(ChunkStatus.getStatusList().size()), (intarraylist) -> { + int i = 0; + +@@ -352,4 +353,12 @@ public class ChunkStatus { + + void doWork(ChunkStatus targetStatus, ServerLevel world, ChunkGenerator chunkGenerator, List chunks, ChunkAccess chunk); + } ++ ++ // Mirai start - c2me: worldgen vanilla bugs fixes ++ static { ++ final List distanceToStatus = new ArrayList<>(STATUS_BY_RANGE); ++ distanceToStatus.add(ChunkStatus.STRUCTURE_STARTS); ++ STATUS_BY_RANGE = ImmutableList.copyOf(distanceToStatus); ++ } ++ // Mirai end + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacedFeature.java b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacedFeature.java +index 327f723e8f9e482cdaed4b77fdb4c3b5bc5a4a54..4e58c9700b2fb3cd30f5b3b6cc30ba11db88502a 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacedFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacedFeature.java +@@ -16,8 +16,15 @@ import net.minecraft.world.level.WorldGenLevel; + import net.minecraft.world.level.chunk.ChunkGenerator; + import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; + import org.apache.commons.lang3.mutable.MutableBoolean; ++// Mirai start - c2me: worldgen vanilla bugs fixes ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++// Mirai end + + public record PlacedFeature(Holder> feature, List placement) { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); // Mirai - c2me: worldgen vanilla bugs fixes ++ + public static final Codec DIRECT_CODEC = RecordCodecBuilder.create((instance) -> { + return instance.group(ConfiguredFeature.CODEC.fieldOf("feature").forGetter((placedFeature) -> { + return placedFeature.feature; +@@ -37,25 +44,48 @@ public record PlacedFeature(Holder> feature, List stream = Stream.of(pos); ++ Stream stream; ++ for (int retries = 1; ; retries++) { ++ try { ++ stream = Stream.of(pos); ++ ++ for (PlacementModifier placementModifier : this.placement) { ++ stream = stream.flatMap(posx -> placementModifier.getPositions(context, random, posx)); ++ } + +- for(PlacementModifier placementModifier : this.placement) { +- stream = stream.flatMap((posx) -> { +- return placementModifier.getPositions(context, random, posx); +- }); ++ break; ++ } catch (IllegalStateException e) { ++ if (e.getMessage().equals("stream has already been operated upon or closed")) { ++ if (retries == 3) { ++ System.err.println("Retry failed, throwing exception"); ++ throw e; ++ } ++ System.err.println(String.format("Possible graalvm issue, retrying... (attempt %d)", retries + 1)); ++ e.printStackTrace(); ++ } ++ } + } + + ConfiguredFeature configuredFeature = this.feature.value(); +- MutableBoolean mutableBoolean = new MutableBoolean(); +- stream.forEach((blockPos) -> { ++ if (configuredFeature == null) { ++ LOGGER.error("NULL configuredFeature in PlacedFeature {}", this, new Throwable()); ++ return false; ++ } ++ MutableBoolean placementModifier = new MutableBoolean(); ++ stream.forEach(blockPos -> { + if (configuredFeature.place(context.getLevel(), context.generator(), random, blockPos)) { +- mutableBoolean.setTrue(); ++ placementModifier.setTrue(); + } +- + }); +- return mutableBoolean.isTrue(); ++ return placementModifier.isTrue(); + } ++ // Mirai end + + public Stream> getFeatures() { + return this.feature.value().getFeatures(); +@@ -66,6 +96,8 @@ public record PlacedFeature(Holder> feature, List +Date: Thu, 21 Apr 2022 19:43:31 +0200 +Subject: [PATCH] Configurable map update interval + + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 6a78374b6ab3198571df7e17023d371667ebb24b..efdcbbc5e70fc283a703462955ad3a1944998012 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -624,7 +624,7 @@ public class MapItemSavedData extends SavedData { + @Nullable + Packet nextUpdatePacket(int mapId) { + MapItemSavedData.MapPatch worldmap_b; +- if (!this.dirtyData && this.tick % 5 != 0) { this.tick++; return null; } // Paper - this won't end up sending, so don't render it! ++ if (!this.dirtyData && this.tick % wtf.etil.mirai.MiraiConfig.mapUpdateInterval != 0) { this.tick++; return null; } // Paper - this won't end up sending, so don't render it! // Mirai - configurable map update interval + boolean vanillaMaps = shouldUseVanillaMap(); // Paper + org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper + +@@ -637,7 +637,7 @@ public class MapItemSavedData extends SavedData { + + Collection collection; + +- if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet ++ if ((true || this.dirtyDecorations) && this.tick++ % wtf.etil.mirai.MiraiConfig.mapUpdateInterval == 0) { // CraftBukkit - custom maps don't update this yet // Mirai - configurable map update interval + this.dirtyDecorations = false; + // CraftBukkit start + java.util.Collection icons = new java.util.ArrayList(); +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index d304bc013ec2bd119d1af25163c694b8d09807f1..9503662a0723b2badbc4e28c3d1d0d1987d68630 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -254,4 +254,9 @@ public class MiraiConfig { + fastSpeedCheck = getBoolean("fast-speed-check", fastSpeedCheck); + } + ++ public static int mapUpdateInterval = 5; ++ private static void mapUpdate() { ++ mapUpdateInterval = getInt("map-update-interval", mapUpdateInterval); ++ } ++ + } +\ No newline at end of file diff --git a/patches/server/0107-Fix-hunger-saturation-depleting-on-peaceful.patch b/patches/server/0107-Fix-hunger-saturation-depleting-on-peaceful.patch new file mode 100644 index 0000000..d1768f0 --- /dev/null +++ b/patches/server/0107-Fix-hunger-saturation-depleting-on-peaceful.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 22 Apr 2022 18:50:49 +0200 +Subject: [PATCH] Fix hunger saturation depleting on peaceful + +The food saturation value is depleted on peaceful, even though +the displayed hunger bar never goes down. Hunger (or any related value, including saturation) +should not go down on peaceful. See https://bugs.mojang.com/browse/MC-31819. + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 695dd38d775c2e3d053f6779f03c7f260314faf0..ea393c638abaa52161812a34ade57d45d2155748 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1862,6 +1862,7 @@ public abstract class Player extends LivingEntity { + } + + public void causeFoodExhaustion(float f, EntityExhaustionEvent.ExhaustionReason reason) { ++ if (this.level.getDifficulty() == Difficulty.PEACEFUL) return; // Mirai - fix hunger saturation depleting on peaceful + // CraftBukkit end + if (!this.abilities.invulnerable) { + if (!this.level.isClientSide) { diff --git a/patches/server/0108-Fix-mobs-attacking-themselves.patch b/patches/server/0108-Fix-mobs-attacking-themselves.patch new file mode 100644 index 0000000..505d02d --- /dev/null +++ b/patches/server/0108-Fix-mobs-attacking-themselves.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 29 Apr 2022 12:08:22 +0200 +Subject: [PATCH] Fix mobs attacking themselves + +If an entity is provoked by a second one using commands, +the second will join in the fight against itself, +causing it to attack itself repeatedly. See https://bugs.mojang.com/browse/MC-110386. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +index 39ea15c7577af186d93d4ad9a48034d746a86fc8..214c1733ba2b5628b36ae6f5b653aa462cf3f86d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +@@ -114,6 +114,7 @@ public class HurtByTargetGoal extends TargetGoal { + } + + protected void alertOther(Mob mob, LivingEntity target) { ++ if (mob == target) return; // Mirai - avoid entities with suicidal thoughts /s + mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason + } + } diff --git a/patches/server/0109-Set-default-redstone-implementation-to-Alternate-Cur.patch b/patches/server/0109-Set-default-redstone-implementation-to-Alternate-Cur.patch new file mode 100644 index 0000000..fba9395 --- /dev/null +++ b/patches/server/0109-Set-default-redstone-implementation-to-Alternate-Cur.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sun, 8 May 2022 13:21:41 +0200 +Subject: [PATCH] Set default redstone implementation to Alternate Current + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 39e49aca16128b2ffe1cbde0ecbb47fa66fbdce8..ad7f6790badc0012a1df68d387a1cd725d4a2d5c 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -85,11 +85,11 @@ public class PaperWorldConfig { + public enum RedstoneImplementation { + VANILLA, EIGENCRAFT, ALTERNATE_CURRENT + } +- public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA; ++ public RedstoneImplementation redstoneImplementation = RedstoneImplementation.ALTERNATE_CURRENT; // Mirai - set default redstone implementation to Alternate Current + private void redstoneImplementation() { + String implementation; + if (PaperConfig.version < 27) { +- implementation = "vanilla"; ++ implementation = "alternate-current"; // Mirai - set default redstone implementation to Alternate Current + if (config.contains("world-settings.default.use-faster-eigencraft-redstone")) { + implementation = config.getBoolean("world-settings.default.use-faster-eigencraft-redstone") ? "eigencraft" : "vanilla"; + config.set("world-settings.default.redstone-implementation", implementation); +@@ -100,7 +100,7 @@ public class PaperWorldConfig { + } + remove("use-faster-eigencraft-redstone"); + } else { +- implementation = this.getString("redstone-implementation", "vanilla").toLowerCase().trim(); ++ implementation = this.getString("redstone-implementation", "alternate-current").toLowerCase().trim(); // Mirai - set default redstone implementation to Alternate Current + } + switch (implementation) { + default: diff --git a/patches/server/0110-Fix-brewing-stands-resetting-their-brewTime-when-bei.patch b/patches/server/0110-Fix-brewing-stands-resetting-their-brewTime-when-bei.patch new file mode 100644 index 0000000..5056768 --- /dev/null +++ b/patches/server/0110-Fix-brewing-stands-resetting-their-brewTime-when-bei.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sun, 8 May 2022 16:34:27 +0200 +Subject: [PATCH] Fix brewing stands resetting their brewTime when being + unloaded + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +index 5c0f1488c8a8100cd39a03adeccded9984722249..d0eb35ede343a09b29bed07291f10d1f741a0856 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +@@ -296,6 +296,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + ContainerHelper.loadAllItems(nbt, this.items); + this.brewTime = nbt.getShort("BrewTime"); + this.fuel = nbt.getByte("Fuel"); ++ this.ingredient = net.minecraft.core.Registry.ITEM.get(new net.minecraft.resources.ResourceLocation(nbt.getString("Mirai.ingredient"))); // Mirai - fix brewing stands resetting their brewTime when being unloaded + } + + @Override +@@ -304,6 +305,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + nbt.putShort("BrewTime", (short) this.brewTime); + ContainerHelper.saveAllItems(nbt, this.items); + nbt.putByte("Fuel", (byte) this.fuel); ++ nbt.putString("Mirai.ingredient", net.minecraft.core.Registry.ITEM.getKey(this.ingredient).toString()); // Mirai - fix brewing stands resetting their brewTime when being unloaded + } + + @Override diff --git a/patches/server/0111-Optimize-Util.combineSafe.patch b/patches/server/0111-Optimize-Util.combineSafe.patch new file mode 100644 index 0000000..a0bac05 --- /dev/null +++ b/patches/server/0111-Optimize-Util.combineSafe.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Fri, 10 Jun 2022 12:21:26 +0200 +Subject: [PATCH] Optimize `Util.combineSafe()` + + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 73693a3a7faaa2be24c367e351f75aeb0d0f2208..49ed7f364ef385cb591b3f7911bc1609b864d735 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -65,6 +65,7 @@ import java.util.concurrent.AbstractExecutorService; + import wtf.etil.mirai.server.util.ServerWorkerWrapper; + import net.minecraft.server.MCUtil; + import java.util.Collections; ++import java.util.ArrayList; // Mirai - optimize `Util.combineSafe()` + + public class Util { + static final Logger LOGGER = LogUtils.getLogger(); +@@ -74,6 +75,7 @@ public class Util { + //private static final ExecutorService BOOTSTRAP_EXECUTOR = makeExecutor("Bootstrap", -2); // Paper - add -2 priority // Patina + private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - add -1 priority + // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread ++ private static final CompletableFuture[] FUTURE_ARRAY_TEMPLATE = new CompletableFuture[0]; // Mirai - optimize `Util.combineSafe()` + public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() { + + private final AtomicInteger count = new AtomicInteger(); +@@ -440,26 +442,20 @@ public class Util { + } + } + ++ // Mirai start - optimize `Util.combineSafe()` + public static CompletableFuture> sequenceFailFast(List> futures) { +- List list = Lists.newArrayListWithCapacity(futures.size()); +- CompletableFuture[] completableFutures = new CompletableFuture[futures.size()]; +- CompletableFuture completableFuture = new CompletableFuture<>(); +- futures.forEach((future) -> { +- int i = list.size(); +- list.add((V)null); +- completableFutures[i] = future.whenComplete((object, throwable) -> { +- if (throwable != null) { +- completableFuture.completeExceptionally(throwable); +- } else { +- list.set(i, object); ++ // No method for collections, only for arrays ++ return CompletableFuture.allOf(futures.toArray(FUTURE_ARRAY_TEMPLATE)) ++ .thenApply(value -> { ++ var results = new ArrayList(futures.size()); ++ for (var future : futures) { ++ results.add(future.join()); ++ } ++ return results; + } +- +- }); +- }); +- return CompletableFuture.allOf(completableFutures).applyToEither(completableFuture, (void_) -> { +- return list; +- }); ++ ); + } ++ // Mirai end + + public static Optional ifElse(Optional optional, Consumer presentAction, Runnable elseAction) { + if (optional.isPresent()) { diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..d84af5c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://papermc.io/repo/repository/maven-public/") + } +} + +rootProject.name = "mirai" + +include("mirai-api", "mirai-server")