diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c35117f7..975aae38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,40 +41,55 @@ jobs: uses: styfle/cancel-workflow-action@0.4.1 with: access_token: ${{ github.token }} + - uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} + + - name: Pull Gradle Cache + uses: actions/cache@v2 + id: gradle-cache + with: + path: ~/.gradle + key: ${{ runner.os }}-maven-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-java-${{ matrix.java }} + + - name: Setup Yatopia Project + run: | + git config --global user.email "ci@github.com" + git config --global user.name "Github CI" + git config --global gc.auto 0 + sudo chmod -R -f 777 ./gradlew + ./gradlew initGitSubmodules + + - name: Get MC Version + run: echo "::set-output name=mcver::$(cat "Paper/work/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4)" + id: mcver + + - name: Pull Minecraft Decompile Cache + uses: actions/cache@v2 + id: decompile-cache + with: + path: Paper/work/Minecraft/${{ steps.mcver.outputs.mcver }} + key: ${{ steps.mcver.outputs.mcver }}-${{ runner.os }}-java-${{ matrix.java }}-minecraft-decomp + + - name: Apply Patches + run: | + ./gradlew setupUpstream + ./gradlew applyPatches + - name: Pull Maven Cache uses: actions/cache@v2 id: maven-cache with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- - - uses: s4u/maven-settings-action@v2.1.0 - name: Use ${{ matrix.java }} as the java target - with: - properties: '[{"maven.compiler.target": "${{ matrix.java }}"}]' - - name: Setup Yatopia Project - run: | - git config --global user.email "ci@github.com" - git config --global user.name "Github CI" - sudo chmod -R -f 777 scripts - ./yatopia init - - name: Get MC Version - run: echo "::set-output name=mcver::$(cat "Tuinity/Paper/work/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4)" - id: mcver - - name: Pull Minecraft Decompile Cache - uses: actions/cache@v2 - id: decompile-cache - with: - path: Tuinity/Paper/work/Minecraft/${{ steps.mcver.outputs.mcver }} - key: ${{ steps.mcver.outputs.mcver }}-${{ runner.os }}-minecraft-decomp + - name: Build Yatopia run: | - ./yatopia full + ./gradlew paperclip + - name: Upload Artifact if: github.ref != 'refs/heads/ver/1.16.4' uses: actions/upload-artifact@v2 diff --git a/.gitignore b/.gitignore index e8e2618e..d50025d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,62 @@ -Spigot +# JVM crash related +core.* +hs_err_pid* + +# Intellij +.idea/ +*.iml +*.ipr +*.iws +out/ + +# Eclipse +.classpath +.project +.settings/ + +# netbeans +nbproject/ +nbactions.xml + +# Gradle +!gradle-wrapper.jar +.gradle/ +build/ +*/build/ + +# we use maven! +build.xml + +# Maven +log/ +target/ +dependency-reduced-pom.xml + +# various other potential build files +build/ +bin/ +dist/ +manifest.mf + +# Mac +.DS_Store/ +.DS_Store + +# vim +.*.sw[a-p] + +# Linux temp files +*~ + +# other stuff +run/ +docs/build/ + Yatopia-Server Yatopia-API mc-dev -*.iml -.settings/org.eclipse.m2e.core.prefs -.project -.idea -yatopia-paperclip.jar - -target - -target/site/surefire-report.html -/patches/Akarin/** -/patches/Purpur/** -/patches/Empirecraft/** -/patches/Rainforest/** -/patches/Origami/** -/patches/AirplaneLite/** -!/patches/Purpur/server.txt -!/patches/Purpur/api.txt -!/patches/Empirecraft/server.txt -!/patches/Empirecraft/api.txt -!/patches/Akarin/server.txt -!/patches/Akarin/api.txt -!/patches/Rainforest/server.txt -!/patches/Rainforest/api.txt -!/patches/Origami/server.txt -!/patches/Origami/api.txt -!/patches/AirplaneLite/server.txt -!/patches/AirplaneLite/api.txt -*.jar - -/testserver/** \ No newline at end of file +yatopia-launcher.jar +*clip.jar +last-paper +!upstreamConfig/* +!upstreamCommits/* diff --git a/.gitmodules b/.gitmodules index f2e40816..8514d7d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,23 +1,39 @@ -[submodule "Tuinity"] - path = Tuinity - url = https://github.com/YatopiaMC/Yatopia-Tuninty.git - branch = ver/1.16.5 -[submodule "Akarin"] - path = Akarin - url = https://github.com/Akarin-project/Akarin.git - branch = ver/1.16.4 -[submodule "Purpur"] - path = Purpur +[submodule "Paper"] + path = Paper + url = https://github.com/PaperMC/Paper.git + +[submodule "upstream/Tuinity"] + path = upstream/Tuinity + url = https://github.com/Spottedleaf/Tuinity + branch = master + +[submodule "upstream/Purpur"] + path = upstream/Purpur url = https://github.com/pl3xgaming/Purpur.git branch = ver/1.16.5 -[submodule "Empirecraft"] - path = Empirecraft + +[submodule "upstream/AirplaneLite"] + path = upstream/AirplaneLite + url = https://github.com/Technove/AirplaneLite.git + branch = master + +[submodule "upstream/Akarin"] + path = upstream/Akarin + url = https://github.com/Akarin-project/Akarin.git + branch = ver/1.16.4 + +[submodule "upstream/Empirecraft"] + path = upstream/Empirecraft url = https://github.com/starlis/empirecraft.git branch = master -[submodule "Origami"] - path = Origami + +[submodule "upstream/Origami"] + path = upstream/Origami url = https://github.com/Minebench/Origami.git branch = 1.16 -[submodule "AirplaneLite"] - path = AirplaneLite - url = https://github.com/Technove/AirplaneLite.git + +[submodule "Yatopia-API"] + path = Yatopia-API + +[submodule "Yatopia-Server"] + path = Yatopia-Server diff --git a/AirplaneLite b/AirplaneLite deleted file mode 160000 index c496d1cc..00000000 --- a/AirplaneLite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c496d1cc713f5c32b1742d02546c281a2b8cbaa9 diff --git a/Jenkinsfile b/Jenkinsfile index f0f94d2d..881745bd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,15 +6,14 @@ pipeline { steps { scmSkip(deleteBuild: true, skipPattern:'.*\\[CI-SKIP\\].*') sh 'rm -rf ./target' - sh 'rm -rf ./Tuinity/Paper/Paper-API ./Tuinity/Paper/Paper-Server ./Tuinity/Paper/work/Spigot/Spigot-API ./Tuinity/Paper/work/Spigot/Spigot-Server' - sh 'rm -rf ./Tuinity/Tuinity-API ./Tuinity/Tuinity-Server ./Tuinity/mc-dev' + sh 'rm -rf ./Paper/Paper-API ./Paper/Paper-Server ./Paper/work/Spigot/Spigot-API ./Paper/work/Spigot/Spigot-Server' sh 'rm -rf ./Yatopia-API ./Yatopia-Server' - sh 'chmod +x ./scripts/*.sh' + sh 'chmod +x ./gradlew' } } stage('Init project & submodules') { steps { - sh './yatopia init' + sh './gradlew initGitSubmodules' } } stage('Decompile & apply patches') { @@ -28,17 +27,13 @@ pipeline { publisherStrategy: 'EXPLICIT', ) { sh ''' - set -e - source "./scripts/functions.sh" - basedir - $scriptdir/updateUpstream.sh "$basedir" false true || exit 1 - set -e - $scriptdir/applyPatches.sh "$basedir" || exit 1 + ./gradlew setupUpstream + ./gradlew applyPatches ''' } } } - stage('Build API') { + stage('Build') { tools { jdk "OpenJDK 8" } @@ -48,23 +43,10 @@ pipeline { mavenLocalRepo: '.repository', publisherStrategy: 'EXPLICIT' ) { - sh 'mvn -N install org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy' - sh 'cd Yatopia-API && mvn install org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy' - sh 'cd ./Tuinity/Paper/Paper-MojangAPI && mvn install' - } - } - } - stage('Build Server') { - tools { - jdk "OpenJDK 8" - } - steps { - withMaven( - maven: '3', - mavenLocalRepo: '.repository', - publisherStrategy: 'EXPLICIT' - ) { - sh 'cd Yatopia-Server && mvn install org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy -DaltDeploymentRepository=codemc-snapshots::default::https://repo.codemc.org/repository/nms-local/' + sh ''' + ./gradlew build + ./gradlew publish + ''' } } } @@ -79,18 +61,10 @@ pipeline { publisherStrategy: 'EXPLICIT' ) { sh ''' - basedir=$(pwd) - paperworkdir="$basedir/Tuinity/Paper/work" - mcver=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) - serverjar="$basedir/Yatopia-Server/target/yatopia-$mcver.jar" - vanillajar="$paperworkdir/Minecraft/$mcver/$mcver.jar" - ( - cd "$paperworkdir/Paperclip" - mvn clean package "-Dmcver=$mcver" "-Dpaperjar=$serverjar" "-Dvanillajar=$vanillajar" - ) mkdir -p "./target" - cp "$paperworkdir/Paperclip/assembly/target/paperclip-$mcver.jar" "./target/yatopia-$mcver-paperclip-b$BUILD_NUMBER.jar" - ''' + ./gradlew paperclip + cp "yatopia-$mcver-paperclip.jar" "./target/yatopia-$mcver-paperclip-b$BUILD_NUMBER.jar" + ''' } } post { diff --git a/Licensing/LICENSE.md b/Licensing/LICENSE.md index edbbb226..c4f39c23 100644 --- a/Licensing/LICENSE.md +++ b/Licensing/LICENSE.md @@ -1,11 +1,5 @@ -The project (Everything that is not a .patch file) is licensed under the MIT license found [here](https://github.com/YatopiaMC/Yatopia/blob/ver/1.16.2/Licensing/MIT.md). - -All patches (.patch files) marked with "lithium" are licensed under LGPL3 found [here](https://github.com/jellysquid3/lithium-fabric/blob/1.16.x/dev/LICENSE.txt). - -All patches (.patch files) marked with "tic-tacs" are licensed under LGPL3 found [here](https://github.com/gegy1000/tic-tacs/blob/1.16.2/LICENSE). - -All patches (.patch files) marked with "gluelist" are licensed under Apache 2.0 found [here](https://github.com/ertugrulcetin/GlueList/blob/master/Readme.md). - -All other patches (.patch files) included in this repo are licensed under the MIT license found [here](https://github.com/YatopiaMC/Yatopia/blob/ver/1.16.2/Licensing/MIT.md). - -See [EMC](https://github.com/starlis/empirecraft/blob/master/README.md), [Akarin](https://github.com/Akarin-project/Akarin/blob/1.16.3/LICENSE.md), [Purpur](https://github.com/pl3xgaming/Purpur/blob/ver/1.16.3/LICENSE), [Rainforest](https://github.com/Proximyst/Rainforest), [Origami](https://github.com/Minebench/Origami/blob/1.16/PATCHES-LICENSE), and [Tuinity](https://github.com/Spottedleaf/Tuinity/blob/master/PATCHES-LICENSE) for the license of patches automatically pulled when built. +The project without the build tools (Everything that is not a .patch file or in the buildSrc folder) are licensed under the MIT license found [here](MIT.md).
+All files in the buildSrc folder are licensed under MIT found [here](../buildSrc/license.txt)
+All patches (.patch files) marked with "lithium" are licensed under LGPL3 found [here](https://github.com/jellysquid3/lithium-fabric/blob/1.16.x/dev/LICENSE.txt).
+All other patches (.patch files) included in this repo are licensed under the MIT license found [here](MIT.md).
+See [EMC](https://github.com/starlis/empirecraft/blob/master/README.md), [Akarin](https://github.com/Akarin-project/Akarin/blob/1.16.3/LICENSE.md), [Purpur](https://github.com/pl3xgaming/Purpur/blob/ver/1.16.5/LICENSE), [AirplaneLite](https://github.com/Technove/AirplaneLite/blob/master/PATCHES-LICENSE), [Origami](https://github.com/Minebench/Origami/blob/1.16/PATCHES-LICENSE), and [Tuinity](https://github.com/Spottedleaf/Tuinity/blob/master/PATCHES-LICENSE) for the license of patches automatically pulled during upstream updates. diff --git a/Licensing/MIT.md b/Licensing/MIT.md index 0e4488dc..94694b9f 100644 --- a/Licensing/MIT.md +++ b/Licensing/MIT.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 YatopiaMC +Copyright (c) 2020 - 2021 YatopiaMC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PATCHES.md b/PATCHES.md index 610e19a9..7e5116cd 100644 --- a/PATCHES.md +++ b/PATCHES.md @@ -8,89 +8,251 @@ # Patches | Side | Patch | Author | CoAuthors | | ----- | ------------- |:-------------:| -----:| +| api | AFK API | William Blake Galbreath | | +| server | AFK API | William Blake Galbreath | | +| server | Ability to re-add farmland mechanics from Alpha | Yive | | +| server | Actually unload POI data | Spottedleaf | | +| server | Add /ping command | William Blake Galbreath | | | server | Add 5 second tps average in /tps | William Blake Galbreath | | | api | Add ChatColor.getById | Aikar | | +| api | Add EntityTeleportHinderedEvent | Mariell Hoversholm | | +| server | Add EntityTeleportHinderedEvent | Mariell Hoversholm | | | api | Add GameProfileLookupEvent | tr7zw | | | server | Add GameProfileLookupEvent | tr7zw | | -| server | Add IntelliJ IDEA runnable | Bud Gidiere | | +| server | Add IntelliJ IDEA runnable | Zoe | | | server | Add JsonList save timings | Ivan Pekov | | | api | Add NBT API as a first-class lib | tr7zw | | | server | Add NBT API as a first-class lib | tr7zw | | +| api | Add StructureGenerateEvent | Nahuel | Mariell Hoversholm | +| server | Add StructureGenerateEvent | Nahuel | Mariell Hoversholm | +| server | Add Velocity natives for encryption and compression | Andrew Steinborn | | | server | Add a special case for floodgate and offline uuids | Ivan Pekov | | +| server | Add adjustable breeding cooldown to config | montlikadani | | +| server | Add allow water in end world option | William Blake Galbreath | | +| server | Add boat fall damage config | BillyGalbreath | | +| server | Add canSaveToDisk to Entity | William Blake Galbreath | | | server | Add component util | William Blake Galbreath | | +| server | Add config for allowing Endermen to despawn even while | jmp | | +| server | Add configurable snowball damage | BillyGalbreath | | +| api | Add critical hit check to EntityDamagedByEntityEvent | BillyGalbreath | | +| server | Add critical hit check to EntityDamagedByEntityEvent | BillyGalbreath | | +| server | Add demo command | BillyGalbreath | | +| server | Add enderman and creeper griefing controls | William Blake Galbreath | | | api | Add last tick time API | Ivan Pekov | tr7zw | | server | Add last tick time API | Ivan Pekov | tr7zw | +| server | Add mobGriefing bypass to everything affected | Encode42 | | | server | Add no-tick block list | William Blake Galbreath | | | server | Add nspt command | Ivan Pekov | | +| server | Add obfhelpers for plugin use | William Blake Galbreath | | +| server | Add option for boats to eject players on land | William Blake Galbreath | | +| server | Add option to allow loyalty on tridents to work in the void | William Blake Galbreath | | | server | Add option to disable dolphin treasure searching | William Blake Galbreath | | +| server | Add option to disable mushroom block updates | William Blake Galbreath | | | server | Add option to disable observer clocks | Phoenix616 | | +| api | Add option to disable zombie aggressiveness towards villagers | nitricspace | | +| server | Add option to disable zombie aggressiveness towards villagers | nitricspace | | +| server | Add option to set armorstand step height | William Blake Galbreath | | +| server | Add option to teleport to spawn if outside world border | William Blake Galbreath | | +| server | Add packet limiter config | Spottedleaf | | | server | Add permission for F3+N debug | William Blake Galbreath | | +| server | Add phantom spawning options | William Blake Galbreath | | +| server | Add player death exp control options | William Blake Galbreath | | +| api | Add predicate to recipe's ExactChoice ingredient | William Blake Galbreath | | +| server | Add predicate to recipe's ExactChoice ingredient | William Blake Galbreath | | +| server | Add soft async catcher | Spottedleaf | | +| server | Add tablist suffix option for afk | montlikadani | | | server | Add timings for Behavior | Phoenix616 | | | server | Add timings for Pathfinder | MrIvanPlays | | | server | Add twisting and weeping vines growth rates | BillyGalbreath | | -| server | AirplaneLite Data Structs | Paul Sauve | | +| server | Add vindicator johnny spawn chance | William Blake Galbreath | | +| server | Add wither skeleton takes wither damage option | William Blake Galbreath | | +| api | Advancement API | William Blake Galbreath | | +| server | Advancement API | William Blake Galbreath | | | server | AirplaneLite MC Dev Fixes | Paul Sauve | | +| server | Allow Entities to be removed from a world while ticking | Spottedleaf | | | server | Allow anvil colors | William Blake Galbreath | | +| server | Allow color codes in books | William Blake Galbreath | | +| server | Allow controlled flushing for network manager | Spottedleaf | | | server | Allow infinite and mending enchantments together | William Blake Galbreath | | | api | Allow inventory resizing | William Blake Galbreath | | | server | Allow leashing villagers | William Blake Galbreath | | | server | Allow soil to moisten from water directly under it | William Blake Galbreath | | | server | Allow to change the piston push limit | tr7zw | | +| server | Allow toggling special MobSpawners per world | jmp | | +| api | Alphabetize in-game /plugins list | BillyGalbreath | | | server | Alternative Keepalive Handling | William Blake Galbreath | | +| api | Anvil API | William Blake Galbreath | | +| server | Anvil API | William Blake Galbreath | | +| server | Apply display names from item forms of entities to entities | jmp | | +| server | Arrows should not reset despawn counter | William Blake Galbreath | | +| server | Attempt to recalculate regionfile header if it is corrupt | Spottedleaf | | | server | Avoid double I/O operation on load player file | ㄗㄠˋ ㄑㄧˊ | | | server | Barrels and enderchests 6 rows | William Blake Galbreath | | +| server | Brand changes | Spottedleaf | | | server | Brandings | YatopiaMC | | +| server | Breedable Polar Bears | William Blake Galbreath | | +| api | Bring back server name | William Blake Galbreath | | +| server | Bring back server name | William Blake Galbreath | | +| server | Cat spawning options | William Blake Galbreath | | +| server | Change writes to use NORMAL priority rather than LOW | Spottedleaf | | +| server | Changeable Mob Left Handed Chance | Ben Kerllenevich | | +| server | Charged creeper naturally spawn | William Blake Galbreath | | +| api | ChatColor conveniences | William Blake Galbreath | | +| server | Chickens can retaliate | William Blake Galbreath | | +| server | Config migration: climbing should not bypass cramming | jmp | | | server | Config migration: disable saving projectiles to disk -> | jmp | | +| server | Config to allow Note Block sounds when blocked | Encode42 | | | server | Configurable BlockPhysicsEvent | Mykyta Komarnytskyy | | +| server | Configurable TPS Catchup | William Blake Galbreath | | +| server | Configurable chance for wolves to spawn rabid | Encode42 | | | server | Configurable criterion triggers | Mykyta Komarnytskyy | | +| server | Configurable daylight cycle | William Blake Galbreath | | +| server | Configurable default wolf collar color | Encode42 | | +| server | Configurable dungeon seed | William Blake Galbreath | | | server | Configurable enchanting table tick | Ivan Pekov | | +| server | Configurable end spike seed | William Blake Galbreath | | +| server | Configurable entity base attributes | BillyGalbreath | | | server | Configurable flight checks | l_MrBoom_l | | +| server | Configurable jockey options | William Blake Galbreath | | | server | Configurable movement checks | l_MrBoom_l | | +| api | Configurable permission message upgrades | William Blake Galbreath | | +| server | Configurable server mod name | William Blake Galbreath | | | server | Configurable villager brain ticks | William Blake Galbreath | | +| server | Configurable villager breeding | draycia | | +| server | Configurable void damage height | William Blake Galbreath | | +| server | Consolidate flush calls for entity tracker packets | Spottedleaf | | +| server | Controllable Minecarts | William Blake Galbreath | | +| server | Copy passenger list in enderTeleportTo | Spottedleaf | | | server | Cows eat mushrooms | William Blake Galbreath | | -| server | Custom Locale Support | Bud Gidiere | | +| server | Crying obsidian valid for portal frames | William Blake Galbreath | | +| server | Custom Locale Support | Zoe | | +| server | Customizable wither health and healing | jmp | | | api | Default permissions | William Blake Galbreath | | +| server | Delay chunk unloads | Spottedleaf | | | server | Despawn rate config options per projectile type | jmp | | +| server | Detail more information in watchdog dumps | Spottedleaf | | +| server | Disable loot drops on death by cramming | William Blake Galbreath | | +| server | Disable outdated build check | William Blake Galbreath | | | api | Disable reload command | Ivan Pekov | | | server | Disable the Snooper | Sotr | | +| server | Dispenser curse of binding protection | William Blake Galbreath | | +| server | Dispensers place anvils option | William Blake Galbreath | | +| server | Distance manager tick timings | Spottedleaf | | +| server | Do not allow ticket level changes while unloading | Spottedleaf | | +| server | Do not load chunks during a crash report | Spottedleaf | | +| server | Do not retain playerchunkmap instance in light thread factory | Spottedleaf | | | server | Do not update distance map when animal and mob spawning is | Beech Horn | | +| server | Don't allow StructureLocateEvent to change worlds | Spottedleaf | | +| server | Don't get entity equipment if not needed | Paul Sauve | | | server | Don't load chunk with seed based feature search | Phoenix616 | | +| server | Don't lookup fluid state when raytracing | Spottedleaf | | | server | Don't trigger Lootable Refresh for non player interaction | Aikar | | | server | Don't wake up entities when damage event is cancelled | Phoenix616 | | | server | Dont send useless entity packets | William Blake Galbreath | | +| api | DragonEggPlaceEvent | William Blake Galbreath | | +| server | DragonEggPlaceEvent | William Blake Galbreath | | +| server | Duplicate paper's vanilla scoreboard colors patch to sync | William Blake Galbreath | | +| server | EMC - Configurable disable give dropping | Aikar | | +| api | EMC - MonsterEggSpawnEvent | Aikar | | +| server | EMC - MonsterEggSpawnEvent | Aikar | | +| server | End gateway should check if entity can use portal | William Blake Galbreath | | +| server | Ender dragon always drop egg | William Blake Galbreath | | +| server | Ender dragon always drop full exp | William Blake Galbreath | | | server | Ensure pools create daemon threads | Ivan Pekov | | +| server | Entities can use portals configuration | William Blake Galbreath | | +| server | Entities pick up loot bypass mob-griefing gamerule | William Blake Galbreath | | | server | Entity lifespan | William Blake Galbreath | | +| api | EntityMoveEvent | William Blake Galbreath | | +| server | EntityMoveEvent | William Blake Galbreath | | +| server | Execute chunk tasks mid-tick | Spottedleaf | | +| api | ExecuteCommandEvent | William Blake Galbreath | | +| server | Farmland trampling changes | Mariell Hoversholm | | | server | Fix 'outdated server' showing in ping before server fully | William Blake Galbreath | | | server | Fix Bukkit.createInventory() with type LECTERN | willies952002 | | | server | Fix IndexOutOfBoundsException when sending too many changes | Ivan Pekov | | | server | Fix LightEngineThreaded memory leak | Ivan Pekov | | +| server | Fix cow rotation when shearing mooshroom | William Blake Galbreath | | +| server | Fix death message colors | William Blake Galbreath | | | server | Fix exp drop of zombie pigmen (MC-56653) | Phoenix616 | | +| api | Fix javadoc warnings (missing @param and @return) | BillyGalbreath | | | server | Fix lead fall dmg config | tr7zw | | | server | Fix rotating UP/DOWN CW and CCW | BillyGalbreath | | | server | Fix stuck in portals | BillyGalbreath | | +| server | Fix swamp hut cat generation deadlock | Spottedleaf | | | server | Fix the dead lagging the server | William Blake Galbreath | | | server | Fix vanilla command permission handler | William Blake Galbreath | | +| api | Full netherite armor grants fire resistance | BillyGalbreath | | +| server | Full netherite armor grants fire resistance | BillyGalbreath | | +| server | Giants AI settings | William Blake Galbreath | | | server | Global Eula file | tr7zw | | | server | Heavily optimize furnance fuel and recipe lookups | tr7zw | Mykyta Komarn | | server | Heavily optimize recipe lookups in CraftingManager | Mykyta Komarn | Ivan Pekov | +| server | Highly optimise single and multi-AABB VoxelShapes and | Spottedleaf | | | server | Highly optimize VillagePlace filtering | Ivan Pekov | | | server | Hopper Optimizations | Phoenix616 | | +| server | Illusioners AI settings | William Blake Galbreath | | | server | Implement TPSBar | BillyGalbreath | | +| server | Implement bed explosion options | William Blake Galbreath | | +| server | Implement configurable search radius for villagers to spawn | William Blake Galbreath | | +| server | Implement elytra settings | William Blake Galbreath | | | server | Implement infinite lava | William Blake Galbreath | | +| server | Implement respawn anchor explosion options | William Blake Galbreath | | +| server | Improve abnormal server shutdown process | Spottedleaf | | +| server | Improve async tp to not load chunks when crossing worlds | Spottedleaf | | +| server | Improve paper prevent moving into unloaded chunk check | Spottedleaf | | | server | Improve task performance | ishland | Mykyta Komarn | -| server | Infinity No Arrows | Bud Gidiere | | +| server | Improved oversized chunk data packet handling | Spottedleaf | | +| server | Infinite fuel furnace | William Blake Galbreath | | +| server | Infinity No Arrows | Zoe | | | server | Infinity bow settings | William Blake Galbreath | | +| api | Item entity immunities | William Blake Galbreath | | +| server | Item entity immunities | William Blake Galbreath | | | server | Item stuck sleep config | tr7zw | | +| api | ItemFactory#getMonsterEgg | William Blake Galbreath | | +| server | ItemFactory#getMonsterEgg | William Blake Galbreath | | +| api | ItemStack convenience methods | William Blake Galbreath | | +| server | Kelp weeping and twisting vines configurable max growth age | BillyGalbreath | | +| server | Lag compensate block breaking | Spottedleaf | | +| api | Lagging threshold | William Blake Galbreath | | | server | Lagging threshold | William Blake Galbreath | | +| api | Left handed API | BillyGalbreath | | +| server | Left handed API | BillyGalbreath | | +| api | LivingEntity safeFallDistance | William Blake Galbreath | | +| server | LivingEntity safeFallDistance | William Blake Galbreath | | +| api | LivingEntity#broadcastItemBreak | William Blake Galbreath | | +| server | LivingEntity#broadcastItemBreak | William Blake Galbreath | | +| api | Llama API | William Blake Galbreath | | +| server | Llama API | William Blake Galbreath | | +| server | Lobotomize stuck villagers | BillyGalbreath | | +| server | Logger settings (suppressing pointless logs) | William Blake Galbreath | | | server | MC-147659 - Fix non black cats spawning in swamp huts | William Blake Galbreath | | | server | MC-168772 Fix - Add turtle egg block options | William Blake Galbreath | | +| server | MC-Dev fixes | Spottedleaf | | +| server | Make CallbackExecutor strict again | Spottedleaf | | +| server | Make Iron Golems Swim | William Blake Galbreath | | +| server | Make animal breeding times configurable | jmp | | | server | Make lava flow speed configurable | William Blake Galbreath | | +| server | Make sure inlined getChunkAt has inlined logic for loaded | Spottedleaf | | +| server | Manually inline methods in BlockPosition | Spottedleaf | | +| server | Mending mends most damages equipment first | William Blake Galbreath | | | api | Modify POM | YatopiaMC | | | server | Modify POM | YatopiaMC | | | server | Modify default configs | tr7zw | | +| server | Movement options for armour stands | Mariell Hoversholm | | +| server | Multi-Threaded Server Ticking Vanilla | Spottedleaf | | +| server | Multi-Threaded ticking CraftBukkit | Spottedleaf | | +| server | Name craft scheduler threads according to the plugin using | Spottedleaf | | | server | Nuke streams off BlockPosition | Ivan Pekov | | | server | Nuke streams off SectionPosition | Ivan Pekov | | +| server | Optimise EntityInsentient#checkDespawn | Spottedleaf | | +| server | Optimise WorldServer#notify | Spottedleaf | | +| server | Optimise chunk tick iteration | Spottedleaf | | +| server | Optimise closest entity lookup used by AI goals | Spottedleaf | | +| server | Optimise collision checking in player move packet handling | Spottedleaf | | +| server | Optimise entity hard collision checking | Spottedleaf | | +| server | Optimise non-flush packet sending | Spottedleaf | | | server | Optimise portals | Ivan Pekov | | +| server | Optimise tab complete | Spottedleaf | | | server | Optimised hallowen checker | Ivan Pekov | | | server | Optimize BehaviorController | MrIvanPlays | | | server | Optimize TileEntity load/unload | tr7zw | | @@ -100,44 +262,108 @@ # Patches | server | Optimize random calls in chunk ticking | Paul Sauve | | | server | Optimize some stuff in WorldServer ticking | MrIvanPlays | | | server | Optimize whitelist command for multiple additions / removals | Ivan Pekov | | +| server | Option for Villager Clerics to farm Nether Wart | jmp | | +| server | Option for chests to open even with a solid block on top | jmp | | | server | Option for simpler Villagers | tr7zw | | | server | Option to toggle milk curing bad omen | William Blake Galbreath | | +| server | Origami - Fix ProtocolLib issues on Java 15 | Phoenix616 | | | server | Origami Server Config | Phoenix616 | | | server | PaperPR - Add hex color code support for console logging | Esophose | | +| server | PaperPR - Config option for Piglins guarding chests | jmp | | | server | PaperPR - Fix username connecting with no texture being | Camotoy | | +| api | PaperPR - PlayerItemCooldownEvent | KennyTV | | +| server | Per World Spawn Limits | Chase Whipple | | | server | Per entity (type) collision settings | MrIvanPlays | tr7zw | | server | Persistent TileEntity Lore and DisplayName | jmp | | +| server | Phantom flames on swoop | BillyGalbreath | | +| api | Phantoms attracted to crystals and crystals shoot phantoms | William Blake Galbreath | | +| server | Phantoms attracted to crystals and crystals shoot phantoms | William Blake Galbreath | | +| server | Phantoms burn in light | draycia | | +| server | Pigs give saddle back | William Blake Galbreath | | +| api | Player invulnerabilities | William Blake Galbreath | | +| server | Player invulnerabilities | William Blake Galbreath | | | api | PlayerAttackEntityEvent | Ivan Pekov | | | server | PlayerAttackEntityEvent | Ivan Pekov | | +| api | PlayerBookTooLargeEvent | BillyGalbreath | | +| server | PlayerBookTooLargeEvent | BillyGalbreath | | +| api | PlayerSetSpawnerTypeWithEggEvent | William Blake Galbreath | | +| server | PlayerSetSpawnerTypeWithEggEvent | William Blake Galbreath | | +| server | Players should not cram to death | William Blake Galbreath | | +| server | Populator seed controls | Spottedleaf | | +| server | Prevent light queue overfill when no players are online | Spottedleaf | | +| server | Prevent long map entry creation in light engine | Spottedleaf | | +| server | Prevent unload() calls removing tickets for sync loads | Spottedleaf | | +| server | Properly handle cancellation of projectile hit event | Spottedleaf | | | api | ProxyForwardDataEvent | Ivan Pekov | | | server | ProxyForwardDataEvent | Ivan Pekov | | +| api | Purpur config files | William Blake Galbreath | | | server | Purpur config files | William Blake Galbreath | | +| server | Queue lighting update only once | Paul Sauve | | +| server | Rabbit naturally spawn toast and killer | William Blake Galbreath | | +| api | Rabid Wolf API | Encode42 | | +| server | Raid cooldown setting | jmp | | +| server | Range check flag dirty calls in PlayerChunk | Spottedleaf | | +| server | Rebrand | William Blake Galbreath | | | server | Redirect Configs | tr7zw | | +| server | Redstone deactivates spawners | draycia | | +| server | Reduce allocation rate from crammed entities | Spottedleaf | | +| server | Reduce iterator allocation from chunk gen | Spottedleaf | | +| server | Reduce pathfinder branches | Spottedleaf | | | server | Reduce projectile chunk loading | Paul Sauve | | | server | Remove some streams and object allocations | Phoenix616 | | +| server | Remove streams for villager AI | Spottedleaf | | | server | Respect PlayerKickEvent leaveMessage | Ivan Pekov | | +| server | Revert MC-4 fix | Spottedleaf | | +| server | Revert getChunkAt(Async) retaining chunks for long periods of | Spottedleaf | | +| server | Rework PlayerChunk main thread checks | Spottedleaf | | +| server | Rewrite the light engine | Spottedleaf | | +| api | Ridables | William Blake Galbreath | | +| server | Ridables | William Blake Galbreath | | +| server | Separate lookup locking from state access in UserCache | Spottedleaf | | +| server | Set name visible when using a Name Tag on an Armor Stand | jmp | | +| server | Short enderman height | William Blake Galbreath | | | server | Shutdown Bootstrap thread pool | foss-mc | | | server | Signs allow color codes | William Blake Galbreath | | | server | Signs editable on right click | William Blake Galbreath | | +| server | Silk touch spawners | William Blake Galbreath | | | server | Simpler ShapelessRecipes comparison for Vanilla | Paul Sauve | | | server | Skip events if there's no listeners | William Blake Galbreath | | | server | Smarter statistics ticking | Mykyta Komarnytskyy | | | server | Smol entity optimisations | Ivan Pekov | | +| server | Snow Golem rate of fire config | Simon Gardling | | | server | Snowman drop and put back pumpkin | William Blake Galbreath | | +| api | Spigot - Improve output of plugins command | Parker Hawke | | | server | Spread out and optimise player list ticks | James Lyne | | | server | Squid EAR immunity | William Blake Galbreath | | +| server | Stonecutter damage | William Blake Galbreath | | | server | Stop squids floating on top of water | William Blake Galbreath | | | server | Stop wasting resources on JsonList#get | Ivan Pekov | | +| server | Striders give saddle back | Ben Kerllenevich | | | server | Strip raytracing for EntityLiving#hasLineOfSight | Paul Sauve | | | server | Swap priority of checks in chunk ticking | Paul Sauve | | -| server | Swaps the predicate order of collision | ㄗㄠˋ ㄑㄧˊ | | +| server | Time scoreboard search | Spottedleaf | | | server | Timings stuff | William Blake Galbreath | | +| server | Toggle for Zombified Piglin death always counting as player | jmp | | +| server | Totems work in inventory | draycia | | +| api | Tuinity POM Changes | Spottedleaf | | +| server | Tuinity POM Changes | Spottedleaf | | +| server | Tuinity Server Config | Spottedleaf | | +| api | Tuinity config | Spottedleaf | | +| server | Tulips change fox type | William Blake Galbreath | | +| server | Update version fetcher repo | JRoy | | +| server | Use configured height for nether surface builders | William Blake Galbreath | | +| server | Use entity ticking chunk map for entity tracker | Spottedleaf | | | server | Use offline uuids if we need to | Ivan Pekov | | | server | Use unmodifiableMap instead of making copy | Paul Sauve | | +| server | Util patch | Spottedleaf | | | server | Utilities | YatopiaMC | Mykyta Komarnytskyy, Ivan Pekov | -| api | Yatopia API Bundle | YatopiaMC | | +| api | Villager#resetOffers | William Blake Galbreath | | +| server | Villagers farming can bypass mob-griefing gamerule | William Blake Galbreath | | +| server | Villagers follow emerald blocks | William Blake Galbreath | | +| api | Yatopia Config & Redirect Config | YatopiaMC | | | server | Yatopia configuration | tr7zw | | -| server | Yatopia-Server-Fixes | YatopiaMC | | +| server | Zombie break door minimum difficulty option | BillyGalbreath | | +| server | Zombie horse naturally spawn | William Blake Galbreath | | | server | add config for logging login location | Simon Gardling | | | server | dont load chunks for physics | Aikar | | | server | lithium DataTrackerMixin | JellySquid | tr7zw | diff --git a/Paper b/Paper new file mode 160000 index 00000000..8fa15382 --- /dev/null +++ b/Paper @@ -0,0 +1 @@ +Subproject commit 8fa15382bd7229e2688bb4b65c3f5fd823d4e23a diff --git a/Purpur b/Purpur deleted file mode 160000 index cd183358..00000000 --- a/Purpur +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd183358589da89639e0ed9efa0743ac5f1de626 diff --git a/README.md b/README.md index fdb0d9bc..d5eb70a4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## So what is Yatopia? -Yatopia combines the best patches from many [Paper](https://github.com/PaperMC/Paper) forks and optimization mods, as well as many unique optimizations. We borrow some of our patches from the following repos: +Yatopia combines the code from many [Paper](https://github.com/PaperMC/Paper) forks and optimization mods, as well as many unique optimizations. We borrow code from the following repos: * [Akarin](https://github.com/Akarin-project/Akarin) * [EMC](https://github.com/starlis/empirecraft) @@ -25,37 +25,28 @@ ## Try it out ## Documentation -You can find a full explanation of the Yatopia configuration file on the [wiki](https://github.com/YatopiaMC/Yatopia/wiki). Check out the list of patches included in this project and who created them [here](PATCHES.md). You can also find our recommended config base [here](https://github.com/YatopiaMC/Yatopia/wiki/Configurations-Parameters-recommended)! - -## Contributors - -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/0)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/0) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/1)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/1) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/2)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/2) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/3)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/3) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/4)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/4) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/5)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/5) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/6)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/6) -[![](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/images/7)](https://sourcerer.io/fame/budgidiere/YatopiaMC/Yatopia/links/7) - + You can find a full explanation of the Yatopia configuration file on the [wiki](https://github.com/YatopiaMC/Yatopia/wiki). Check out the list of patches included in this project and who created them [here](PATCHES.md). ## Building and setting up Run the following commands in the root directory: ```shell -./yatopia init -./yatopia full +./gradlew initGitSubmodules +./gradlew setupUpstream +./gradlew applyPatches +./gradlew paperclip ``` -If you are repatching you need to delete `Yatopia-API` and `Yatopia-Server` folders. ## Using Yatopia-API To build your plugin against the Yatopia-API, first add the CodeMC maven repository: + +# Maven +Add the CodeMC Repo: ```xml - codemc-repo https://repo.codemc.io/repository/maven-public/ @@ -75,6 +66,42 @@ ## Using Yatopia-API ``` +# Gradle + +> Groovy DSL + +Add the CodeMC Repo: +```groovy +repositories { + maven { + url 'https://repo.codemc.io/repository/maven-public/' + } +} +``` + +And then add the Yatopia-API dependency: +```groovy +dependencies { + compileOnly 'org.yatopiamc:yatopia-api:1.16.5-R0.1-SNAPSHOT' +} +``` + +> Kotlin DSL + +Add the CodeMC Repo: +```kotlin +repositories { + maven("https://repo.codemc.io/repository/maven-public/") +} +``` + +And then add the Yatopia-API dependency: +```kotlin +dependencies { + compileOnly("org.yatopiamc:yatopia-api:1.16.5-R0.1-SNAPSHOT") +} +``` + ## Why aren't there many API additions? (Modified from [starlis/empirecraft](https://github.com/starlis/empirecraft/)) @@ -90,11 +117,11 @@ ## Why aren't there many API additions? ## License -License information can be found [here](https://github.com/YatopiaMC/Yatopia/blob/ver/1.16.4/Licensing/LICENSE.md). +License information can be found [here](../Licensing/LICENSE.md). ## Security -Security information can be found found [here](https://github.com/YatopiaMC/Yatopia/blob/ver/1.16.4/SECURITY.md). +Security information can be found found [here](../SECURITY.md). ## Statistics [![bStats Graph Data](https://bstats.org/signatures/server-implementation/Yatopia.svg)](https://bstats.org/plugin/server-implementation/Yatopia) diff --git a/Tuinity b/Tuinity deleted file mode 160000 index 4bcbfd71..00000000 --- a/Tuinity +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4bcbfd717e39f8c48028c44aaa75d0d40c0f1c32 diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..0dcbb786 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,60 @@ +plugins { + `java-library` + `maven-publish` + toothpick +} + +toothpick { + forkName = "Yatopia" + groupId = "org.yatopiamc" + val versionTag = System.getenv("BUILD_NUMBER") + ?: "\"${gitCmd("rev-parse", "--short", "HEAD").output}\"" + forkVersion = "git-$forkName-$versionTag" + forkUrl = "https://github.com/YatopiaMC/Yatopia" + + minecraftVersion = "1.16.5" + nmsPackage = "1_16_R3" + nmsRevision = "R0.1-SNAPSHOT" + + upstream = "Paper" + upstreamBranch = "origin/master" + + paperclipName = "yatopia-$minecraftVersion-paperclip.jar" + + patchCreditsOutput = "PATCHES.md" + patchCreditsTemplate = ".template.md" + + server { + project = project(":$forkNameLowercase-server") + patchesDir = rootProject.projectDir.resolve("patches/server") + } + api { + project = project(":$forkNameLowercase-api") + patchesDir = rootProject.projectDir.resolve("patches/api") + } +} + +subprojects { + repositories { + mavenCentral() + maven("https://repo.aikar.co/content/groups/aikar/") + maven("https://nexus.velocitypowered.com/repository/velocity-artifacts-snapshots/") + maven("https://libraries.minecraft.net") + maven("https://repo.codemc.io/repository/maven-public/") + mavenLocal() + } + + java { + if(JavaVersion.VERSION_1_8 > JavaVersion.current()){ + error("This build must be run with Java 8 or better") + } + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.current() + withSourcesJar() + } + + publishing.repositories.maven { + url = uri("https://repo.codemc.io/repository/maven-snapshots/") + credentials(PasswordCredentials::class) + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..891742b3 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,30 @@ +val kotlinxDomVersion = "0.0.10" +val shadowVersion = "6.1.0" +val mustacheVersion = "0.9.6" +val javaxMailVersion = "1.4.4" + +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + jcenter() + maven("https://plugins.gradle.org/m2/") +} + +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx.dom:$kotlinxDomVersion") + implementation("com.github.jengelman.gradle.plugins:shadow:$shadowVersion") + implementation("com.github.spullara.mustache.java:compiler:$mustacheVersion") + implementation("javax.mail:mail:$javaxMailVersion") +} + +gradlePlugin { + plugins { + register("Toothpick") { + id = "toothpick" + implementationClass = "Toothpick" + } + } +} diff --git a/buildSrc/license.txt b/buildSrc/license.txt new file mode 100644 index 00000000..ffe469d9 --- /dev/null +++ b/buildSrc/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2021 Jason Penilla & Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/buildSrc/src/main/kotlin/ConfigureSubprojects.kt b/buildSrc/src/main/kotlin/ConfigureSubprojects.kt new file mode 100644 index 00000000..e5ef2a1c --- /dev/null +++ b/buildSrc/src/main/kotlin/ConfigureSubprojects.kt @@ -0,0 +1,155 @@ +import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer +import kotlinx.dom.elements +import kotlinx.dom.parseXml +import kotlinx.dom.search +import org.gradle.api.Project +import org.gradle.api.plugins.JavaLibraryPlugin +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin +import org.gradle.api.publish.maven.tasks.GenerateMavenPom +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.* +import java.nio.charset.StandardCharsets.UTF_8 +import java.text.SimpleDateFormat +import java.util.* + +internal fun Project.configureSubprojects() { + subprojects { + apply() + apply() + + tasks.withType { + options.encoding = UTF_8.name() + } + tasks.withType { + options.encoding = UTF_8.name() + } + + extensions.configure { + publications { + create("mavenJava") { + groupId = rootProject.group as String + version = rootProject.version as String + pom { + name.set(project.name) + url.set(toothpick.forkUrl) + } + } + } + } + + when { + project.name.endsWith("server") -> configureServerProject() + project.name.endsWith("api") -> configureApiProject() + } + } +} + +private fun Project.configureServerProject() { + apply() + + val generatePomFileForMavenJavaPublication by tasks.getting(GenerateMavenPom::class) { + destination = project.buildDir.resolve("tmp/pom.xml") + } + + @Suppress("UNUSED_VARIABLE") + val test by tasks.getting(Test::class) { + // didn't bother to look into why these fail. paper excludes them in paperweight as well though + exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class") + } + + val shadowJar by tasks.getting(ShadowJar::class) { + archiveClassifier.set("") // ShadowJar is the main server artifact + dependsOn(generatePomFileForMavenJavaPublication) + transform(Log4j2PluginsCacheFileTransformer::class.java) + mergeServiceFiles() + manifest { + attributes( + "Main-Class" to "org.bukkit.craftbukkit.Main", + "Implementation-Title" to "CraftBukkit", + "Implementation-Version" to toothpick.forkVersion, + "Implementation-Vendor" to SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(Date()), + "Specification-Title" to "Bukkit", + "Specification-Version" to "${project.version}", + "Specification-Vendor" to "Bukkit Team" + ) + } + from(project.buildDir.resolve("tmp/pom.xml")) { + // dirty hack to make "java -Dpaperclip.install=true -jar paperclip.jar" work without forking paperclip + into("META-INF/maven/io.papermc.paper/paper") + } + + // Don't like to do this but sadly have to do this for compatibility reasons + relocate("org.bukkit.craftbukkit", "org.bukkit.craftbukkit.v${toothpick.nmsPackage}") { + exclude("org.bukkit.craftbukkit.Main*") + } + relocate("net.minecraft.server", "net.minecraft.server.v${toothpick.nmsPackage}") + + // Make sure we relocate deps the same as Paper et al. + val pomFile = project.projectDir.resolve("pom.xml") + if (!pomFile.exists()) return@getting + val dom = parseXml(pomFile) + val buildSection = dom.search("build").first() + val plugins = buildSection.search("plugins").first() + plugins.elements("plugin").filter { + val artifactId = it.search("artifactId").first().textContent + artifactId == "maven-shade-plugin" + }.forEach { + it.search("executions").first() + .search("execution").first() + .search("configuration").first() + .search("relocations").first() + .elements("relocation").forEach { relocation -> + val pattern = relocation.search("pattern").first().textContent + val shadedPattern = relocation.search("shadedPattern").first().textContent + if (pattern != "org.bukkit.craftbukkit" && pattern != "net.minecraft.server") { // We handle these ourselves above + logger.debug("Imported relocation to server project shadowJar from ${pomFile.absolutePath}: $pattern to $shadedPattern") + relocate(pattern, shadedPattern) + } + } + } + } + tasks.getByName("build") { + dependsOn(shadowJar) + } + + extensions.configure { + publications { + getByName("mavenJava") { + artifactId = rootProject.name + artifact(tasks["shadowJar"]) + } + } + } +} + +@Suppress("UNUSED_VARIABLE") +private fun Project.configureApiProject() { + val jar by this.tasks.getting(Jar::class) { + doFirst { + buildDir.resolve("tmp/pom.properties") + .writeText("version=${project.version}") + } + from(buildDir.resolve("tmp/pom.properties")) { + into("META-INF/maven/${project.group}/${project.name}") + } + manifest { + attributes("Automatic-Module-Name" to "org.bukkit") + } + } + + extensions.configure { + publications { + getByName("mavenJava") { + artifactId = project.name + from(components["java"]) + } + } + } +} diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt new file mode 100644 index 00000000..e579b850 --- /dev/null +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -0,0 +1,2 @@ +const val taskGroup = "toothpick" +const val internalTaskGroup = "toothpick_internal" diff --git a/buildSrc/src/main/kotlin/DependencyLoading.kt b/buildSrc/src/main/kotlin/DependencyLoading.kt new file mode 100644 index 00000000..ff3b9f15 --- /dev/null +++ b/buildSrc/src/main/kotlin/DependencyLoading.kt @@ -0,0 +1,64 @@ +import kotlinx.dom.elements +import kotlinx.dom.parseXml +import kotlinx.dom.search +import org.gradle.api.Project +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.kotlin.dsl.DependencyHandlerScope +import org.gradle.kotlin.dsl.maven +import org.gradle.kotlin.dsl.project + +fun RepositoryHandler.loadRepositories(project: Project) { + val pomFile = project.projectDir.resolve("pom.xml") + if (!pomFile.exists()) return + val dom = parseXml(pomFile) + val repositoriesBlock = dom.search("repositories").firstOrNull() ?: return + + // Load repositories + repositoriesBlock.elements("repository").forEach { repositoryElem -> + val url = repositoryElem.search("url").firstOrNull()?.textContent ?: return@forEach + maven(url) + } +} + +fun DependencyHandlerScope.loadDependencies(project: Project) { + val pomFile = project.projectDir.resolve("pom.xml") + if (!pomFile.exists()) return + val dom = parseXml(pomFile) + + val dependenciesBlock = dom.search("dependencies").firstOrNull() ?: return + + // Load dependencies + dependenciesBlock.elements("dependency").forEach { dependencyElem -> + val groupId = dependencyElem.search("groupId").first().textContent + val artifactId = dependencyElem.search("artifactId").first().textContent + val version = dependencyElem.search("version").first().textContent.applyReplacements( + "project.version" to project.version.toString(), + "minecraft.version" to project.toothpick.minecraftVersion + ) + val scope = dependencyElem.search("scope").firstOrNull()?.textContent + val classifier = dependencyElem.search("classifier").firstOrNull()?.textContent + + val dependencyString = "${groupId}:${artifactId}:${version}${classifier?.run { ":$this" } ?: ""}" + project.logger.debug("Read dependency '{}' from '{}'", dependencyString, pomFile.absolutePath) + + // Special case API + if (artifactId == "${project.toothpick.forkNameLowercase}-api" + || artifactId == "${project.toothpick.upstreamLowercase}-api" + ) { + if (project.name.endsWith("-server")) { + add("api", project(":${project.toothpick.forkNameLowercase}-api")) + } + return@forEach + } + + when (scope) { + "compile", null -> add("api", dependencyString) + "provided" -> { + add("compileOnly", dependencyString) + add("testImplementation", dependencyString) + } + "runtime" -> add("runtimeOnly", dependencyString) + "test" -> add("testImplementation", dependencyString) + } + } +} diff --git a/buildSrc/src/main/kotlin/InitTasks.kt b/buildSrc/src/main/kotlin/InitTasks.kt new file mode 100644 index 00000000..bbca6fac --- /dev/null +++ b/buildSrc/src/main/kotlin/InitTasks.kt @@ -0,0 +1,70 @@ +import org.gradle.api.Project +import task.* + +@Suppress("UNUSED_VARIABLE") +internal fun Project.initToothpickTasks() { + if (project.hasProperty("fast")) { + gradle.taskGraph.whenReady { + gradle.taskGraph.allTasks.filter { + it.name.contains("test", ignoreCase = true) || it.name.contains("javadoc", ignoreCase = true) + }.forEach { + it.onlyIf { false } + } + } + } + + tasks.getByName("build") { + doFirst { + val readyToBuild = + upstreamDir.resolve(".git").exists() + && toothpick.subprojects.values.all { it.projectDir.exists() && it.baseDir.exists() } + if (!readyToBuild) { + error("Workspace has not been setup. Try running `./gradlew applyPatches` first") + } + } + } + + val initGitSubmodules = createInitGitSubmodulesTask() + + val setupUpstream = createSetupUpstreamTask { + dependsOn(initGitSubmodules) + } + + val importMCDev = createImportMCDevTask { + mustRunAfter(setupUpstream) + } + + val paperclip = createPaperclipTask { + val shadowJar = toothpick.serverProject.project.tasks.getByName("shadowJar") + dependsOn(shadowJar) + inputs.file(shadowJar.outputs.files.singleFile) + } + + val applyPatches = createApplyPatchesTask { + // If Paper has not been setup yet or if we modified the submodule (i.e. upstream update), patch + if (!lastUpstream.exists() + || !upstreamDir.resolve(".git").exists() + || lastUpstream.readText() != gitHash(upstreamDir) + ) { + dependsOn(setupUpstream) + } + mustRunAfter(setupUpstream) + dependsOn(importMCDev) + } + + val patchCredits = createPatchCreditsTask() + + val fixBranch = createFixBranchesTask() + + val rebuildPatches = createRebuildPatchesTask { + dependsOn(fixBranch) + finalizedBy(patchCredits) + } + + val updateUpstream = createUpdateUpstreamTask { + finalizedBy(setupUpstream) + } + + val upstreamCommit = createUpstreamCommitTask() + +} diff --git a/buildSrc/src/main/kotlin/MCDevImports.kt b/buildSrc/src/main/kotlin/MCDevImports.kt new file mode 100644 index 00000000..d4be9416 --- /dev/null +++ b/buildSrc/src/main/kotlin/MCDevImports.kt @@ -0,0 +1,28 @@ +/** + * This is the set of extra NMS files which will be imported as part of the patch process + * + * See `./Paper/work/Minecraft/$MCVER/net/minecraft/server` for a list of possible files + * + * The `.java` extension is always assumed and should be excluded + * + * NOTE: Do not commit changes to this set! Instead make changes, rebuild patches, and commit the modified patches. + * Files already modified in existing patches will be imported automatically. + */ +val nmsImports = setOf( + // ex: + //"EntityZombieVillager" +) + +data class LibraryImport(val group: String, val library: String, val prefix: String, val file: String) + +/** + * This is the set of extra files to import into the server workspace from libraries + * + * Changes to this set should be committed to the repo, as these won't be automatically imported. + */ +val libraryImports = setOf( + LibraryImport("com.mojang", "brigadier", "com/mojang/brigadier", "CommandDispatcher"), + LibraryImport("com.mojang", "brigadier", "com/mojang/brigadier/tree", "LiteralCommandNode"), + LibraryImport("com.mojang", "brigadier", "com/mojang/brigadier/suggestion", "SuggestionsBuilder"), + LibraryImport("com.mojang", "brigadier", "com/mojang/brigadier/arguments", "BoolArgumentType") +) diff --git a/buildSrc/src/main/kotlin/PatchParser.kt b/buildSrc/src/main/kotlin/PatchParser.kt new file mode 100644 index 00000000..9e33de97 --- /dev/null +++ b/buildSrc/src/main/kotlin/PatchParser.kt @@ -0,0 +1,77 @@ +import java.io.File +import java.io.IOException +import java.io.UnsupportedEncodingException +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.util.* +import java.util.function.Function +import javax.mail.internet.MimeUtility + +/** + * Rudimentary parser to get Author, subject and coAuthors of a patch file + * + * @author tr7zw + */ +object PatchParser { + fun parsePatch(file: File): PatchInfo { + val lines: List = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8) + var from: String = "Unknown" + var subject: String = "Unknown" + val coAuthors: MutableList = ArrayList() + for (line: String in lines) { + when { + line.startsWith("From: ") -> { + from = + decodeStringIfNeeded(line.replace("From: ", "").split("<").toTypedArray()[0].trim { it <= ' ' }) + } + line.startsWith("Subject: ") -> { + subject = line.replace("Subject: ", "").replace("[PATCH]", "").trim { it <= ' ' } + } + line.startsWith("Co-authored-by: ") -> { + coAuthors.add( + decodeStringIfNeeded( + line.replace("Co-authored-by: ", "").split("<").toTypedArray()[0].trim { it <= ' ' }) + ) + } + } + } + return PatchInfo(file.parentFile.name, from, subject, coAuthors) + } + + private fun decodeStringIfNeeded(org: String): String { + if (org.contains("=") || org.startsWith("=?UTF-8")) { + try { + return MimeUtility.decodeText(org) + } catch (ex: UnsupportedEncodingException) { + throw IOException(ex) + } + } + return org + } + + class PatchInfo(val parent: String, val from: String, val subject: String, val coAuthors: List) { + val coAuthorString: Function + get() = Function { + java.lang.String.join( + ", ", + coAuthors + ) + } + + override fun toString(): String { + return ("PatchInfo{" + + "parent='" + + parent + + '\'' + + ", from='" + + from + + '\'' + + ", subject='" + + subject + + '\'' + + ", coAuthors=" + + coAuthors + + '}') + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Toothpick.kt b/buildSrc/src/main/kotlin/Toothpick.kt new file mode 100644 index 00000000..c5829962 --- /dev/null +++ b/buildSrc/src/main/kotlin/Toothpick.kt @@ -0,0 +1,9 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.create + +class Toothpick : Plugin { + override fun apply(project: Project) { + project.extensions.create("toothpick", project.objects) + } +} diff --git a/buildSrc/src/main/kotlin/ToothpickExtension.kt b/buildSrc/src/main/kotlin/ToothpickExtension.kt new file mode 100644 index 00000000..fd314511 --- /dev/null +++ b/buildSrc/src/main/kotlin/ToothpickExtension.kt @@ -0,0 +1,84 @@ +import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory +import java.io.File +import java.io.FileInputStream +import java.util.* +import java.util.stream.Collectors +import kotlin.collections.ArrayList + +@Suppress("UNUSED_PARAMETER") +open class ToothpickExtension(objects: ObjectFactory) { + lateinit var project: Project + lateinit var forkName: String + val forkNameLowercase + get() = forkName.toLowerCase(Locale.ENGLISH) + lateinit var forkUrl: String + lateinit var forkVersion: String + lateinit var groupId: String + lateinit var minecraftVersion: String + lateinit var nmsRevision: String + lateinit var nmsPackage: String + + lateinit var upstream: String + val upstreamLowercase + get() = upstream.toLowerCase(Locale.ENGLISH) + var upstreamBranch: String = "origin/master" + + var paperclipName: String? = null + val calcPaperclipName + get() = paperclipName ?: "${forkNameLowercase}-paperclip.jar" + + lateinit var serverProject: ToothpickSubproject + + lateinit var patchCreditsOutput: String + lateinit var patchCreditsTemplate: String + + fun server(receiver: ToothpickSubproject.() -> Unit) { + serverProject = ToothpickSubproject() + receiver(serverProject) + } + + lateinit var apiProject: ToothpickSubproject + fun api(receiver: ToothpickSubproject.() -> Unit) { + apiProject = ToothpickSubproject() + receiver(apiProject) + } + + val subprojects: Map + get() = if (::forkName.isInitialized) mapOf( + "$forkName-API" to apiProject, + "$forkName-Server" to serverProject + ) else emptyMap() + + val paperDir: File by lazy { + if (upstream == "Paper") { + project.upstreamDir + } else { + project.upstreamDir.walk().find { + it.name == "Paper" && it.isDirectory + && it.resolve("work/Minecraft/${minecraftVersion}").exists() + } ?: error("Failed to find Paper directory!") + } + } + + val paperWorkDir: File + get() = paperDir.resolve("work/Minecraft/${minecraftVersion}") + + fun getUpstreams(rootProjectDir: File): MutableList? { + val configDir = rootProjectDir.resolve("$rootProjectDir/upstreamConfig") + val upstreams = configDir.listFiles() + val upstreamArray = ArrayList() + val prop = Properties() + for (upstream in upstreams) { + prop.load(FileInputStream(upstream)) + upstreamArray.add(Upstream(prop.getProperty("name"), + prop.getProperty("useBlackList")!!.toBoolean(), + (prop.getProperty("list")), + rootProjectDir, + prop.getProperty("branch"), + Integer.parseInt(upstream.name.substring(0,4)), + project)) + } + return upstreamArray.stream().sorted { upstream1, upstream2 -> upstream1.id - upstream2.id}.collect(Collectors.toList()) + } +} diff --git a/buildSrc/src/main/kotlin/ToothpickExtensions.kt b/buildSrc/src/main/kotlin/ToothpickExtensions.kt new file mode 100644 index 00000000..a4a02800 --- /dev/null +++ b/buildSrc/src/main/kotlin/ToothpickExtensions.kt @@ -0,0 +1,41 @@ +import org.gradle.api.Project +import org.gradle.kotlin.dsl.findByType +import java.io.File + +val Project.toothpick: ToothpickExtension + get() = rootProject.extensions.findByType(ToothpickExtension::class)!! + +fun Project.toothpick(receiver: ToothpickExtension.() -> Unit) { + toothpick.project = this + receiver(toothpick) + allprojects { + group = toothpick.groupId + version = "${toothpick.minecraftVersion}-${toothpick.nmsRevision}" + } + configureSubprojects() + initToothpickTasks() +} + +val Project.lastUpstream: File + get() = rootProject.projectDir.resolve("last-${toothpick.upstreamLowercase}") + +val Project.rootProjectDir: File + get() = rootProject.projectDir + +val Project.upstreamDir: File + get() = rootProject.projectDir.resolve(toothpick.upstream) + +val Project.upstream: String + get() = toothpick.upstream + +val Project.upstreams: MutableList + get() = toothpick.getUpstreams(rootProject.projectDir) as MutableList + +val Project.forkName: String + get() = toothpick.forkName + +val Project.patchCreditsOutput: String + get() = toothpick.patchCreditsOutput + +val Project.patchCreditsTemplate: String + get() = toothpick.patchCreditsTemplate diff --git a/buildSrc/src/main/kotlin/ToothpickSubproject.kt b/buildSrc/src/main/kotlin/ToothpickSubproject.kt new file mode 100644 index 00000000..df34f33e --- /dev/null +++ b/buildSrc/src/main/kotlin/ToothpickSubproject.kt @@ -0,0 +1,20 @@ +import org.gradle.api.Project +import java.io.File + +class ToothpickSubproject { + lateinit var project: Project + + val baseDir: File by lazy { + val name = project.name + val upstream = project.toothpick.upstream + val suffix = if (name.endsWith("server")) "Server" else "API" + project.upstreamDir.resolve("$upstream-$suffix") + } + val projectDir: File + get() = project.projectDir + lateinit var patchesDir: File + + operator fun component1(): File = baseDir + operator fun component2(): File = projectDir + operator fun component3(): File = patchesDir +} diff --git a/buildSrc/src/main/kotlin/Upstream.kt b/buildSrc/src/main/kotlin/Upstream.kt new file mode 100644 index 00000000..e5857b13 --- /dev/null +++ b/buildSrc/src/main/kotlin/Upstream.kt @@ -0,0 +1,88 @@ +import org.gradle.api.Project +import java.io.File +import java.io.FileWriter +import java.nio.file.Files +import java.nio.file.Paths +import java.util.stream.Collectors + +open class Upstream(in_name: String, in_useBlackList: Boolean, in_list: String, in_rootProjectDir: File, in_branch: String, in_id: Int, in_project: Project) { + var name: String = in_name + var useBlackList: Boolean = in_useBlackList + private var list: ArrayList = ArrayList(in_list.split(",".toRegex())) + private var rootProjectDir: File = in_rootProjectDir + var branch = in_branch + var id = in_id + + var serverList = list.stream().filter { patch -> patch.startsWith("server/") } + ?.sorted()?.map { patch -> patch.substring(7, patch.length) }?.collect(Collectors.toList()) + var apiList = list.stream().filter { patch -> patch.startsWith("API/") } + ?.sorted()?.map { patch -> patch.substring(4, patch.length) }?.collect(Collectors.toList()) + + + var patchPath = Paths.get("$rootProjectDir/patches/$name/patches") + var repoPath = Paths.get("$rootProjectDir/upstream/$name") + + var project = in_project + + var upstreamCommit = getUpstreamCommitHash() + + private fun getUpstreamCommitHash(): String { + val commitFileFolder = Paths.get("$rootProjectDir/upstreamCommits") + val commitFilePath = Paths.get("$commitFileFolder/$name") + val commitFile = commitFilePath.toFile() + var commitHash: String + if (commitFile.isFile) { + commitHash = Files.readAllLines(commitFilePath).toString() + commitHash = commitHash.substring(1, commitHash.length - 1) + if (commitHash == "") { + commitHash = updateHashFile(commitFile) + } + } else { + Files.createFile(commitFilePath) + commitHash = updateHashFile(commitFile) + } + return commitHash; + } + + public fun updateUpstreamCommitHash() { + val commitFileFoler = Paths.get("$rootProjectDir/upstreamCommits") + val commitFilePath = Paths.get("$commitFileFoler/$name") + val commitFile = commitFilePath.toFile() + updateHashFile(commitFile) + upstreamCommit = getUpstreamCommitHash() + } + + public fun getCurrentCommitHash(): String { + return project.getCommitHash() + } + + private fun updateHashFile(commitFile: File): String { + var commitHash: String + commitHash = project.getCommitHash() + val fileWriter = FileWriter(commitFile) + fileWriter.use { out -> out.write(commitHash) } + fileWriter.close() + return commitHash + } + + private fun Project.getCommitHash(): String = gitHash(repo = repoPath.toFile()) + + public fun getRepoServerPatches(): MutableList? { + return getRepoPatches(rootProjectDir.resolve("$repoPath/patches/server")).stream() + .sorted().map {patch -> patch.substring(5, patch.length) }.collect(Collectors.toList()) + } + + public fun getRepoAPIPatches(): MutableList? { + return getRepoPatches(rootProjectDir.resolve("$repoPath/patches/api")).stream() + .sorted().map {patch -> patch.substring(5, patch.length) }.collect(Collectors.toList()) + } + + private fun getRepoPatches(path: File): ArrayList { + val files = path.listFiles() + val filesList = ArrayList() + for (patch in files) { + filesList.add(patch.name) + } + return filesList + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Util.kt b/buildSrc/src/main/kotlin/Util.kt new file mode 100644 index 00000000..77ac15cd --- /dev/null +++ b/buildSrc/src/main/kotlin/Util.kt @@ -0,0 +1,88 @@ +import org.gradle.api.Project +import java.io.File +import java.util.* +import kotlin.streams.asSequence + +data class CmdResult(val exitCode: Int, val output: String?) + +fun Project.cmd( + vararg args: String, + dir: File = rootProject.projectDir, + printOut: Boolean = false +): CmdResult { + val process = ProcessBuilder() + .command(*args) + .redirectErrorStream(true) + .directory(dir) + .start() + val output = process.inputStream.bufferedReader().use { reader -> + reader.lines().asSequence() + .onEach { + if (printOut) { + logger.lifecycle(it) + } else { + logger.debug(it) + } + } + .toCollection(LinkedList()) + .joinToString(separator = "\n") + } + val exit = process.waitFor() + return CmdResult(exit, output) +} + +fun ensureSuccess( + cmd: CmdResult, + errorHandler: CmdResult.() -> Unit = {} +): String? { + val (exit, output) = cmd + if (exit != 0) { + errorHandler(cmd) + error("Failed to run command, exit code is $exit") + } + return output +} + +fun Project.gitCmd( + vararg args: String, + dir: File = rootProject.projectDir, + printOut: Boolean = false +): CmdResult = + cmd("git", *args, dir = dir, printOut = printOut) + +fun Project.bashCmd( + vararg args: String, + dir: File = rootProject.projectDir, + printOut: Boolean = false +): CmdResult = + cmd("bash", "-c", *args, dir = dir, printOut = printOut) + +internal fun String.applyReplacements( + vararg replacements: Pair +): String { + var result = this + for ((key, value) in replacements) { + result = result.replace("\${$key}", value) + } + return result +} + +private fun Project.gitSigningEnabled(repo: File): Boolean = + gitCmd("config", "commit.gpgsign", dir = repo).output?.toBoolean() == true + +internal fun Project.temporarilyDisableGitSigning(repo: File): Boolean { + val isCurrentlyEnabled = gitSigningEnabled(repo) + if (isCurrentlyEnabled) { + gitCmd("config", "commit.gpgsign", "false", dir = repo) + } + return isCurrentlyEnabled +} + +internal fun Project.reEnableGitSigning(repo: File) { + gitCmd("config", "commit.gpgsign", "true", dir = repo) +} + +fun Project.gitHash(repo: File): String = + gitCmd("rev-parse", "HEAD", dir = repo).output ?: "" + +val jenkins = System.getenv("JOB_NAME") != null diff --git a/buildSrc/src/main/kotlin/task/ApplyPatches.kt b/buildSrc/src/main/kotlin/task/ApplyPatches.kt new file mode 100644 index 00000000..e73458d2 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/ApplyPatches.kt @@ -0,0 +1,98 @@ +package task + +import ensureSuccess +import forkName +import gitCmd +import org.gradle.api.Project +import org.gradle.api.Task +import reEnableGitSigning +import taskGroup +import temporarilyDisableGitSigning +import toothpick +import upstreams +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + +internal fun Project.createApplyPatchesTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("applyPatches") { + receiver(this) + group = taskGroup + + fun checkCursed(project: Project): Boolean { + return project.properties.getOrDefault("cursed", "false").toString().toBoolean() + } + + fun applyPatches(patchDir: Path, applyName: String, name: String, wasGitSigningEnabled: Boolean, projectDir: File): Boolean { + val patchPaths = Files.newDirectoryStream(patchDir) + .map { it.toFile() } + .filter { it.name.endsWith(".patch") } + .sorted() + .takeIf { it.isNotEmpty() } ?: return true + val patches = patchPaths.map { it.absolutePath }.toTypedArray() + + logger.lifecycle(">>> Applying $applyName patches to $name") + + gitCmd("am", "--abort") + + //Cursed Apply Mode that makes fixing stuff a lot easier + if (checkCursed(project)) { + for (patch in patches) { + val gitCommand = arrayListOf("am", "--3way", "--ignore-whitespace", + "--rerere-autoupdate", "--whitespace=fix", "--reject", "-C0", patch) + if (gitCmd(*gitCommand.toTypedArray(), dir = projectDir, printOut = true).exitCode != 0) { + gitCmd("add", ".", dir = projectDir, printOut = true) + gitCmd("am", "--continue", dir = projectDir, printOut = true) + } + } + } else { + val gitCommand = arrayListOf("am", "--3way", "--ignore-whitespace", + "--rerere-autoupdate", "--whitespace=fix", *patches) + ensureSuccess(gitCmd(*gitCommand.toTypedArray(), dir = projectDir, printOut = true)) { + if (wasGitSigningEnabled) reEnableGitSigning(projectDir) + } + } + return false; + } + + doLast { + for ((name, subproject) in toothpick.subprojects) { + val (sourceRepo, projectDir, patchesDir) = subproject + + val folder = (if (patchesDir.endsWith("server")) "server" else "api") + + // Reset or initialize subproject + logger.lifecycle(">>> Resetting subproject $name") + if (projectDir.exists()) { + ensureSuccess(gitCmd("fetch", "origin", dir = projectDir)) + ensureSuccess(gitCmd("reset", "--hard", "origin/master", dir = projectDir)) + } else { + ensureSuccess(gitCmd("clone", sourceRepo.absolutePath, projectDir.absolutePath, printOut = true)) + } + logger.lifecycle(">>> Done resetting subproject $name") + + val wasGitSigningEnabled = temporarilyDisableGitSigning(projectDir) + + for (upstream in upstreams) { + if (((folder == "server" && upstream.serverList?.isEmpty() != false) || (folder == "api" && upstream.apiList?.isEmpty() != false)) && !upstream.useBlackList) continue + if (((folder == "server" && upstream.getRepoServerPatches()?.isEmpty() != false) || (folder == "api" && upstream.getRepoAPIPatches()?.isEmpty() != false)) && upstream.useBlackList) continue + project.gitCmd("branch", "-D", "${upstream.name}-$folder", dir = projectDir) + project.gitCmd("checkout", "-b", "${upstream.name}-$folder", dir = projectDir) + // Apply patches + val patchDir = Paths.get("${upstream.patchPath}/$folder") + + if (applyPatches(patchDir, upstream.name, name, wasGitSigningEnabled, projectDir)) continue + } + project.gitCmd("branch", "-D", "$forkName-$folder", dir = projectDir) + project.gitCmd("checkout", "-b", "$forkName-$folder", dir = projectDir) + val patchDir = patchesDir.toPath() + // Apply patches + if (applyPatches(patchDir, forkName, name, wasGitSigningEnabled, projectDir)) continue + + if (wasGitSigningEnabled) reEnableGitSigning(projectDir) + logger.lifecycle(">>> Done applying patches to $name") + } + } +} diff --git a/buildSrc/src/main/kotlin/task/FixBranches.kt b/buildSrc/src/main/kotlin/task/FixBranches.kt new file mode 100644 index 00000000..5275f353 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/FixBranches.kt @@ -0,0 +1,53 @@ +package task + +import org.gradle.api.Project +import org.gradle.api.Task +import taskGroup +import upstreams +import gitCmd +import toothpick +import java.nio.file.Paths +import java.util.concurrent.ConcurrentHashMap +import ensureSuccess + +internal fun Project.createFixBranchesTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("fixBranches") { + receiver(this) + group = taskGroup + val folderArray = arrayListOf("api", "server") + doLast { + for (folder in folderArray) { + val subprojectWorkDir = Paths.get("${toothpick.forkName}-${if (folder == "api") {"API"} else {"Server"}}").toFile() + val currentBranchCommits = gitCmd("--no-pager", "log", "${toothpick.forkName}-$folder...${toothpick.upstreamBranch}", "--pretty=oneline", + dir = subprojectWorkDir).output.toString() + val nameMap = ConcurrentHashMap() + for (upstream in upstreams) { + val patchPath = Paths.get("${upstream.patchPath}/$folder").toFile() + if (patchPath.listFiles()?.isEmpty() != false) continue + val commitName = gitCmd("--no-pager", "log", "${upstream.name}-$folder", "-1", "--format=\"%s\"", + dir = subprojectWorkDir).output.toString() + val branchName = "${upstream.name}-$folder" + val commitNameFiltered = commitName.substring(1, commitName.length-1) + for (line in currentBranchCommits.split("\\n".toRegex()).stream().parallel()) { + val commitNameIterator = line.substring(41, line.length) + if (commitNameIterator == commitNameFiltered) { + val hash = line.substring(0, 40) + nameMap.put(branchName, hash) + continue + } + } + } + for (upstream in upstreams) { + val patchPath = Paths.get("${upstream.patchPath}/$folder").toFile() + if (patchPath.listFiles()?.isEmpty() != false) continue + val branchName = "${upstream.name}-$folder" + ensureSuccess(gitCmd("checkout", branchName, dir = subprojectWorkDir, printOut = true)) + ensureSuccess(gitCmd("reset", "--hard", nameMap.get(branchName) as String, dir = subprojectWorkDir, + printOut = true)) + } + ensureSuccess(gitCmd("checkout", "${toothpick.forkName}-$folder", dir = subprojectWorkDir, + printOut = true)) + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/task/ImportMCDev.kt b/buildSrc/src/main/kotlin/task/ImportMCDev.kt new file mode 100644 index 00000000..3f4a1a59 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/ImportMCDev.kt @@ -0,0 +1,96 @@ +package task + +import LibraryImport +import ensureSuccess +import gitCmd +import internalTaskGroup +import libraryImports +import nmsImports +import org.gradle.api.Project +import org.gradle.api.Task +import toothpick +import upstreams +import java.io.File + +internal fun Project.createImportMCDevTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("importMCDev") { + receiver(this) + group = internalTaskGroup + val upstreamServer = toothpick.serverProject.baseDir + val importLog = arrayListOf("Extra mc-dev imports") + + fun importNMS(className: String) { + logger.lifecycle("Importing n.m.s.$className") + importLog.add("Imported n.m.s.$className") + val source = toothpick.paperWorkDir.resolve("spigot/net/minecraft/server/$className.java") + if (!source.exists()) error("Missing NMS: $className") + val target = upstreamServer.resolve("src/main/java/net/minecraft/server/$className.java") + source.copyTo(target) + } + + fun importLibrary(import: LibraryImport) { + val (group, lib, prefix, file) = import + logger.lifecycle("Importing $group.$lib $prefix/$file") + importLog.add("Imported $group.$lib $prefix/$file") + val source = toothpick.paperWorkDir.resolve("libraries/$group/$lib/$prefix/$file.java") + if (!source.exists()) error("Missing Base: $lib $prefix/$file") + val targetDir = upstreamServer.resolve("src/main/java/$prefix") + val target = targetDir.resolve("$file.java") + targetDir.mkdirs() + source.copyTo(target) + } + + fun getAndApplyNMS(patchesDir: File) { + (patchesDir.listFiles() ?: return).asSequence() + .flatMap { it.readLines().asSequence() } + .filter { it.startsWith("+++ b/src/main/java/net/minecraft/server/") } + .distinct() + .map { it.substringAfter("/server/").substringBefore(".java") } + .filter { !upstreamServer.resolve("src/main/java/net/minecraft/server/$it.java").exists() } + .map { toothpick.paperWorkDir.resolve("spigot/net/minecraft/server/$it.java") } + .filter { + val exists = it.exists() + if (!it.exists()) logger.lifecycle("NMS ${it.nameWithoutExtension} is either missing, or is a new file added through a patch") + exists + } + .map { it.nameWithoutExtension } + .forEach(::importNMS) + } + + doLast { + logger.lifecycle(">>> Importing mc-dev") + val lastCommitIsMCDev = gitCmd( + "log", "-1", "--oneline", + dir = upstreamServer + ).output?.contains("Extra mc-dev imports") == true + if (lastCommitIsMCDev) { + ensureSuccess( + gitCmd( + "reset", "--hard", "HEAD~1", + dir = upstreamServer, + printOut = true + ) + ) + } + for (upstream in upstreams) { + val patchesDir = rootProject.projectDir.resolve("${upstream.patchPath}/server") + getAndApplyNMS(patchesDir) + } + + val patchesDir = toothpick.serverProject.patchesDir + getAndApplyNMS(patchesDir) + + + // Imports from MCDevImports.kt + nmsImports.forEach(::importNMS) + libraryImports.forEach(::importLibrary) + + val add = gitCmd("add", ".", "-A", dir = upstreamServer).exitCode == 0 + val commit = gitCmd("commit", "-m", importLog.joinToString("\n"), dir = upstreamServer).exitCode == 0 + if (!add || !commit) { + logger.lifecycle(">>> Didn't import any extra files") + } + logger.lifecycle(">>> Done importing mc-dev") + } +} diff --git a/buildSrc/src/main/kotlin/task/InitGitSubmodules.kt b/buildSrc/src/main/kotlin/task/InitGitSubmodules.kt new file mode 100644 index 00000000..65345379 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/InitGitSubmodules.kt @@ -0,0 +1,25 @@ +package task + +import gitCmd +import org.gradle.api.Project +import org.gradle.api.Task +import taskGroup +import upstreamDir +import upstreams + +internal fun Project.createInitGitSubmodulesTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("initGitSubmodules") { + receiver(this) + group = taskGroup + var upstreamNotInit = false + for (upstream in upstreams) { upstreamNotInit = upstreamNotInit || upstream.repoPath.toFile().resolve(".git").exists() } + onlyIf { !upstreamDir.resolve(".git").exists() || upstreamNotInit } + doLast { + var exit = gitCmd("submodule", "update", "--init", printOut = true).exitCode + exit += gitCmd("submodule", "update", "--init", "--recursive", dir = upstreamDir, printOut = true).exitCode + if (exit != 0) { + error("Failed to checkout git submodules: git exited with code $exit") + } + } +} diff --git a/buildSrc/src/main/kotlin/task/Paperclip.kt b/buildSrc/src/main/kotlin/task/Paperclip.kt new file mode 100644 index 00000000..6a795f74 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/Paperclip.kt @@ -0,0 +1,37 @@ +package task + +import cmd +import ensureSuccess +import jenkins +import org.gradle.api.Project +import org.gradle.api.Task +import rootProjectDir +import taskGroup +import toothpick + +internal fun Project.createPaperclipTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("paperclip") { + receiver(this) + group = taskGroup + doLast { + val workDir = toothpick.paperDir.resolve("work") + val paperclipDir = workDir.resolve("Paperclip") + val vanillaJarPath = + workDir.resolve("Minecraft/${toothpick.minecraftVersion}/${toothpick.minecraftVersion}.jar").absolutePath + val patchedJarPath = inputs.files.singleFile.absolutePath + logger.lifecycle(">>> Building paperclip") + val paperclipCmd = arrayListOf( + "mvn", "-T", "2C", "clean", "package", + "-Dmcver=${toothpick.minecraftVersion}", + "-Dpaperjar=$patchedJarPath", + "-Dvanillajar=$vanillaJarPath" + ) + if (jenkins) paperclipCmd.add("-Dstyle.color=never") + ensureSuccess(cmd(*paperclipCmd.toTypedArray(), dir = paperclipDir, printOut = true)) + val paperClip = paperclipDir.resolve("assembly/target/paperclip-${toothpick.minecraftVersion}.jar") + val destination = rootProjectDir.resolve(toothpick.calcPaperclipName) + paperClip.copyTo(destination, overwrite = true) + logger.lifecycle(">>> ${toothpick.calcPaperclipName} saved to root project directory") + } +} diff --git a/buildSrc/src/main/kotlin/task/PatchCredits.kt b/buildSrc/src/main/kotlin/task/PatchCredits.kt new file mode 100644 index 00000000..96c84d7f --- /dev/null +++ b/buildSrc/src/main/kotlin/task/PatchCredits.kt @@ -0,0 +1,100 @@ +package task + +import PatchParser +import PatchParser.PatchInfo +import com.github.mustachejava.DefaultMustacheFactory +import com.github.mustachejava.MustacheFactory +import org.gradle.api.Project +import org.gradle.api.Task +import taskGroup +import java.io.* +import java.nio.charset.StandardCharsets +import java.nio.file.Paths +import java.util.* +import patchCreditsOutput +import patchCreditsTemplate + +internal fun Project.createPatchCreditsTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("patchCredits") { + receiver(this) + group = taskGroup + + val projectDirectory: File = rootDir + val patchDirectory: File? = Paths.get("$rootDir/patches").toFile() + val outputFileName: String = patchCreditsOutput + val srcFileName: String = patchCreditsTemplate + + doLast { + val src = File(projectDirectory, srcFileName) + if (!src.exists()) { + logger.warn("Unable to find src at '" + src.absolutePath + "'! Skipping!") + return@doLast + } + if (!patchDirectory!!.exists()) { + logger + .warn( + "Unable to find patch directory at '" + + patchDirectory.absolutePath + + "'! Skipping!" + ) + return@doLast + } + logger.lifecycle("Scanning '$patchDirectory' for patches!") + val patches: MutableList = ArrayList() + scanFolder(patchDirectory, patches, project) + if (patches.isEmpty()) { + logger.warn("Unable to find any patches! Skipping!") + return@doLast + } + + patches.sortWith { a: PatchInfo, b: PatchInfo -> a.subject.compareTo(b.subject) } + val output = Output() + output.patches = patches + try { + val mf: MustacheFactory = DefaultMustacheFactory() + FileReader(src).use { srcReader -> + val mustache = mf.compile(srcReader, "template") + val outputFile = File(projectDirectory, outputFileName) + if (outputFile.exists()) { + outputFile.delete() + } + OutputStreamWriter(FileOutputStream(outputFile), StandardCharsets.UTF_8).use { writer -> + mustache.execute( + writer, + output + ).flush() + } + } + } catch (ex: IOException) { + error("Error while writing the output file!") + } + } +} + + +fun scanFolder(folder: File?, patches: MutableList, project: Project) { + val files = folder!!.listFiles { dir: File, name: String -> + if (dir.isDirectory) { + return@listFiles !name.equals("removed", ignoreCase = true) + } else { + return@listFiles true + } + } ?: return + for (f: File in files) { + if (f.isDirectory) { + scanFolder(f, patches, project) + } else if (f.name.endsWith(".patch")) { + try { + patches.add(PatchParser.parsePatch(f)) + } catch (ex: IOException) { + project.logger.warn("Exception while parsing '" + f.absolutePath + "'!", ex) + } + } + } +} + +class Output { + var patches: List? = null +} + diff --git a/buildSrc/src/main/kotlin/task/RebuildPatches.kt b/buildSrc/src/main/kotlin/task/RebuildPatches.kt new file mode 100644 index 00000000..b44a7159 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/RebuildPatches.kt @@ -0,0 +1,76 @@ +package task + +import ensureSuccess +import forkName +import gitCmd +import org.gradle.api.Project +import org.gradle.api.Task +import taskGroup +import toothpick +import upstreams +import java.io.File +import java.nio.file.Paths +import Upstream + +@Suppress("UNUSED_VARIABLE") +internal fun Project.createRebuildPatchesTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("rebuildPatches") { + receiver(this) + group = taskGroup + doLast { + for ((name, subproject) in toothpick.subprojects) { + val (sourceRepo, projectDir, patchesDir) = subproject + var previousUpstreamName = "origin/master" + val folder = (if (patchesDir.endsWith("server")) "server" else "api") + + for (upstream in upstreams) { + val patchPath = Paths.get("${upstream.patchPath}/$folder").toFile() + + if (patchPath.listFiles()?.isEmpty() != false) continue + + updatePatches(patchPath, upstream.name, folder, projectDir, previousUpstreamName) + previousUpstreamName = "${upstream.name}-$folder" + } + ensureSuccess(gitCmd("checkout", "$forkName-$folder", dir = projectDir, + printOut = true)) + + updatePatches(patchesDir, toothpick.forkName, folder, projectDir, previousUpstreamName) + + logger.lifecycle(">>> Done rebuilding patches for $name") + } + } +} + +private fun Project.updatePatches( + patchPath: File, + name: String, + folder: String, + projectDir: File, + previousUpstreamName: String +) { + logger.lifecycle(">>> Rebuilding patches for $name-$folder") + if (!patchPath.exists()) { + patchPath.mkdirs() + } + // Nuke old patches + patchPath.listFiles() + ?.filter { it -> it.name.endsWith(".patch") } + ?.forEach { it -> it.delete() } + + ensureSuccess( + gitCmd( + "checkout", "$name-$folder", dir = projectDir, + printOut = true + ) + ) + ensureSuccess( + gitCmd( + "format-patch", + "--no-stat", "--zero-commit", "--full-index", "--no-signature", "-N", + "-o", patchPath.absolutePath, previousUpstreamName, + dir = projectDir, + printOut = true + ) + ) +} diff --git a/buildSrc/src/main/kotlin/task/SetupUpstream.kt b/buildSrc/src/main/kotlin/task/SetupUpstream.kt new file mode 100644 index 00000000..fa969e24 --- /dev/null +++ b/buildSrc/src/main/kotlin/task/SetupUpstream.kt @@ -0,0 +1,35 @@ +package task + +import bashCmd +import gitHash +import lastUpstream +import org.gradle.api.Project +import org.gradle.api.Task +import taskGroup +import toothpick +import upstreamDir + +internal fun Project.createSetupUpstreamTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("setupUpstream") { + receiver(this) + group = taskGroup + doLast { + val setupUpstreamCommand = if (upstreamDir.resolve(toothpick.upstreamLowercase).exists()) { + "./${toothpick.upstreamLowercase} patch" + } else if ( + upstreamDir.resolve("build.gradle.kts").exists() + && upstreamDir.resolve("subprojects/server.gradle.kts").exists() + && upstreamDir.resolve("subprojects/api.gradle.kts").exists() + ) { + "./gradlew applyPatches" + } else { + error("Don't know how to setup upstream!") + } + val result = bashCmd(setupUpstreamCommand, dir = upstreamDir, printOut = true) + if (result.exitCode != 0) { + error("Failed to apply upstream patches: script exited with code ${result.exitCode}") + } + lastUpstream.writeText(gitHash(upstreamDir)) + } +} diff --git a/buildSrc/src/main/kotlin/task/UpdateUpstream.kt b/buildSrc/src/main/kotlin/task/UpdateUpstream.kt new file mode 100644 index 00000000..5b0c612e --- /dev/null +++ b/buildSrc/src/main/kotlin/task/UpdateUpstream.kt @@ -0,0 +1,155 @@ +package task + +import Upstream +import ensureSuccess +import gitCmd +import org.apache.tools.ant.util.FileUtils +import org.gradle.api.Project +import org.gradle.api.Task +import rootProjectDir +import taskGroup +import toothpick +import upstreamDir +import upstreams +import java.io.File +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths +import java.util.stream.Collectors + + +internal fun Project.createUpdateUpstreamTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("updateUpstream") { + receiver(this) + group = taskGroup + doLast { + ensureSuccess(gitCmd("fetch", dir = upstreamDir, printOut = true)) + ensureSuccess(gitCmd("reset", "--hard", toothpick.upstreamBranch, dir = upstreamDir, printOut = true)) + ensureSuccess(gitCmd("add", toothpick.upstream, dir = rootProjectDir, printOut = true)) + for (upstream in upstreams) { + ensureSuccess(gitCmd("fetch", dir = upstream.repoPath.toFile(), printOut = true)) + ensureSuccess(gitCmd("reset", "--hard", upstream.branch, dir = upstream.repoPath.toFile(), printOut = true)) + ensureSuccess(gitCmd("add", "upstream/${upstream.name}", dir = rootProjectDir, printOut = true)) + } + ensureSuccess(gitCmd("submodule", "update", "--init", "--recursive", dir = upstreamDir, printOut = true)) + val fileUtils = FileUtils.getFileUtils() + for (upstream in upstreams) { + val serverRepoPatches = upstream.getRepoServerPatches() + val apiRepoPatches = upstream.getRepoAPIPatches() + val serverPatches = upstream.serverList + val apiPatches = upstream.apiList + logger.lifecycle(">>> Pulling ${upstream.name} patches") + updatePatches(serverRepoPatches, upstream, fileUtils, serverPatches, "server") + updatePatches(apiRepoPatches, upstream, fileUtils, apiPatches, "api") + upstream.updateUpstreamCommitHash() + } + } +} + +private fun updatePatches( + repoPatches: MutableList?, + upstream: Upstream, + fileUtils: FileUtils, + patches: MutableList?, + folder: String +) { + if (repoPatches != null) { + var i = 0 + val currentPatchList = Paths.get("${upstream.patchPath}/$folder").toFile().listFiles() as Array? + val tmpFolder = Paths.get("${upstream.patchPath}/tmp/$folder").toFile() + tmpFolder.mkdirs() + if (currentPatchList != null) { + for (patch in currentPatchList) { + if (patch.exists()) { + fileUtils.copyFile( + "${upstream.patchPath}/$folder/${patch.name}", + "${upstream.patchPath}/tmp/$folder/${patch.name}" + ) + patch.delete() + } + } + } + val currentPatchListFiltered = currentPatchList?.toList() + ?.stream()?.sorted()?.map { patch -> patch.name.substring(5, patch.name.length) } + ?.collect(Collectors.toList()) + for (patch in repoPatches) { + if (patches != null) { + if ((patches.contains(patch) && upstream.useBlackList) || (!patches.contains(patch) && !upstream.useBlackList)) { + continue + } else { + i++ + updatePatch(fileUtils, upstream, repoPatches, patch, i, folder, currentPatchListFiltered) + } + } + } + val tmpFolderList = tmpFolder.listFiles() + if (tmpFolderList != null) { + for (patch in tmpFolderList) { + patch.delete() + } + } + } +} + +private fun updatePatch( + fileUtils: FileUtils, + upstream: Upstream, + serverRepoPatches: MutableList, + patch: String, + i: Int, + folder: String, + currentPatchListFiltered: MutableList? +) { + if (currentPatchListFiltered == null || patchHasDiff(upstream, serverRepoPatches, patch, folder, currentPatchListFiltered)) { + fileUtils.copyFile("${upstream.repoPath}/patches/$folder/" + + "${String.format("%04d", serverRepoPatches.indexOf(patch) + 1)}-$patch", + "${upstream.patchPath}/$folder/${String.format("%04d", i)}-$patch" + ) + } else { + fileUtils.copyFile("${upstream.patchPath}/tmp/$folder/" + + "${String.format("%04d", currentPatchListFiltered.indexOf(patch) + 1)}-$patch", + "${upstream.patchPath}/$folder/${String.format("%04d", i)}-$patch" + ) + } +} + +fun patchHasDiff( + upstream: Upstream, + serverRepoPatches: MutableList, + patch: String, + folder: String, + currentPatchListFiltered: MutableList +): Boolean { + if (!Paths.get("${upstream.patchPath}/tmp/$folder/${String.format("%04d", currentPatchListFiltered.indexOf(patch) + 1)}-$patch").toFile().isFile) return true + if (!patchChanged(upstream, serverRepoPatches, patch, folder)) return false + val upstreamFile = Files.readAllLines(Paths.get("${upstream.repoPath}/patches/$folder/${String.format("%04d", serverRepoPatches.indexOf(patch) + 1)}-$patch"), StandardCharsets.UTF_8) + val repoFile = Files.readAllLines(Paths.get("${upstream.patchPath}/tmp/$folder/${String.format("%04d", currentPatchListFiltered.indexOf(patch) + 1)}-$patch"), StandardCharsets.UTF_8) + return upstreamFile.stream().filter {line -> line.startsWith("+") || line.startsWith("-")} + .filter {line -> if (line.startsWith("---") || line.startsWith("+++")) { + line.substring(3, line.length).trim().isNotBlank() + } + else if (line.startsWith("--") || line.startsWith("++")) { + line.substring(2, line.length).trim().isNotBlank() + } + else { + line.substring(1, line.length).trim().isNotBlank() + } } + .filter {line -> if (repoFile.contains(line)) { + repoFile.remove(line) + return@filter false + } else { return@filter true } }.collect(Collectors.toList()).isNotEmpty() +} + +fun patchChanged( + upstream: Upstream, + serverRepoPatches: MutableList, + patch: String, + folder: String +): Boolean { + val diffCheckCmdResult = upstream.project.gitCmd("diff", "--name-only", upstream.upstreamCommit, upstream.getCurrentCommitHash(), dir = upstream.repoPath.toFile() ) + val diffCheckResult = diffCheckCmdResult.output.toString() + if (diffCheckResult.isBlank()) return false + val diffCheckChangeFiles = diffCheckResult.split("\\n".toRegex()).toTypedArray().toList() + return diffCheckChangeFiles.contains("patches/$folder/${String.format("%04d", serverRepoPatches.indexOf(patch) + 1)}-$patch") +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/task/UpstreamCommit.kt b/buildSrc/src/main/kotlin/task/UpstreamCommit.kt new file mode 100644 index 00000000..0fbadcad --- /dev/null +++ b/buildSrc/src/main/kotlin/task/UpstreamCommit.kt @@ -0,0 +1,82 @@ +package task + +import ensureSuccess +import gitCmd +import org.gradle.api.Project +import org.gradle.api.Task +import taskGroup +import toothpick +import upstreamDir +import upstreams +import java.io.File +import Upstream +import java.util.concurrent.CopyOnWriteArrayList + +internal fun Project.createUpstreamCommitTask( + receiver: Task.() -> Unit = {} +): Task = tasks.create("upstreamCommit") { + receiver(this) + group = taskGroup + doLast { + gitChangelog = getUpstreamChanges(project,this, toothpick.upstream, + upstreamDir, toothpick.upstream) + + for (upstream in upstreams) { + gitChangelog = getUpstreamChanges(project,this, upstream.name, + upstream.repoPath.toFile(), "upstream/${upstream.name}") + } + + var changedUpstreamsString = "" + for (upstreamName in changedUpstreams) { + if (changedUpstreamsString.isNotEmpty()) { + changedUpstreamsString += "/" + } + changedUpstreamsString += upstreamName + } + if (changedUpstreamsString.isNotEmpty()) { + val commitMessage = """ + |Updated Upstream and Sidestream(s) ($changedUpstreamsString) + | + |Upstream/An Sidestream has released updates that appears to apply and compile correctly + |This update has NOT been tested by YatopiaMC and as with ANY update, please do your own testing. + | + | + |$gitChangelog + """.trimMargin() + ensureSuccess(gitCmd("commit", "-m", commitMessage, printOut = true)) + } + + } +} + +private fun getUpstreamChanges( + project: Project, + task: Task, + name: String, + dir: File, + path: String +): String { + var gitChangelog1 = gitChangelog + val oldRev = ensureSuccess(project.gitCmd("ls-tree", "HEAD", path)) + ?.substringAfter("commit ")?.substringBefore("\t") + val upstreamTmp = ensureSuccess( + project.gitCmd( + "log", + "--oneline", + "$oldRev...HEAD", + printOut = true, + dir = dir + ) + ) { + task.logger.lifecycle("No $name changes to commit.") + } + if (!upstreamTmp.isNullOrBlank()) { + changedUpstreams.add(name) + gitChangelog1 += "$name Changes:\n$upstreamTmp\n\n" + } + return gitChangelog1 +} + +val changedUpstreams = CopyOnWriteArrayList() + +var gitChangelog = "" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..944e302d --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.daemon=true +org.gradle.jvmargs=-Xmx2G +org.gradle.parallel=true +org.gradle.configureondemand=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..da9702f9 --- /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-6.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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 "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /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/patches/AirplaneLite/PATCHES-LICENSE b/patches/AirplaneLite/PATCHES-LICENSE new file mode 100644 index 00000000..810fce6e --- /dev/null +++ b/patches/AirplaneLite/PATCHES-LICENSE @@ -0,0 +1,621 @@ + 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 diff --git a/patches/AirplaneLite/patches/server/0001-AirplaneLite-MC-Dev-Fixes.patch b/patches/AirplaneLite/patches/server/0001-AirplaneLite-MC-Dev-Fixes.patch new file mode 100644 index 00000000..254a445a --- /dev/null +++ b/patches/AirplaneLite/patches/server/0001-AirplaneLite-MC-Dev-Fixes.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sat, 31 Oct 2020 19:21:42 -0500 +Subject: [PATCH] AirplaneLite MC Dev Fixes + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/LootTableInfo.java b/src/main/java/net/minecraft/server/LootTableInfo.java +index addeb268d4d487e18ddaadebf96f078fd079246f..268147484805e9fff298d2f5006f1c594c485342 100644 +--- a/src/main/java/net/minecraft/server/LootTableInfo.java ++++ b/src/main/java/net/minecraft/server/LootTableInfo.java +@@ -53,7 +53,7 @@ public class LootTableInfo { + + @Nullable + public T getContextParameter(LootContextParameter lootcontextparameter) { +- return this.h.get(lootcontextparameter); ++ return (T) this.h.get(lootcontextparameter); // AirplaneL - compile error + } + + public boolean a(LootTable loottable) { +@@ -207,7 +207,7 @@ public class LootTableInfo { + } + + public T a(LootContextParameter lootcontextparameter) { +- T t0 = this.b.get(lootcontextparameter); ++ T t0 = (T) this.b.get(lootcontextparameter); // AirplaneL - compile error + + if (t0 == null) { + throw new IllegalArgumentException("No parameter " + lootcontextparameter); +@@ -218,7 +218,7 @@ public class LootTableInfo { + + @Nullable + public T b(LootContextParameter lootcontextparameter) { +- return this.b.get(lootcontextparameter); ++ return (T) this.b.get(lootcontextparameter); // AirplaneL - compile error + } + + public LootTableInfo build(LootContextParameterSet lootcontextparameterset) { diff --git a/patches/AirplaneLite/patches/server/0002-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch b/patches/AirplaneLite/patches/server/0002-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch new file mode 100644 index 00000000..f80f237e --- /dev/null +++ b/patches/AirplaneLite/patches/server/0002-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch @@ -0,0 +1,185 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sat, 31 Oct 2020 18:43:02 -0500 +Subject: [PATCH] Strip raytracing for EntityLiving#hasLineOfSight + +The IBlockAccess#rayTrace method is very wasteful in both allocations, +and in logic. While EntityLiving#hasLineOfSight provides static +parameters for collisions with blocks and fluids, the method still does +a lot of dynamic checks for both of these, which result in extra work. +As well, since the fluid collision option is set to NONE, the entire +fluid collision system is completely unneeded, yet used anyways. + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 8bceb152d9c7a3227c4bd3bb47964cf69abea0b4..c86f947a4c9843beec08f2b2ac52c0021063c285 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -3046,7 +3046,7 @@ public abstract class EntityLiving extends Entity { + Vec3D vec3d = new Vec3D(this.locX(), this.getHeadY(), this.locZ()); + Vec3D vec3d1 = new Vec3D(entity.locX(), entity.getHeadY(), entity.locZ()); + +- return this.world.rayTrace(new RayTrace(vec3d, vec3d1, RayTrace.BlockCollisionOption.COLLIDER, RayTrace.FluidCollisionOption.NONE, this)).getType() == MovingObjectPosition.EnumMovingObjectType.MISS; ++ return this.world.rayTraceDirect(vec3d, vec3d1, VoxelShapeCollision.a(this)) == MovingObjectPosition.EnumMovingObjectType.MISS; // AirplaneL - use direct method + } + + @Override +diff --git a/src/main/java/net/minecraft/server/IBlockAccess.java b/src/main/java/net/minecraft/server/IBlockAccess.java +index 5c3eb4fc7e5aec2ad8d0050673fc8f4d2bff6a71..376ff36062d85b8ea8b004d9266ee9ee382b2942 100644 +--- a/src/main/java/net/minecraft/server/IBlockAccess.java ++++ b/src/main/java/net/minecraft/server/IBlockAccess.java +@@ -44,6 +44,15 @@ public interface IBlockAccess { + return BlockPosition.a(axisalignedbb).map(this::getType); + } + ++ // AirplaneL start - broken down variant of below rayTraceBlock, used by World#rayTraceDirect ++ default MovingObjectPosition.EnumMovingObjectType rayTraceBlockDirect(Vec3D vec3d, Vec3D vec3d1, BlockPosition blockposition, IBlockData iblockdata, VoxelShapeCollision voxelshapecoll) { ++ VoxelShape voxelshape = RayTrace.BlockCollisionOption.COLLIDER.get(iblockdata, this, blockposition, voxelshapecoll); ++ MovingObjectPositionBlock movingobjectpositionblock = this.rayTrace(vec3d, vec3d1, blockposition, voxelshape, iblockdata); ++ ++ return movingobjectpositionblock == null ? null : movingobjectpositionblock.getType(); ++ } ++ // AirplaneL end ++ + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default MovingObjectPositionBlock rayTraceBlock(RayTrace raytrace1, BlockPosition blockposition) { + // Paper start - Prevent raytrace from loading chunks +diff --git a/src/main/java/net/minecraft/server/MathHelper.java b/src/main/java/net/minecraft/server/MathHelper.java +index 2e7721a650c5a351b3584665bd236f92ef577761..8a6b623084fdc5ee2b0718f9e72f7c52a0d58d91 100644 +--- a/src/main/java/net/minecraft/server/MathHelper.java ++++ b/src/main/java/net/minecraft/server/MathHelper.java +@@ -238,6 +238,7 @@ public class MathHelper { + return f - (float) d(f); + } + ++ public static double getDecimals(double num) { return h(num); } // AirplaneL + public static double h(double d0) { + return d0 - (double) d(d0); + } +@@ -416,6 +417,7 @@ public class MathHelper { + return f1 + f * (f2 - f1); + } + ++ public static double linearInterpolation(double value1, double value2, double amount) { return d(value1, value2, amount); } // AirplaneL - OBFHELPER + public static double d(double d0, double d1, double d2) { + return d1 + d0 * (d2 - d1); + } +@@ -432,6 +434,7 @@ public class MathHelper { + return d0 * d0 * d0 * (d0 * (d0 * 6.0D - 15.0D) + 10.0D); + } + ++ public static int sign(double num) { return k(num); } // AirplaneL - OBFHELPER + public static int k(double d0) { + return d0 == 0.0D ? 0 : (d0 > 0.0D ? 1 : -1); + } +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 91aa8a2bc111ee6935ada0ae471fe1a3bc8fad80..87a4e53ad1ea1978bc9a0c335293190460efde8b 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -332,6 +332,91 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return null; + } + ++ // AirplaneL start - broken down method of raytracing for EntityLiving#hasLineOfSight, replaces IBlockAccess#rayTrace(RayTrace) ++ protected MovingObjectPosition.EnumMovingObjectType rayTraceDirect(Vec3D vec3d, Vec3D vec3d1, VoxelShapeCollision voxelshapecoll) { ++ // most of this code comes from IBlockAccess#a(RayTrace, BiFunction, Function), but removes the needless functions ++ if (vec3d.equals(vec3d1)) { ++ return MovingObjectPosition.EnumMovingObjectType.MISS; ++ } ++ ++ double endX = MathHelper.linearInterpolation(-1.0E-7D, vec3d1.x, vec3d.x); ++ double endY = MathHelper.linearInterpolation(-1.0E-7D, vec3d1.y, vec3d.y); ++ double endZ = MathHelper.linearInterpolation(-1.0E-7D, vec3d1.z, vec3d.z); ++ ++ double startX = MathHelper.linearInterpolation(-1.0E-7D, vec3d.x, vec3d1.x); ++ double startY = MathHelper.linearInterpolation(-1.0E-7D, vec3d.y, vec3d1.y); ++ double startZ = MathHelper.linearInterpolation(-1.0E-7D, vec3d.z, vec3d1.z); ++ ++ int currentX = MathHelper.floor(startX); ++ int currentY = MathHelper.floor(startY); ++ int currentZ = MathHelper.floor(startZ); ++ ++ BlockPosition.MutableBlockPosition currentBlock = new BlockPosition.MutableBlockPosition(currentX, currentY, currentZ); ++ ++ Chunk chunk = this.getChunkIfLoaded(currentBlock); ++ if (chunk == null) { ++ return MovingObjectPosition.EnumMovingObjectType.MISS; ++ } ++ ++ MovingObjectPosition.EnumMovingObjectType initialCheck = this.rayTraceBlockDirect(vec3d, vec3d1, currentBlock, chunk.getType(currentBlock), voxelshapecoll); ++ ++ if (initialCheck != null) { ++ return initialCheck; ++ } ++ ++ double diffX = endX - startX; ++ double diffY = endY - startY; ++ double diffZ = endZ - startZ; ++ ++ int xDirection = MathHelper.sign(diffX); ++ int yDirection = MathHelper.sign(diffY); ++ int zDirection = MathHelper.sign(diffZ); ++ ++ double normalizedX = xDirection == 0 ? Double.MAX_VALUE : (double) xDirection / diffX; ++ double normalizedY = yDirection == 0 ? Double.MAX_VALUE : (double) yDirection / diffY; ++ double normalizedZ = zDirection == 0 ? Double.MAX_VALUE : (double) zDirection / diffZ; ++ ++ double normalizedXDirection = normalizedX * (xDirection > 0 ? 1.0D - MathHelper.getDecimals(startX) : MathHelper.getDecimals(startX)); ++ double normalizedYDirection = normalizedY * (yDirection > 0 ? 1.0D - MathHelper.getDecimals(startY) : MathHelper.getDecimals(startY)); ++ double normalizedZDirection = normalizedZ * (zDirection > 0 ? 1.0D - MathHelper.getDecimals(startZ) : MathHelper.getDecimals(startZ)); ++ ++ MovingObjectPosition.EnumMovingObjectType result; ++ ++ do { ++ if (normalizedXDirection > 1.0D && normalizedYDirection > 1.0D && normalizedZDirection > 1.0D) { ++ return MovingObjectPosition.EnumMovingObjectType.MISS; ++ } ++ ++ if (normalizedXDirection < normalizedYDirection) { ++ if (normalizedXDirection < normalizedZDirection) { ++ currentX += xDirection; ++ normalizedXDirection += normalizedX; ++ } else { ++ currentZ += zDirection; ++ normalizedZDirection += normalizedZ; ++ } ++ } else if (normalizedYDirection < normalizedZDirection) { ++ currentY += yDirection; ++ normalizedYDirection += normalizedY; ++ } else { ++ currentZ += zDirection; ++ normalizedZDirection += normalizedZ; ++ } ++ ++ currentBlock.setValues(currentX, currentY, currentZ); ++ if (chunk.getPos().x != currentBlock.getX() >> 4 || chunk.getPos().z != currentBlock.getZ() >> 4) { ++ chunk = this.getChunkIfLoaded(currentBlock); ++ if (chunk == null) { ++ return MovingObjectPosition.EnumMovingObjectType.MISS; ++ } ++ } ++ result = this.rayTraceBlockDirect(vec3d, vec3d1, currentBlock, chunk.getType(currentBlock), voxelshapecoll); ++ } while (result == null); ++ ++ return result; ++ } ++ // AirplaneL end ++ + public static boolean isValidLocation(BlockPosition blockposition) { + return blockposition.isValidLocation(); // Paper - use better/optimized check + } diff --git a/patches/AirplaneLite/patches/server/0003-Simpler-ShapelessRecipes-comparison-for-Vanilla.patch b/patches/AirplaneLite/patches/server/0003-Simpler-ShapelessRecipes-comparison-for-Vanilla.patch new file mode 100644 index 00000000..3037ba47 --- /dev/null +++ b/patches/AirplaneLite/patches/server/0003-Simpler-ShapelessRecipes-comparison-for-Vanilla.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sat, 31 Oct 2020 18:51:38 -0500 +Subject: [PATCH] Simpler ShapelessRecipes comparison for Vanilla + +Paper added a fancy sorting comparison due to Bukkit recipes breaking +the vanilla one, however this is far more advanced than what you need +for all the vanilla recipes. + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/ShapelessRecipes.java b/src/main/java/net/minecraft/server/ShapelessRecipes.java +index 61d88dbaa1f5c543be610ce0914b2c89d8ad40ee..e7870de2467ca28070c234a39452778a3ac08862 100644 +--- a/src/main/java/net/minecraft/server/ShapelessRecipes.java ++++ b/src/main/java/net/minecraft/server/ShapelessRecipes.java +@@ -17,8 +17,16 @@ public class ShapelessRecipes implements RecipeCrafting { + private final String group; + private final ItemStack result; + private final NonNullList ingredients; ++ private final boolean isBukkit; // AirplaneL + ++ // AirplaneL start - add isBukkit constructor param + public ShapelessRecipes(MinecraftKey minecraftkey, String s, ItemStack itemstack, NonNullList nonnulllist) { ++ this(minecraftkey, s, itemstack, nonnulllist, false); ++ } ++ ++ public ShapelessRecipes(MinecraftKey minecraftkey, String s, ItemStack itemstack, NonNullList nonnulllist, boolean isBukkit) { ++ this.isBukkit = isBukkit; ++ // AirplaneL end + this.key = minecraftkey; + this.group = s; + this.result = itemstack; +@@ -60,6 +68,28 @@ public class ShapelessRecipes implements RecipeCrafting { + } + + public boolean a(InventoryCrafting inventorycrafting, World world) { ++ // AirplaneL start ++ if (!this.isBukkit) { ++ java.util.List ingredients = com.google.common.collect.Lists.newArrayList(this.ingredients.toArray(new RecipeItemStack[0])); ++ ++ inventory: for (int index = 0; index < inventorycrafting.getSize(); index++) { ++ ItemStack itemStack = inventorycrafting.getItem(index); ++ ++ if (!itemStack.isEmpty()) { ++ for (int i = 0; i < ingredients.size(); i++) { ++ if (ingredients.get(i).test(itemStack)) { ++ ingredients.remove(i); ++ continue inventory; ++ } ++ } ++ return false; ++ } ++ } ++ ++ return ingredients.isEmpty(); ++ } ++ // AirplaneL end ++ + AutoRecipeStackManager autorecipestackmanager = new AutoRecipeStackManager(); + int i = 0; + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java +index 4aba511fe8078164bf1467b39645dd9bf6a931e7..e1a8f54450de4f173e59cd1851124b7b8621af7d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java +@@ -44,6 +44,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe + data.set(i, toNMS(ingred.get(i), true)); + } + +- MinecraftServer.getServer().getCraftingManager().addRecipe(new ShapelessRecipes(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftItemStack.asNMSCopy(this.getResult()), data)); ++ MinecraftServer.getServer().getCraftingManager().addRecipe(new ShapelessRecipes(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftItemStack.asNMSCopy(this.getResult()), data, true)); // AirplaneL + } + } diff --git a/patches/AirplaneLite/patches/server/0004-Queue-lighting-update-only-once.patch b/patches/AirplaneLite/patches/server/0004-Queue-lighting-update-only-once.patch new file mode 100644 index 00000000..dca998d2 --- /dev/null +++ b/patches/AirplaneLite/patches/server/0004-Queue-lighting-update-only-once.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sat, 31 Oct 2020 19:03:25 -0500 +Subject: [PATCH] Queue lighting update only once + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 38ca1c042afd41a1f660f88e398fedde00f34e39..45db17d2f415197849c1fac5adce46d893e2a81d 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -996,6 +996,7 @@ public class ChunkProviderServer extends IChunkProvider { + // Paper - moved up + // Tuinity start - optimise chunk tick iteration + com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entityTickingChunks.iterator(); ++ boolean updateLighting = false; // AirplaneL + try { + while (iterator.hasNext()) { + Chunk chunk = iterator.next(); +@@ -1020,7 +1021,7 @@ public class ChunkProviderServer extends IChunkProvider { + } + + this.world.timings.chunkTicks.startTiming(); // Spigot // Paper +- this.world.a(chunk, k); ++ if (this.world.abool(chunk, k)) updateLighting = true; // AirplaneL + this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper + MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick + } +@@ -1030,6 +1031,7 @@ public class ChunkProviderServer extends IChunkProvider { + } finally { + iterator.finishedIterating(); + } ++ if (updateLighting) this.getLightEngine().queueUpdate(); // AirplaneL + // Tuinity end - optimise chunk tick iteration + this.world.getMethodProfiler().enter("customSpawners"); + if (flag1) { +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 6d5616dc4a899a1c01a21daece17f1c2cf0411bd..ff3918adeaad7636f179e33df5b0169dbb84b16c 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -1019,7 +1019,10 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom(); + // Paper end + +- public void a(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper ++ // AirplaneL start - create version of chunk tick that returns a bool for updating lighting ++ public void a(Chunk chunk, int i) { this.abool(chunk, i); } ++ public boolean abool(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper ++ // AirplaneL end + ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); + boolean flag = this.isRaining(); + int j = chunkcoordintpair.d(); +@@ -1128,9 +1131,13 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + gameprofilerfiller.exit(); + timings.chunkTicksBlocks.stopTiming(); // Paper +- getChunkProvider().getLightEngine().queueUpdate(); // Paper ++ // AirplaneL start ++ //getChunkProvider().getLightEngine().queueUpdate(); // Paper ++ return true; ++ // AirplaneL end + // Paper end + } ++ return false; // AirplaneL + } + + protected BlockPosition a(BlockPosition blockposition) { diff --git a/patches/AirplaneLite/patches/server/0005-Use-unmodifiableMap-instead-of-making-copy.patch b/patches/AirplaneLite/patches/server/0005-Use-unmodifiableMap-instead-of-making-copy.patch new file mode 100644 index 00000000..a755db3e --- /dev/null +++ b/patches/AirplaneLite/patches/server/0005-Use-unmodifiableMap-instead-of-making-copy.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sat, 31 Oct 2020 19:22:37 -0500 +Subject: [PATCH] Use unmodifiableMap instead of making copy + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/LootTableInfo.java b/src/main/java/net/minecraft/server/LootTableInfo.java +index 268147484805e9fff298d2f5006f1c594c485342..1a87d73e116d3662bbc709adaf2ef7e4dd12f865 100644 +--- a/src/main/java/net/minecraft/server/LootTableInfo.java ++++ b/src/main/java/net/minecraft/server/LootTableInfo.java +@@ -34,8 +34,8 @@ public class LootTableInfo { + this.world = worldserver; + this.d = function; + this.f = function1; +- this.h = ImmutableMap.copyOf(map); +- this.i = ImmutableMap.copyOf(map1); ++ this.h = java.util.Collections.unmodifiableMap(map); // AirplaneL ++ this.i = java.util.Collections.unmodifiableMap(map1); // AirplaneL + } + + public boolean hasContextParameter(LootContextParameter lootcontextparameter) { diff --git a/patches/AirplaneLite/patches/server/0006-Swap-priority-of-checks-in-chunk-ticking.patch b/patches/AirplaneLite/patches/server/0006-Swap-priority-of-checks-in-chunk-ticking.patch new file mode 100644 index 00000000..3bca0ad1 --- /dev/null +++ b/patches/AirplaneLite/patches/server/0006-Swap-priority-of-checks-in-chunk-ticking.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sun, 1 Nov 2020 16:59:08 -0600 +Subject: [PATCH] Swap priority of checks in chunk ticking + +World.V showed up a lot in lag spikes for some reason, although I wonder +if it's just Spark getting JVM safe points. + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index ff3918adeaad7636f179e33df5b0169dbb84b16c..ca69b5e5126eef423bee24af4f63f4bb442c560f 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -1032,7 +1032,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.enter("thunder"); + final BlockPosition.MutableBlockPosition blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change + +- if (!this.paperConfig.disableThunder && flag && this.W() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder ++ if (!this.paperConfig.disableThunder && flag && this.random.nextInt(100000) == 0 && this.W()) { // Paper - Disable thunder // AirplaneL - check this.W last + blockposition.setValues(this.a(this.a(j, 0, k, 15))); // Paper + if (this.isRainingAt(blockposition)) { + DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition); diff --git a/patches/AirplaneLite/patches/server/0007-Reduce-projectile-chunk-loading.patch b/patches/AirplaneLite/patches/server/0007-Reduce-projectile-chunk-loading.patch new file mode 100644 index 00000000..14bbc58f --- /dev/null +++ b/patches/AirplaneLite/patches/server/0007-Reduce-projectile-chunk-loading.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sun, 13 Dec 2020 17:52:35 -0600 +Subject: [PATCH] Reduce projectile chunk loading + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/EntityProjectile.java b/src/main/java/net/minecraft/server/EntityProjectile.java +index d85a19905efab7189e461a61becb6ca2b8c50803..71ff64c09858890b61b1cc9ea8e591ff4cf6c28b 100644 +--- a/src/main/java/net/minecraft/server/EntityProjectile.java ++++ b/src/main/java/net/minecraft/server/EntityProjectile.java +@@ -85,6 +85,37 @@ public abstract class EntityProjectile extends IProjectile { + this.setPosition(d0, d1, d2); + } + ++ private static int loadedThisTick = 0; ++ private static int loadedTick; ++ ++ private int buffered = 0; ++ ++ // AirplaneL start ++ @Override ++ public void setPosition(double d0, double d1, double d2) { ++ if (loadedTick != MinecraftServer.currentTick) { ++ loadedTick = MinecraftServer.currentTick; ++ loadedThisTick = 0; ++ } ++ int previousX = MathHelper.floor(this.locX()) >> 4, previousZ = MathHelper.floor(this.locZ()) >> 4; ++ int newX = MathHelper.floor(d0) >> 4, newZ = MathHelper.floor(d2) >> 4; ++ if (previousX != newX || previousZ != newZ) { ++ boolean isLoaded = this.world.isChunkLoaded(newX, newZ); ++ if (!isLoaded) { ++ if (loadedThisTick > 10) { // AirplaneL 10 = max chunks to load from projectiles in a tick todo config ++ if (++buffered > 20) { // AirplaneL 20 = max chunks a single projectile loads overall todo config ++ this.die(); ++ } ++ return; ++ } ++ loadedThisTick++; ++ } ++ buffered = 0; ++ } ++ super.setPosition(d0, d1, d2); ++ } ++ // AirplaneL end ++ + protected float k() { + return 0.03F; + } diff --git a/patches/AirplaneLite/patches/server/0008-Optimize-random-calls-in-chunk-ticking.patch b/patches/AirplaneLite/patches/server/0008-Optimize-random-calls-in-chunk-ticking.patch new file mode 100644 index 00000000..73c4c4ac --- /dev/null +++ b/patches/AirplaneLite/patches/server/0008-Optimize-random-calls-in-chunk-ticking.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Thu, 7 Jan 2021 11:49:36 -0600 +Subject: [PATCH] Optimize random calls in chunk ticking + +Especially at over 30,000 chunks these random calls are fairly heavy. We +use a different method here for checking lightning, and for checking +ice. + +Lighting: Each chunk now keeps an int of how many ticks until the +lightning should strike. This int is a random number from 0 to 100000 * 2, +the multiplication is required to keep the probability the same. + +Ice and snow: We just generate a single random number 0-16 and increment +it, while checking if it's 0 for the current chunk. + +Depending on configuration for things that tick in a chunk, this is a +5-10% improvement. + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index ae07ea2a34f5cd82ce2eae523359cb7540065335..8b7fd21e6b366196fbc9cd44a340335c4cf9205f 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -53,6 +53,17 @@ public class Chunk implements IChunkAccess { + private final ChunkCoordIntPair loc; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key + private volatile boolean x; + ++ // AirplaneL start - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively ++ private int lightningTick = -1; ++ public boolean shouldDoLightning() { ++ boolean doTick = this.lightningTick == 0; ++ if (this.lightningTick-- <= 0) { ++ this.lightningTick = this.world.random.nextInt(100000) << 1; ++ } ++ return doTick; ++ } ++ // AirplaneL end ++ + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage) { + this(world, chunkcoordintpair, biomestorage, ChunkConverter.a, TickListEmpty.b(), TickListEmpty.b(), 0L, (ChunkSection[]) null, (Consumer) null); + } +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 45db17d2f415197849c1fac5adce46d893e2a81d..ce2864f44dcc2003a85f7a211073b2b0bb617cd2 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -965,6 +965,7 @@ public class ChunkProviderServer extends IChunkProvider { + } + // Paper end - optimize isOutisdeRange + this.world.getMethodProfiler().enter("pollingChunks"); ++ this.world.resetIceAndSnowTick(); // AirplaneL - reset ice & snow tick random + int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED); + boolean flag2 = world.ticksPerAnimalSpawns != 0L && worlddata.getTime() % world.ticksPerAnimalSpawns == 0L; // CraftBukkit + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index ca69b5e5126eef423bee24af4f63f4bb442c560f..970a71fb66d235bf772aeddd02c50390ab7e568b 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -1019,6 +1019,8 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom(); + // Paper end + ++ private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // AirplaneL ++ + // AirplaneL start - create version of chunk tick that returns a bool for updating lighting + public void a(Chunk chunk, int i) { this.abool(chunk, i); } + public boolean abool(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper +@@ -1032,7 +1034,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.enter("thunder"); + final BlockPosition.MutableBlockPosition blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change + +- if (!this.paperConfig.disableThunder && flag && this.random.nextInt(100000) == 0 && this.W()) { // Paper - Disable thunder // AirplaneL - check this.W last ++ if (!this.paperConfig.disableThunder && flag && chunk.shouldDoLightning() && this.W()) { // Paper - Disable thunder // AirplaneL - check this.W last // AirplaneL - replace random with shouldDoLighting + blockposition.setValues(this.a(this.a(j, 0, k, 15))); // Paper + if (this.isRainingAt(blockposition)) { + DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition); +@@ -1062,7 +1064,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + gameprofilerfiller.exitEnter("iceandsnow"); +- if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking ++ if (!this.paperConfig.disableIceAndSnow && (this.currentIceAndSnowTick++ & 15) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking // AirplaneL - optimize further random ticking + // Paper start - optimise chunk ticking + this.getRandomBlockPosition(j, 0, k, 15, blockposition); + int normalY = chunk.getHighestBlockY(HeightMap.Type.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15); diff --git a/patches/AirplaneLite/patches/server/0009-Don-t-get-entity-equipment-if-not-needed.patch b/patches/AirplaneLite/patches/server/0009-Don-t-get-entity-equipment-if-not-needed.patch new file mode 100644 index 00000000..2b7173e8 --- /dev/null +++ b/patches/AirplaneLite/patches/server/0009-Don-t-get-entity-equipment-if-not-needed.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Fri, 15 Jan 2021 20:08:54 -0600 +Subject: [PATCH] Don't get entity equipment if not needed + +Airplane Lite +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index c86f947a4c9843beec08f2b2ac52c0021063c285..0dbd3e0cf72881b766b624621f8dcd0fff3b66b4 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -819,11 +819,13 @@ public abstract class EntityLiving extends Entity { + } + + if (entity != null) { +- ItemStack itemstack = this.getEquipment(EnumItemSlot.HEAD); +- Item item = itemstack.getItem(); ++ // AirplaneL start - don't get equipment if not needed ++ //ItemStack itemstack = this.getEquipment(EnumItemSlot.HEAD); ++ //Item item = itemstack.getItem(); + EntityTypes entitytypes = entity.getEntityType(); + +- if (entitytypes == EntityTypes.SKELETON && item == Items.SKELETON_SKULL || entitytypes == EntityTypes.ZOMBIE && item == Items.ZOMBIE_HEAD || entitytypes == EntityTypes.CREEPER && item == Items.CREEPER_HEAD) { ++ if (entitytypes == EntityTypes.SKELETON && this.getEquipment(EnumItemSlot.HEAD).getItem() == Items.SKELETON_SKULL || entitytypes == EntityTypes.ZOMBIE && this.getEquipment(EnumItemSlot.HEAD).getItem() == Items.ZOMBIE_HEAD || entitytypes == EntityTypes.CREEPER && this.getEquipment(EnumItemSlot.HEAD).getItem() == Items.CREEPER_HEAD) { ++ // AirplaneL end + d0 *= 0.5D; + } + } diff --git a/patches/AirplaneLite/server.txt b/patches/AirplaneLite/server.txt deleted file mode 100644 index 51102896..00000000 --- a/patches/AirplaneLite/server.txt +++ /dev/null @@ -1 +0,0 @@ -AirplaneLite-MC-Dev-Fixes&AirplaneLite-Data-Structs&Strip-raytracing-for-EntityLiving-hasLineOfSight&Simpler-ShapelessRecipes-comparison-for-Vanilla&Use-unmodifiableMap-instead-of-making-copy&Swap-priority-of-checks-in-chunk-ticking&Reduce-projectile-chunk-loading&Optimize-random-calls-in-chunk-ticking \ No newline at end of file diff --git a/patches/Akarin/LICENSE.md b/patches/Akarin/LICENSE.md new file mode 100644 index 00000000..cc54b50f --- /dev/null +++ b/patches/Akarin/LICENSE.md @@ -0,0 +1,23 @@ +# License + +Akarin inherits its licensing from upstream projects. + +As such, Akarin is licensed under the +[GNU General Public License version 3](licenses/GPL.md); as it inherits it from Paper, +who in turn inherits it from the original Spigot projects. + +Any author who is _not_ listed below should be presumed to have their work released +under the original [GPL](licenses/GPL.md) license. + +In the interest of promoting a better Minecraft platform for everyone, contributors +may choose to release their code under the more permissive [MIT License](licenses/MIT.md). + +The authors listed below have chosen to release their code under the more permissive +[MIT License](licenses/MIT.md). Any contributor who wants their name added below +should submit a Pull Request to this project and add their name. + +```text +Sotr +MatrixTunnel +JosephWorks +``` diff --git a/patches/Akarin/patches/server/0001-Disable-the-Snooper.patch b/patches/Akarin/patches/server/0001-Disable-the-Snooper.patch new file mode 100644 index 00000000..4c915b7d --- /dev/null +++ b/patches/Akarin/patches/server/0001-Disable-the-Snooper.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sotr +Date: Wed, 15 Apr 2020 22:17:18 +0700 +Subject: [PATCH] Disable the Snooper + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 760799782d0cb01e2b14408a9b085f78034ec78d..f6e3bf632a6dd559a58d73eab07f15d6b69c5c13 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1321,6 +1321,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 100) { // Spigot + this.snooper.a(); + } +@@ -1328,6 +1330,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Thu, 2 Apr 2020 11:29:08 +0800 +Subject: [PATCH] Avoid double I/O operation on load player file + + +diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java +index b5cf60495b85c6ae6c32ee8a1c65d80e59fdce3d..1f77b251d7e7b0f023793cbf0876fc067caa75c1 100644 +--- a/src/main/java/net/minecraft/server/WorldNBTStorage.java ++++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java +@@ -49,7 +49,8 @@ public class WorldNBTStorage { + File file = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat"); + // Spigot Start + boolean usingWrongFile = false; +- if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // Paper - Check online mode first ++ boolean normalFile = file.exists() && file.isFile(); // Akarin - ensures normal file ++ if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file + { + file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); + if ( file.exists() ) +@@ -60,7 +61,7 @@ public class WorldNBTStorage { + } + // Spigot End + +- if (file.exists() && file.isFile()) { ++ if (normalFile) { // Akarin - avoid double I/O operation + nbttagcompound = NBTCompressedStreamTools.a(file); + } + // Spigot Start diff --git a/patches/Akarin/server.txt b/patches/Akarin/server.txt deleted file mode 100644 index 36ed5702..00000000 --- a/patches/Akarin/server.txt +++ /dev/null @@ -1 +0,0 @@ -Disable-the-Snooper&Avoid-double-I-O-operation-on-load-player-file&Swaps-the-predicate-order-of-collision \ No newline at end of file diff --git a/patches/Empirecraft/README.md b/patches/Empirecraft/README.md new file mode 100644 index 00000000..7dfc16dd --- /dev/null +++ b/patches/Empirecraft/README.md @@ -0,0 +1,48 @@ +# EmpireCraft +## What +EmpireCraft is a fork of Spigot used by the [Empire Minecraft](https://ref.emc.gs/Aikar?gam=EmpireCraft) Server. + +It contains many gameplay changes to suit our server, but more importantly, contains new performance improvements pending testing to be contributed to Spigot / Paper / Sponge. + +We also have many APIs that we privately use but [choose not to](#why-we-dont-release-all-apis) publicly PR upstream. + +## Why we don't release all APIs +APIs are tough to design. In public projects such as Bukkit, Spigot, Paper, etc., once an API is commited, it's almost forever. You can't go breaking it without solid justification. This is the politics game. + +With that in mind, much thought has to be given to our APIs in current and future use cases and applications to ensure it can be extended without breaking. + +This is a lot of politics that we don't have time in our lives to deal with. + +Therefore, we write APIs to OUR base needs, which is often not 'complete' or 'up to style guidelines' of upstream repositories. We do not have the time to write code that we personally do not need for these APIs. + +We also want to retain the ability to make breaking changes to these APIs if it results in a better way to do things or performance improvements. + +By contributing it upstream, we would give up that power. + +So that is why we have many extremely useful APIs that are not PR'd upstream. + +## License, Support, & Usage of Patches +All patches written by Aikar, Starlis LLC, and/or Contractors of Starlis LLC that are included within EmpireCraft are licensed MIT, and are free to be used in your own fork. + +We offer __ABSOLUTELY NO SUPPORT__ for these patches. If you wish to use them, you must take the effort to extract them from our repo, apply them to your own, and repair any code changes to get it to compile (note: we use a `.callEvent()` utility method on the Event class for nearly all custom events to reduce diff.) + +If we make any breaking changes, and you still wish to use these patches, it's your job to fix the changes! + +So in summary, we love to share! Use anything we wrote in this repo how ever you please, but support it yourself :) + +## OS Support & Scripts +We only directly support the latest LTS Ubuntu for shell scripts. It may work elsewhere... but no promises. + +Many scripts will try to push to our repos, please change that if you fork :) + +### scripts/importmcdev +Imports specific files from mc-dev that CB/Spigot doesn't use, but we need. + +### scripts/generatesources +Generates an mc-dev folder that can be added to IDE for the other mc-dev files located in minecraft-server.jar. + +### scripts/rebuildpatches +Rebuilds patch state from the EmpireCraft-* repo's current state. Automatically handles patch deletion and renaming for you, unlike Spigot's version of these scripts. + +### scripts/applypatches +Takes current patches, i.e., Bukkit & CraftBukkit, and applies them on the latest Spigot upstream. diff --git a/patches/Empirecraft/api.txt b/patches/Empirecraft/api.txt deleted file mode 100644 index 572eeaee..00000000 --- a/patches/Empirecraft/api.txt +++ /dev/null @@ -1 +0,0 @@ -Add-ChatColor.getById \ No newline at end of file diff --git a/patches/Empirecraft/patches/api/0001-Add-ChatColor.getById.patch b/patches/Empirecraft/patches/api/0001-Add-ChatColor.getById.patch new file mode 100644 index 00000000..97aa2a17 --- /dev/null +++ b/patches/Empirecraft/patches/api/0001-Add-ChatColor.getById.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 24 Apr 2017 20:27:23 -0400 +Subject: [PATCH] Add ChatColor.getById + +Bukkit has had a map of this for years and it was totally unused... + +diff --git a/src/main/java/org/bukkit/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java +index 499b222dee1f11d497a29a9a263a5596401ca1eb..c6df76ea58f131ed066fa3cbf2808e6e79785743 100644 +--- a/src/main/java/org/bukkit/ChatColor.java ++++ b/src/main/java/org/bukkit/ChatColor.java +@@ -263,6 +263,15 @@ public enum ChatColor { + return net.md_5.bungee.api.ChatColor.RESET; + }; + ++ /** ++ * Gets the numeric ID associated with this color ++ * ++ * @return An int value of this color code ++ */ ++ public int getId() { ++ return intCode; ++ } ++ + /** + * Gets the char value associated with this color + * +@@ -296,6 +305,18 @@ public enum ChatColor { + return !isFormat && this != RESET; + } + ++ /** ++ * Gets the color represented by the specified color ID ++ * ++ * @param id Code to check ++ * @return Associative {@link org.bukkit.ChatColor} with the given id, ++ * or null if it doesn't exist ++ */ ++ @Nullable // Yatopia ++ public static ChatColor getById(int id) { ++ return BY_ID.get(id); ++ } ++ + /** + * Gets the color represented by the specified color code + * diff --git a/patches/Empirecraft/patches/server/0001-Don-t-trigger-Lootable-Refresh-for-non-player-intera.patch b/patches/Empirecraft/patches/server/0001-Don-t-trigger-Lootable-Refresh-for-non-player-intera.patch new file mode 100644 index 00000000..fcf998e1 --- /dev/null +++ b/patches/Empirecraft/patches/server/0001-Don-t-trigger-Lootable-Refresh-for-non-player-intera.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 15 Jun 2016 22:06:57 -0400 +Subject: [PATCH] Don't trigger Lootable Refresh for non player interaction + + +diff --git a/src/main/java/net/minecraft/server/TileEntityLootable.java b/src/main/java/net/minecraft/server/TileEntityLootable.java +index c6df2318762dc6542e73f18ed9a3172ee31d96ee..525e4730cb40c1ed86012cd75712c428eeb780c2 100644 +--- a/src/main/java/net/minecraft/server/TileEntityLootable.java ++++ b/src/main/java/net/minecraft/server/TileEntityLootable.java +@@ -50,6 +50,7 @@ public abstract class TileEntityLootable extends TileEntityContainer { + } + + public void d(@Nullable EntityHuman entityhuman) { ++ if (entityhuman == null) return; // EMC + if (this.lootableData.shouldReplenish(entityhuman) && this.world.getMinecraftServer() != null) { // Paper + LootTable loottable = this.world.getMinecraftServer().getLootTableRegistry().getLootTable(this.lootTable); + diff --git a/patches/Empirecraft/patches/server/0002-Fix-Bukkit.createInventory-with-type-LECTERN.patch b/patches/Empirecraft/patches/server/0002-Fix-Bukkit.createInventory-with-type-LECTERN.patch new file mode 100644 index 00000000..3310667c --- /dev/null +++ b/patches/Empirecraft/patches/server/0002-Fix-Bukkit.createInventory-with-type-LECTERN.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: willies952002 +Date: Fri, 16 Aug 2019 22:18:35 -0400 +Subject: [PATCH] Fix Bukkit.createInventory() with type LECTERN + +This fixes an issue with Bukkit which makes it possible to open a Lectern interface, but not be able to interact with it (e.g.: change pages). The following changes had to be made: + +nms.TileEntityLectern: +- Add `virtual` flag, this is used to stop calls that would attempt to update a block which we do not have, as well as used in the following change. + +nms.TileEntityLectern$LecternInventory +- in `a(EntityHuman)`, add `(TileEntityLectern.this.virtual && TileEntityLectern.this.hasBook()) ||` to short-circuit the "can use" logic +- Add `getLectern()` method for use in the following change. + +obc.e.CraftHumanEntity#openInventory(Inventory): +- Check if the wrapped inventory is a TileEntityLectern.LecternInventory, and get the Lectern from that + +obc.i.u.CraftTileInventoryConverter$Lectern: +- Mark the created lectern as "virtual" +- Override `getInventory(IInventory)` to return a CraftInventoryLectern. + +This patch is licensed under the MIT License. +License: https://opensource.org/licenses/MIT + +diff --git a/src/main/java/net/minecraft/server/TileEntityLectern.java b/src/main/java/net/minecraft/server/TileEntityLectern.java +index cb6f55c4d870ea1ce146f64ac13e7090f2de3bc8..f7b54111ba86d9ee131e320f573f568cf45fa660 100644 +--- a/src/main/java/net/minecraft/server/TileEntityLectern.java ++++ b/src/main/java/net/minecraft/server/TileEntityLectern.java +@@ -19,6 +19,11 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + // CraftBukkit start - add fields and methods + public final IInventory inventory = new LecternInventory(); + public class LecternInventory implements IInventory { ++ // EMC start ++ public TileEntityLectern getLectern() { ++ return TileEntityLectern.this; ++ } ++ // EMC end + + public List transaction = new ArrayList<>(); + private int maxStack = 1; +@@ -76,7 +81,7 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + + @Override + public ItemStack splitStack(int i, int j) { +- if (i == 0) { ++ if (i == 0 && !TileEntityLectern.this.virtual) { // EMC + ItemStack itemstack = TileEntityLectern.this.book.cloneAndSubtract(j); + + if (TileEntityLectern.this.book.isEmpty()) { +@@ -91,7 +96,7 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + + @Override + public ItemStack splitWithoutUpdate(int i) { +- if (i == 0) { ++ if (i == 0 && !TileEntityLectern.this.virtual) { // EMC + ItemStack itemstack = TileEntityLectern.this.book; + + TileEntityLectern.this.book = ItemStack.b; +@@ -126,7 +131,7 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + + @Override + public boolean a(EntityHuman entityhuman) { +- return TileEntityLectern.this.world.getTileEntity(TileEntityLectern.this.position) != TileEntityLectern.this ? false : (entityhuman.h((double) TileEntityLectern.this.position.getX() + 0.5D, (double) TileEntityLectern.this.position.getY() + 0.5D, (double) TileEntityLectern.this.position.getZ() + 0.5D) > 64.0D ? false : TileEntityLectern.this.hasBook()); ++ return (TileEntityLectern.this.virtual && TileEntityLectern.this.hasBook()) || TileEntityLectern.this.world.getTileEntity(TileEntityLectern.this.position) != TileEntityLectern.this ? false : (entityhuman.h((double) TileEntityLectern.this.position.getX() + 0.5D, (double) TileEntityLectern.this.position.getY() + 0.5D, (double) TileEntityLectern.this.position.getZ() + 0.5D) > 64.0D ? false : TileEntityLectern.this.hasBook()); // EMC + } + + @Override +@@ -159,6 +164,7 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + private ItemStack book; + private int page; + private int maxPage; ++ public boolean virtual = false; // EMC + + public TileEntityLectern() { + super(TileEntityTypes.LECTERN); +@@ -180,6 +186,7 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + } + + private void k() { ++ if (this.virtual) return; // EMC + this.page = 0; + this.maxPage = 0; + BlockLectern.setHasBook(this.getWorld(), this.getPosition(), this.getBlock(), false); +@@ -197,6 +204,7 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv + + if (j != this.page) { + this.page = j; ++ if (this.virtual) return; // EMC + this.update(); + if (this.world != null) BlockLectern.a(this.getWorld(), this.getPosition(), this.getBlock()); // CraftBukkit + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 53917d3b381730efb079113fdffecdc29939d6ea..37d71def297c3f34e9afc74821df6090f3737156 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -32,6 +32,7 @@ import net.minecraft.server.PacketPlayInCloseWindow; + import net.minecraft.server.PacketPlayOutOpenWindow; + import net.minecraft.server.TileEntity; + import net.minecraft.server.TileEntityContainer; ++import net.minecraft.server.TileEntityLectern; // EMC + import org.bukkit.GameMode; + import org.bukkit.Location; + import org.bukkit.Material; +@@ -300,6 +301,11 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (craft.getInventory() instanceof ITileInventory) { + iinventory = (ITileInventory) craft.getInventory(); + } ++ // EMC start ++ if (craft.getInventory() instanceof TileEntityLectern.LecternInventory) { ++ iinventory = ((TileEntityLectern.LecternInventory)craft.getInventory()).getLectern(); ++ } ++ // EMC end + } + + if (iinventory instanceof ITileInventory) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +index ad91eabb47511268d0cf307d941070800ce9bd3f..ea4692ddd3422145d0d33771f5a387b47700d865 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +@@ -126,8 +126,19 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + + @Override + public IInventory getTileEntity() { +- return new TileEntityLectern().inventory; ++ // EMC start ++ TileEntityLectern lectern = new TileEntityLectern(); ++ lectern.virtual = true; ++ return lectern.inventory; ++ // EMC end + } ++ ++ // EMC start ++ @Override ++ public Inventory getInventory(IInventory tileEntity) { ++ return new org.bukkit.craftbukkit.inventory.CraftInventoryLectern(tileEntity); ++ } ++ // EMC end + } + + public static class Smoker extends CraftTileInventoryConverter { diff --git a/patches/Empirecraft/patches/server/0003-dont-load-chunks-for-physics.patch b/patches/Empirecraft/patches/server/0003-dont-load-chunks-for-physics.patch new file mode 100644 index 00000000..b7e8b549 --- /dev/null +++ b/patches/Empirecraft/patches/server/0003-dont-load-chunks-for-physics.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 Mar 2020 03:07:02 -0400 +Subject: [PATCH] dont load chunks for physics + + +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index 657885cdaa086293f6b5aa6f3058acd16df0ba35..8724ad342bec7c733b3c825bd62dbfa5c28c06dd 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -593,7 +593,8 @@ public abstract class BlockBase { + EnumDirection enumdirection = aenumdirection[l]; + + blockposition_mutableblockposition.a((BaseBlockPosition) blockposition, enumdirection); +- IBlockData iblockdata = generatoraccess.getType(blockposition_mutableblockposition); ++ IBlockData iblockdata = generatoraccess.getTypeIfLoaded(blockposition_mutableblockposition); // EMC ++ if (iblockdata == null) continue; // EMC + IBlockData iblockdata1 = iblockdata.updateState(enumdirection.opposite(), this.p(), generatoraccess, blockposition_mutableblockposition, blockposition); + + Block.a(iblockdata, iblockdata1, generatoraccess, blockposition_mutableblockposition, i, j); +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 87a4e53ad1ea1978bc9a0c335293190460efde8b..f2512fd66849cfa743cf7bfdffcb4c26178ffebf 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -774,7 +774,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public void neighborChanged(BlockPosition pos, Block blockIn, BlockPosition fromPos) { a(pos, blockIn, fromPos); } // Paper - OBFHELPER + public void a(BlockPosition blockposition, Block block, BlockPosition blockposition1) { + if (!this.isClientSide) { +- IBlockData iblockdata = this.getType(blockposition); ++ IBlockData iblockdata = this.getTypeIfLoaded(blockposition); // EMC ++ if (iblockdata == null) return; // EMC + + try { + // CraftBukkit start diff --git a/patches/Empirecraft/server.txt b/patches/Empirecraft/server.txt deleted file mode 100644 index ee8e852b..00000000 --- a/patches/Empirecraft/server.txt +++ /dev/null @@ -1 +0,0 @@ -Don-t-trigger-Lootable-Refresh-for-non-player-intera&Fix-Bukkit.createInventory-with-type-LECTERN&dont-load-chunks-for-physics \ No newline at end of file diff --git a/patches/Origami/PATCHES-LICENSE b/patches/Origami/PATCHES-LICENSE new file mode 100644 index 00000000..e72bfdda --- /dev/null +++ b/patches/Origami/PATCHES-LICENSE @@ -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 +. \ No newline at end of file diff --git a/patches/Origami/patches/server/0001-Origami-Server-Config.patch b/patches/Origami/patches/server/0001-Origami-Server-Config.patch new file mode 100644 index 00000000..ef241932 --- /dev/null +++ b/patches/Origami/patches/server/0001-Origami-Server-Config.patch @@ -0,0 +1,198 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 24 Nov 2019 22:41:38 +0100 +Subject: [PATCH] Origami Server Config + + +diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fe7330fabe386966c2d203a190a00a785ea21be0 +--- /dev/null ++++ b/src/main/java/de/minebench/origami/OrigamiConfig.java +@@ -0,0 +1,117 @@ ++package de.minebench.origami; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.configuration.file.YamlConfiguration; ++ ++import java.io.File; ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++import java.util.logging.Level; ++ ++public final class OrigamiConfig { ++ ++ public static final String CONFIG_HEADER = "Configuration file for Origami."; ++ public static final int CURRENT_CONFIG_VERSION = 0; ++ ++ private static final Object[] EMPTY = new Object[0]; ++ ++ private static File configFile; ++ private static YamlConfiguration config; ++ private static int configVersion; ++ ++ public static void init(final File file) { ++ OrigamiConfig.configFile = file; ++ OrigamiConfig.config = new YamlConfiguration(); ++ config.options().header(CONFIG_HEADER); ++ config.options().copyDefaults(true); ++ ++ if (!file.exists()) { ++ try { ++ file.createNewFile(); ++ } catch (final Exception ex) { ++ System.out.println("Failure to create origami config"); ++ ex.printStackTrace(); ++ } ++ } else { ++ try { ++ config.load(file); ++ } catch (final Exception ex) { ++ System.out.println("Failure to load origami config"); ++ throw new RuntimeException(ex); ++ } ++ } ++ ++ OrigamiConfig.load(OrigamiConfig.class, null); ++ } ++ ++ public static void load(Class clazz, Object instance) { ++ OrigamiConfig.configVersion = OrigamiConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); ++ ++ for (final Method method : clazz.getDeclaredMethods()) { ++ if (method.getReturnType() != void.class || method.getParameterCount() != 0 || ++ !Modifier.isPrivate(method.getModifiers()) || (instance == null && !Modifier.isStatic(method.getModifiers()))) { ++ continue; ++ } ++ ++ try { ++ method.setAccessible(true); ++ method.invoke(instance, EMPTY); ++ } catch (final Exception ex) { ++ throw new RuntimeException(ex); ++ } ++ } ++ ++ /* We re-save to add new options */ ++ try { ++ config.save(OrigamiConfig.configFile); ++ } catch (final Exception ex) { ++ System.out.println("Unable to save origami config"); ++ ex.printStackTrace(); ++ } ++ } ++ ++ private static boolean getBoolean(final String path, final boolean dfl) { ++ OrigamiConfig.config.addDefault(path, Boolean.valueOf(dfl)); ++ return OrigamiConfig.config.getBoolean(path, dfl); ++ } ++ ++ private static int getInt(final String path, final int dfl) { ++ OrigamiConfig.config.addDefault(path, Integer.valueOf(dfl)); ++ return OrigamiConfig.config.getInt(path, dfl); ++ } ++ ++ private static double getDouble(final String path, final double dfl) { ++ OrigamiConfig.config.addDefault(path, Double.valueOf(dfl)); ++ return OrigamiConfig.config.getDouble(path, dfl); ++ } ++ ++ public static final class WorldConfig { ++ ++ public final String worldName; ++ ++ public WorldConfig(final String worldName) { ++ this.worldName = worldName; ++ this.init(); ++ } ++ ++ public void init() { ++ load(WorldConfig.class, this); ++ } ++ ++ private boolean getBoolean(final String path, final boolean dfl) { ++ config.addDefault("worlds.default." + path, Boolean.valueOf(dfl)); ++ return config.getBoolean("worlds." + worldName + "." + path, config.getBoolean("worlds.default." + path, dfl)); ++ } ++ ++ private int getInt(final String path, final int dfl) { ++ config.addDefault("worlds.default." + path, Integer.valueOf(dfl)); ++ return config.getInt("worlds." + worldName + "." + path, config.getInt("worlds.default." + path, dfl)); ++ } ++ ++ private double getDouble(final String path, final double dfl) { ++ config.addDefault("worlds.default." + path, Double.valueOf(dfl)); ++ return config.getDouble("worlds." + worldName + "." + path, config.getDouble("worlds.default." + path, dfl)); ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index f2512fd66849cfa743cf7bfdffcb4c26178ffebf..b47bc7cadd34f4592605c1ecfdfcb33e2d580034 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -97,6 +97,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config + public final net.pl3x.purpur.PurpurWorldConfig purpurConfig; // Purpur + ++ public final de.minebench.origami.OrigamiConfig.WorldConfig origamiConfig; // Origami - World Config ++ + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPosition lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; +@@ -199,6 +201,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper + this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config + this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig(((WorldDataServer) worlddatamutable).getName(), env); // Purpur ++ this.origamiConfig = new de.minebench.origami.OrigamiConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Origami - World Config + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur + this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 413c55feb6b1c2ddc80aa8dc1c83ed13b2c4c4a5..53deddce357170a712913d916ba1d58e663fc1a1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -863,6 +863,7 @@ public final class CraftServer implements Server { + com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper + com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config + net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur ++ de.minebench.origami.OrigamiConfig.init((File) console.options.valueOf("origami-settings")); // Origami - Server Config + for (WorldServer world : console.getWorlds()) { + world.worldDataServer.setDifficulty(config.difficulty); + world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); +@@ -899,6 +900,7 @@ public final class CraftServer implements Server { + world.paperConfig.init(); // Paper + world.tuinityConfig.init(); // Tuinity - Server Config + world.purpurConfig.init(); // Purpur ++ world.origamiConfig.init(); // Origami - World Config + } + + Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index b10873022efc8f01ef172e86cad07831d7bf0d5e..448538cc8a3d16b028a0a6f0f05c9370a02f4259 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -154,6 +154,14 @@ public class Main { + .describedAs("Yml file"); + // Purpur end + ++ // Origami Start - Server Config ++ acceptsAll(asList("origami", "origami-settings"), "File for origami settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File("origami.yml")) ++ .describedAs("Yml file"); ++ // Origami end - Server Config ++ + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() +@@ -287,6 +295,7 @@ public class Main { + } + // Paper end + System.setProperty( "library.jansi.version", "Paper" ); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows ++ de.minebench.origami.OrigamiConfig.init((java.io.File) options.valueOf("origami-settings")); // Origami - Server Config + System.out.println("Loading libraries, please wait..."); + net.minecraft.server.Main.main(options); + } catch (Throwable t) { diff --git a/patches/Origami/patches/server/0002-Optimize-inventory-API-item-handling.patch b/patches/Origami/patches/server/0002-Optimize-inventory-API-item-handling.patch new file mode 100644 index 00000000..29e15589 --- /dev/null +++ b/patches/Origami/patches/server/0002-Optimize-inventory-API-item-handling.patch @@ -0,0 +1,63 @@ +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 + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index c2802c5bfb5ec82daad32d3a3375f4428ae76dfd..ec40f1933cf3e9da935d6d6def0e3096f7c00028 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 = getStorageContents(); +- ItemStack filteredItem = CraftItemStack.asCraftCopy(item); + if (item == null) { + return -1; + } ++ // Origami start - Optimize inventory API (moved down from before null check) ++ ItemStack[] inventory = 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 = firstEmpty(); ++ // Origami start - Cache firstEmpty result ++ if (firstFree == -2) { ++ firstFree = firstEmpty(); ++ } ++ // Origami end + + if (firstFree == -1) { + // No space at all! +@@ -320,9 +327,11 @@ public class CraftInventory implements Inventory { + stack.setAmount(getMaxItemStack()); + setItem(firstFree, stack); + item.setAmount(item.getAmount() - getMaxItemStack()); ++ firstFree = -2; // Origami - Cache firstEmpty result + } else { + // Just store it + setItem(firstFree, item); ++ firstFree = -2; // Origami - Cache firstEmpty result + break; + } + } diff --git a/patches/Origami/patches/server/0003-Don-t-load-chunk-with-seed-based-feature-search.patch b/patches/Origami/patches/server/0003-Don-t-load-chunk-with-seed-based-feature-search.patch new file mode 100644 index 00000000..a2686fbd --- /dev/null +++ b/patches/Origami/patches/server/0003-Don-t-load-chunk-with-seed-based-feature-search.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 13 Jan 2020 01:00:49 +0100 +Subject: [PATCH] Don't load chunk with seed based feature search + + +diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java +index fe7330fabe386966c2d203a190a00a785ea21be0..a1a585f5b4b2c10c41b184636149c8dde2a3ae51 100644 +--- a/src/main/java/de/minebench/origami/OrigamiConfig.java ++++ b/src/main/java/de/minebench/origami/OrigamiConfig.java +@@ -112,6 +112,14 @@ public final class OrigamiConfig { + config.addDefault("worlds.default." + path, Double.valueOf(dfl)); + return config.getDouble("worlds." + worldName + "." + path, config.getDouble("worlds.default." + path, dfl)); + } ++ ++ public boolean fastFeatureSearchDontLoad = false; ++ private void fastFeatureSearch() { ++ fastFeatureSearchDontLoad = getBoolean("fast-feature-search-dont-load-chunk", fastFeatureSearchDontLoad); ++ if (fastFeatureSearchDontLoad) { ++ Bukkit.getLogger().info("Returning matching chunk rom fast search directly instead of loading it."); ++ } ++ } + } + + } +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java +index 8fc283f014783b76afda83097201bb7938a1f9fa..250aeaba3c29ed35a9318768dc6d95ebc092ac00 100644 +--- a/src/main/java/net/minecraft/server/StructureGenerator.java ++++ b/src/main/java/net/minecraft/server/StructureGenerator.java +@@ -151,6 +151,11 @@ public abstract class StructureGenerator + } + } + // Paper end ++ // Origami start - seed based feature search doesn't load ++ if (iworldreader instanceof World && ((World) iworldreader).origamiConfig.fastFeatureSearchDontLoad) { ++ return chunkcoordintpair.l(); ++ } ++ // Origami end + IChunkAccess ichunkaccess = iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); + StructureStart structurestart = structuremanager.a(SectionPosition.a(ichunkaccess.getPos(), 0), this, ichunkaccess); + diff --git a/patches/Origami/patches/server/0004-Remove-some-streams-and-object-allocations.patch b/patches/Origami/patches/server/0004-Remove-some-streams-and-object-allocations.patch new file mode 100644 index 00000000..c5037988 --- /dev/null +++ b/patches/Origami/patches/server/0004-Remove-some-streams-and-object-allocations.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Thu, 30 Jan 2020 21:50:40 +0100 +Subject: [PATCH] Remove some streams and object allocations + +Partially based on a patch in Spottedleaf's Paper fork + +diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java +index 7bfb5cb72820c2e42c2fcf889291b04247b09cab..60f184a0ef2f2011b89385a418f10b8300d88892 100644 +--- a/src/main/java/net/minecraft/server/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/ChunkMap.java +@@ -13,9 +13,10 @@ public abstract class ChunkMap extends LightEngineGraph { + + @Override + protected void a(long i, int j, boolean flag) { +- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i); +- int k = chunkcoordintpair.x; +- int l = chunkcoordintpair.z; ++ // Origami start - remove allocation of ChunkCoordIntPair ++ int k = ChunkCoordIntPair.getX(i); ++ int l = ChunkCoordIntPair.getZ(i); ++ // Origami end + + for (int i1 = -1; i1 <= 1; ++i1) { + for (int j1 = -1; j1 <= 1; ++j1) { +@@ -32,9 +33,10 @@ public abstract class ChunkMap extends LightEngineGraph { + @Override + protected int a(long i, long j, int k) { + int l = k; +- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i); +- int i1 = chunkcoordintpair.x; +- int j1 = chunkcoordintpair.z; ++ // Origami start - remove allocation of ChunkCoordIntPair ++ int i1 = ChunkCoordIntPair.getX(i); ++ int j1 = ChunkCoordIntPair.getZ(i); ++ // Origami end + + for (int k1 = -1; k1 <= 1; ++k1) { + for (int l1 = -1; l1 <= 1; ++l1) { diff --git a/patches/Origami/patches/server/0005-Hopper-Optimizations.patch b/patches/Origami/patches/server/0005-Hopper-Optimizations.patch new file mode 100644 index 00000000..ee12ffac --- /dev/null +++ b/patches/Origami/patches/server/0005-Hopper-Optimizations.patch @@ -0,0 +1,255 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 24 Mar 2020 17:40:50 +0100 +Subject: [PATCH] Hopper Optimizations + +- Don't tick empty hoppers: This avoids ticking hoppers that are only used to transport items in a +chain. +- Add config option to increase the full hopper cooldown +- Only set check cooldown if it's bigger than already set cooldown + +diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java +index a1a585f5b4b2c10c41b184636149c8dde2a3ae51..70490d7b7e0dd5fbb5ef475c20223670dc8cf1b2 100644 +--- a/src/main/java/de/minebench/origami/OrigamiConfig.java ++++ b/src/main/java/de/minebench/origami/OrigamiConfig.java +@@ -120,6 +120,13 @@ public final class OrigamiConfig { + Bukkit.getLogger().info("Returning matching chunk rom fast search directly instead of loading it."); + } + } ++ ++ public boolean tickEmptyHoppers = false; ++ public int fullHopperCooldown = 128; ++ private void hopperOptimizations() { ++ tickEmptyHoppers = getBoolean("tick-empty-hoppers", tickEmptyHoppers); ++ fullHopperCooldown = getInt("ticks-per.full-hopper-cooldown", fullHopperCooldown); ++ } + } + + } +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/BlockComposter.java b/src/main/java/net/minecraft/server/BlockComposter.java +index ccb61d656b57f02efaeb60df4a70169aef302eed..55a5999080b831217b88ed3657e95218fe982c18 100644 +--- a/src/main/java/net/minecraft/server/BlockComposter.java ++++ b/src/main/java/net/minecraft/server/BlockComposter.java +@@ -250,6 +250,7 @@ public class BlockComposter extends Block implements IInventoryHolder { + if ((Integer) iblockdata.get(BlockComposter.a) == 7) { + worldserver.setTypeAndData(blockposition, (IBlockData) iblockdata.a((IBlockState) BlockComposter.a), 3); + worldserver.playSound((EntityHuman) null, blockposition, SoundEffects.BLOCK_COMPOSTER_READY, SoundCategory.BLOCKS, 1.0F, 1.0F); ++ TileEntityHopper.enableTicking(worldserver.getTileEntity(new BlockPosition(blockposition.getX(), blockposition.getY() - 1, blockposition.getZ())), 0); // Origami - don't tick empty hoppers + } + + } +diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java +index ec37d2c3b0393c43097bdfc6064ebe3ab8bc28ce..954d37ca9e1079616836d3f441845b37c5a541f3 100644 +--- a/src/main/java/net/minecraft/server/EntityItem.java ++++ b/src/main/java/net/minecraft/server/EntityItem.java +@@ -138,6 +138,13 @@ public class EntityItem extends Entity { + } + } + ++ // Origami start - don't tick empty hoppers ++ if (!world.origamiConfig.tickEmptyHoppers && locY() >= 1 && (this.age < 10 || this.age > this.getDespawnRate() - 10 ++ || (int) locX() != (int) lastX || (int) locZ() != (int) lastZ || (int) locY() != (int) lastY)) { ++ TileEntityHopper.enableTicking(world.getTileEntity(new BlockPosition(locX(), locY() - 1, locZ())), 0); ++ } ++ // Origami end ++ + if (!this.world.isClientSide && this.age >= this.getDespawnRate()) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { +diff --git a/src/main/java/net/minecraft/server/EntityMinecartContainer.java b/src/main/java/net/minecraft/server/EntityMinecartContainer.java +index 574838bd33a46e464ff7d801345613a3351f487f..9b5146fcf618f5bebc71fdfd8e06294a0226e8c2 100644 +--- a/src/main/java/net/minecraft/server/EntityMinecartContainer.java ++++ b/src/main/java/net/minecraft/server/EntityMinecartContainer.java +@@ -137,7 +137,6 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp + if (!itemstack.isEmpty() && itemstack.getCount() > this.getMaxStackSize()) { + itemstack.setCount(this.getMaxStackSize()); + } +- + } + + @Override +@@ -151,7 +150,25 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp + } + + @Override +- public void update() {} ++ public void update() { ++ // Origami start - don't tick empty hoppers ++ checkHopperBelow(); ++ } ++ ++ @Override ++ public void tick() { ++ super.tick(); ++ if (locY() >= 1 && ((int) locX() != (int) lastX || (int) locZ() != (int) lastZ || (int) locY() != (int) lastY)) { ++ checkHopperBelow(); ++ } ++ } ++ ++ private void checkHopperBelow() { ++ if (!world.origamiConfig.tickEmptyHoppers && !this.isEmpty()) { ++ TileEntityHopper.enableTicking(world.getTileEntity(new BlockPosition(locX(), locY() - 1, locZ())), 0); ++ } ++ } ++ // Origami end + + @Override + public boolean a(EntityHuman entityhuman) { +diff --git a/src/main/java/net/minecraft/server/TileEntityContainer.java b/src/main/java/net/minecraft/server/TileEntityContainer.java +index 74390aebd353c969353a6efc0904bafe30774d65..9ce4f340d097132401054a1bb38abb73aa6a5fb1 100644 +--- a/src/main/java/net/minecraft/server/TileEntityContainer.java ++++ b/src/main/java/net/minecraft/server/TileEntityContainer.java +@@ -84,4 +84,14 @@ public abstract class TileEntityContainer extends TileEntity implements IInvento + return new org.bukkit.Location(world.getWorld(), position.getX(), position.getY(), position.getZ()); + } + // CraftBukkit end ++ ++ // Origami start - don't tick empty hoppers ++ @Override ++ public void update() { ++ super.update(); ++ if (world != null) { ++ TileEntityHopper.enableTicking(world.getTileEntity(position.shift(EnumDirection.DOWN)), world.spigotConfig.hopperCheck); ++ } ++ } ++ // Origami end + } +diff --git a/src/main/java/net/minecraft/server/TileEntityHopper.java b/src/main/java/net/minecraft/server/TileEntityHopper.java +index d432de40eba2767f4ced4d9c642c9d2033acd0ea..271c55d8604680cb995a4dd5d7be56ed309c099f 100644 +--- a/src/main/java/net/minecraft/server/TileEntityHopper.java ++++ b/src/main/java/net/minecraft/server/TileEntityHopper.java +@@ -19,8 +19,9 @@ import org.bukkit.inventory.Inventory; + public class TileEntityHopper extends TileEntityLootable implements IHopper, ITickable { + + private NonNullList items; +- private int j; ++ private int j; public int getCooldown() { return this.j; } // Origami - OBFHELPER + private long k; ++ public boolean shouldTick = true; // Origami - don't tick empty hoppers + + // CraftBukkit start - add fields and methods + public List transaction = new java.util.ArrayList(); +@@ -98,7 +99,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + if (itemstack.getCount() > this.getMaxStackSize()) { + itemstack.setCount(this.getMaxStackSize()); + } +- ++ shouldTick = true; // Origami - don't tick empty hoppers + } + + @Override +@@ -108,7 +109,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + + @Override + public void tick() { +- if (this.world != null && !this.world.isClientSide) { ++ if (this.world != null && !this.world.isClientSide && (shouldTick || world.origamiConfig.tickEmptyHoppers)) { // Origami - don't tick empty hoppers + --this.j; + this.k = this.world.getTime(); + if (!this.m()) { +@@ -117,7 +118,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + boolean result = this.a(() -> { + return a((IHopper) this); + }); +- if (!result && this.world.spigotConfig.hopperCheck > 1) { ++ if (!result && this.world.spigotConfig.hopperCheck > 1 && this.world.spigotConfig.hopperCheck > this.getCooldown()) { // Origami - only set check cooldown if it's bigger than already set one + this.setCooldown(this.world.spigotConfig.hopperCheck); + } + // Spigot end +@@ -126,6 +127,26 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + } + ++ // Origami start - don't tick empty hoppers ++ @Override ++ public void update() { ++ shouldTick = true; ++ super.update(); ++ } ++ ++ public static void enableTicking(TileEntity tileEntity, int cooldown) { ++ if (tileEntity instanceof TileEntityHopper) { ++ if (!((TileEntityHopper) tileEntity).shouldTick) { ++ if (((TileEntityHopper) tileEntity).getCooldown() > cooldown && tileEntity.getBlock().get(BlockHopper.ENABLED)) { ++ // Force the hopper to update if it is enabled and didn't tick/decrease cooldown before ++ ((TileEntityHopper) tileEntity).setCooldown(cooldown); ++ } ++ ((TileEntityHopper) tileEntity).shouldTick = true; ++ } ++ } ++ } ++ // Origami end ++ + private boolean a(Supplier supplier) { + if (this.world != null && !this.world.isClientSide) { + if (!this.m() && (Boolean) this.getBlock().get(BlockHopper.ENABLED)) { +@@ -133,6 +154,10 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + + if (!this.isEmpty()) { + flag = this.k(); ++ // Origami - don't tick empty hoppers ++ } else { ++ shouldTick = world.origamiConfig.tickEmptyHoppers; ++ // Origami end + } + + if (!this.j()) { +@@ -212,7 +237,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + } + if (foundItem && world.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown +- this.setCooldown(world.spigotConfig.hopperTransfer); ++ this.setCooldown(world.origamiConfig.fullHopperCooldown > -1 ? world.origamiConfig.fullHopperCooldown : world.spigotConfig.hopperTransfer); // Origami - full hopper cooldown config + } + return false; + } +@@ -252,7 +277,13 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + origItemStack.setCount(origCount); + + if (world.paperConfig.cooldownHopperWhenFull) { +- cooldownHopper(ihopper); ++ // Origami start - full hopper cooldown config ++ if (ihopper instanceof TileEntityHopper) { ++ ((TileEntityHopper) ihopper).setCooldown(world.origamiConfig.fullHopperCooldown > -1 ? world.origamiConfig.fullHopperCooldown : world.spigotConfig.hopperTransfer); ++ } else if (ihopper instanceof EntityMinecartHopper) { ++ ((EntityMinecartHopper) ihopper).setCooldown(world.spigotConfig.hopperTransfer / 2); ++ } ++ // Origami end - full hopper cooldown config + } + + return false; +@@ -460,6 +491,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + entityitem = (EntityItem) iterator.next(); + } while (!a((IInventory) ihopper, entityitem)); + ++ if (ihopper instanceof TileEntityHopper) ((TileEntityHopper) ihopper).shouldTick = true; // Origami - don't tick empty hoppers + return true; + } + } +@@ -548,7 +580,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + itemstack = a(iinventory, iinventory1, itemstack, k, enumdirection); + } + } +- ++ if (iinventory1 instanceof TileEntityHopper) ((TileEntityHopper) iinventory1).shouldTick = true; // Origami - don't tick empty hoppers + return itemstack; + } + +@@ -689,7 +721,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + return (double) this.position.getZ() + 0.5D; + } + +- private void setCooldown(int i) { ++ public void setCooldown(int i) { // Origami - make public + this.j = i; + } + +@@ -716,6 +748,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + BlockPosition blockposition = this.getPosition(); + + if (VoxelShapes.c(VoxelShapes.a(entity.getBoundingBox().d((double) (-blockposition.getX()), (double) (-blockposition.getY()), (double) (-blockposition.getZ()))), this.aa_(), OperatorBoolean.AND)) { ++ enableTicking(this, 0); // Origami - don't tick empty hoppers + this.a(() -> { + return a((IInventory) this, (EntityItem) entity); + }); diff --git a/patches/Origami/patches/server/0006-Add-option-to-disable-observer-clocks.patch b/patches/Origami/patches/server/0006-Add-option-to-disable-observer-clocks.patch new file mode 100644 index 00000000..32d79a9f --- /dev/null +++ b/patches/Origami/patches/server/0006-Add-option-to-disable-observer-clocks.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 31 Mar 2020 21:43:04 +0100 +Subject: [PATCH] Add option to disable observer clocks + + +diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java +index 70490d7b7e0dd5fbb5ef475c20223670dc8cf1b2..bf6b9f4dbd8c3f649b4e059f41c0e72e0d0f70c6 100644 +--- a/src/main/java/de/minebench/origami/OrigamiConfig.java ++++ b/src/main/java/de/minebench/origami/OrigamiConfig.java +@@ -127,6 +127,11 @@ public final class OrigamiConfig { + tickEmptyHoppers = getBoolean("tick-empty-hoppers", tickEmptyHoppers); + fullHopperCooldown = getInt("ticks-per.full-hopper-cooldown", fullHopperCooldown); + } ++ ++ public boolean disableObserverClocks = false; ++ private void observerClock() { ++ disableObserverClocks = getBoolean("disable-observer-clocks", disableObserverClocks); ++ } + } + + } +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/BlockObserver.java b/src/main/java/net/minecraft/server/BlockObserver.java +index 7dfe632523f7fc4426a035b6bf23917b2ea80389..29c3f328f512ffc6c423a5996e1377040f6c4712 100644 +--- a/src/main/java/net/minecraft/server/BlockObserver.java ++++ b/src/main/java/net/minecraft/server/BlockObserver.java +@@ -52,7 +52,8 @@ public class BlockObserver extends BlockDirectional { + + @Override + public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { +- if (iblockdata.get(BlockObserver.FACING) == enumdirection && !(Boolean) iblockdata.get(BlockObserver.b)) { ++ if (iblockdata.get(BlockObserver.FACING) == enumdirection && !(Boolean) iblockdata.get(BlockObserver.b) ++ && (!generatoraccess.getMinecraftWorld().origamiConfig.disableObserverClocks || !(iblockdata1.getBlock() instanceof BlockObserver) || iblockdata1.get(BlockObserver.FACING).opposite() != enumdirection)) { // Origami - disable Observer clocks + this.a(generatoraccess, blockposition); + } + diff --git a/patches/Origami/patches/server/0007-Add-timings-for-Behavior.patch b/patches/Origami/patches/server/0007-Add-timings-for-Behavior.patch new file mode 100644 index 00000000..0e622bf7 --- /dev/null +++ b/patches/Origami/patches/server/0007-Add-timings-for-Behavior.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 20 Apr 2020 17:41:09 +0100 +Subject: [PATCH] Add timings for Behavior + + +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +index 96aaaab5b7685c874463505f9d25e8a0a01a6e7c..5bcbf2e5ee02764425802a4113d28d03a2a49dc8 100644 +--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -140,4 +140,10 @@ public class WorldTimingsHandler { + public static Timing getTickList(WorldServer worldserver, String timingsType) { + return Timings.ofSafe(((WorldDataServer) worldserver.getWorldData()).getName() + " - Scheduled " + timingsType); + } ++ ++ // Origami start - behavior timings ++ public static Timing getBehaviorTimings(String behaviourType) { ++ return Timings.ofSafe("Behavior - " + behaviourType); ++ } ++ // Origami end + } +diff --git a/src/main/java/net/minecraft/server/Behavior.java b/src/main/java/net/minecraft/server/Behavior.java +index 0b9d469a92decfb0632805791868ef7faa88c535..071ce4d470044b3cdf0ef150623f5366875b7e04 100644 +--- a/src/main/java/net/minecraft/server/Behavior.java ++++ b/src/main/java/net/minecraft/server/Behavior.java +@@ -9,6 +9,7 @@ public abstract class Behavior { + protected final Map, MemoryStatus> a; + private Behavior.Status b; public final Behavior.Status getStatus() { return this.b; } // Tuinity - OBFHELPER + private long c; ++ co.aikar.timings.Timing timing; // Origami - behavior timing + private final int d; + private final int e; + +@@ -25,6 +26,10 @@ public abstract class Behavior { + this.d = i; + this.e = j; + this.a = map; ++ String key = getClass().getSimpleName(); // Yatopia Compatible Fix ++ // Origami start - behavior timing ++ timing = co.aikar.timings.WorldTimingsHandler.getBehaviorTimings(key); ++ // Origami end + } + + public Behavior.Status a() { +@@ -37,7 +42,9 @@ public abstract class Behavior { + int j = this.d + worldserver.getRandom().nextInt(this.e + 1 - this.d); + + this.c = i + (long) j; ++ timing.startTiming(); // Origami - behavior timing + this.a(worldserver, e0, i); ++ timing.stopTiming(); // Origami - behavior timing + return true; + } else { + return false; diff --git a/patches/Origami/patches/server/0008-Don-t-wake-up-entities-when-damage-event-is-cancelle.patch b/patches/Origami/patches/server/0008-Don-t-wake-up-entities-when-damage-event-is-cancelle.patch new file mode 100644 index 00000000..16398efc --- /dev/null +++ b/patches/Origami/patches/server/0008-Don-t-wake-up-entities-when-damage-event-is-cancelle.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Thu, 23 Apr 2020 18:03:57 +0100 +Subject: [PATCH] Don't wake up entities when damage event is cancelled + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 0dbd3e0cf72881b766b624621f8dcd0fff3b66b4..93a5c897e9154c26b201177a4372bb6e5af42a78 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -1117,9 +1117,12 @@ public abstract class EntityLiving extends Entity { + } else if (damagesource.isFire() && this.hasEffect(MobEffects.FIRE_RESISTANCE)) { + return false; + } else { +- if (this.isSleeping() && !this.world.isClientSide) { +- this.entityWakeup(); +- } ++ // Origami start - Don't wake up entities when damage event is cancelled ++ // moved down into damageEntity0 ++ //if (this.isSleeping() && !this.world.isClientSide) { ++ // this.entityWakeup(); ++ //} ++ // Origami end + + this.ticksFarFromPlayer = 0; + float f1 = f; +@@ -1865,6 +1868,11 @@ public abstract class EntityLiving extends Entity { + if (event.isCancelled()) { + return false; + } ++ // Origami start - Don't wake up entities when damage event is cancelled ++ if (this.isSleeping() && !this.world.isClientSide) { ++ this.entityWakeup(); ++ } ++ // Origami end + + f = (float) event.getFinalDamage(); + diff --git a/patches/Origami/patches/server/0009-Fix-exp-drop-of-zombie-pigmen-MC-56653.patch b/patches/Origami/patches/server/0009-Fix-exp-drop-of-zombie-pigmen-MC-56653.patch new file mode 100644 index 00000000..c193737e --- /dev/null +++ b/patches/Origami/patches/server/0009-Fix-exp-drop-of-zombie-pigmen-MC-56653.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 7 Jul 2020 00:22:11 +0100 +Subject: [PATCH] Fix exp drop of zombie pigmen (MC-56653) + + +diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java +index bf6b9f4dbd8c3f649b4e059f41c0e72e0d0f70c6..f452cc575adf9137f3b9f1eef1904f8116c7a7ec 100644 +--- a/src/main/java/de/minebench/origami/OrigamiConfig.java ++++ b/src/main/java/de/minebench/origami/OrigamiConfig.java +@@ -132,6 +132,10 @@ public final class OrigamiConfig { + private void observerClock() { + disableObserverClocks = getBoolean("disable-observer-clocks", disableObserverClocks); + } ++ public boolean pigmenDontTargetUnlessHit = false; ++ private void pigmenDontTargetUnlessHit() { ++ pigmenDontTargetUnlessHit = getBoolean("pigmen.dont-target-unless-hit", pigmenDontTargetUnlessHit); ++ } + } + + } +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/EntityPigZombie.java b/src/main/java/net/minecraft/server/EntityPigZombie.java +index 4c050c841f9846cc74fef51d5eb69f4cbb737ef1..7c426f3b1c65f4e2f4997c8b69c27e8df8f334c0 100644 +--- a/src/main/java/net/minecraft/server/EntityPigZombie.java ++++ b/src/main/java/net/minecraft/server/EntityPigZombie.java +@@ -89,7 +89,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + protected void mobTick() { + AttributeModifiable attributemodifiable = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED); + +- if (this.isAngry()) { ++ if (this.isAngry() && !this.world.origamiConfig.pigmenDontTargetUnlessHit) { // Origami - fix MC-56653 + if (!this.isBaby() && !attributemodifiable.a(EntityPigZombie.c)) { + attributemodifiable.b(EntityPigZombie.c); + } diff --git a/patches/Origami/server.txt b/patches/Origami/server.txt deleted file mode 100644 index 8a4cd7f5..00000000 --- a/patches/Origami/server.txt +++ /dev/null @@ -1 +0,0 @@ -Origami-Server-Config&Optimize-inventory-API-item-handling&Don-t-load-chunk-with-seed-based-feature-search&Remove-some-streams-and-object-allocations&Hopper-Optimizations&Add-option-to-disable-observer-clocks&Add-timings-for-Behavior&Don-t-wake-up-entities-when-damage-event-is-cancelle&Fix-exp-drop-of-zombie-pigmen-MC-56653 \ No newline at end of file diff --git a/patches/Purpur/LICENSE b/patches/Purpur/LICENSE new file mode 100644 index 00000000..6c40a956 --- /dev/null +++ b/patches/Purpur/LICENSE @@ -0,0 +1,23 @@ +The MIT License (MIT) +===================== + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/patches/Purpur/api.txt b/patches/Purpur/api.txt deleted file mode 100644 index 60ef49b4..00000000 --- a/patches/Purpur/api.txt +++ /dev/null @@ -1 +0,0 @@ -Default-permissions&Allow-inventory-resizing \ No newline at end of file diff --git a/patches/Purpur/patches/api/0001-Purpur-config-files.patch b/patches/Purpur/patches/api/0001-Purpur-config-files.patch new file mode 100644 index 00000000..257372be --- /dev/null +++ b/patches/Purpur/patches/api/0001-Purpur-config-files.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 18 Feb 2020 20:30:03 -0600 +Subject: [PATCH] Purpur config files + + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index b45ad8df8b7a44c9e6d12326e5ea85e8d166a16c..c69143ba156f6aa3cf3ffb9ee3f6d461867982c5 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1490,6 +1490,18 @@ public interface Server extends PluginMessageRecipient { + } + // Tuinity end - add config to timings report + ++ // Purpur start ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getPurpurConfig() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ ++ @NotNull ++ public java.util.Properties getServerProperties() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Purpur end ++ + /** + * Sends the component to the player + * diff --git a/patches/Purpur/patches/api/0002-Default-permissions.patch b/patches/Purpur/patches/api/0002-Default-permissions.patch new file mode 100644 index 00000000..777f0343 --- /dev/null +++ b/patches/Purpur/patches/api/0002-Default-permissions.patch @@ -0,0 +1,113 @@ +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 permissions + + +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..deedffb4aca00a9ff27a47a09ec7087e5566ad29 +--- /dev/null ++++ b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java +@@ -0,0 +1,76 @@ ++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<>(); ++ ++ private PurpurPermissions() { ++ for (EntityType mob : EntityType.values()) { ++ Class clazz = mob.getEntityClass(); ++ if (clazz != null && clazz.isAssignableFrom(Mob.class)) { ++ 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"); ++ ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.six", "Gives the user six rows of enderchest space", org.bukkit.permissions.PermissionDefault.FALSE); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.five", "Gives the user five rows of enderchest space", org.bukkit.permissions.PermissionDefault.FALSE); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.four", "Gives the user four rows of enderchest space", org.bukkit.permissions.PermissionDefault.FALSE); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.three", "Gives the user three rows of enderchest space", org.bukkit.permissions.PermissionDefault.FALSE); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.two", "Gives the user two rows of enderchest space", org.bukkit.permissions.PermissionDefault.FALSE); ++ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.one", "Gives the user one row of enderchest space", org.bukkit.permissions.PermissionDefault.FALSE); ++ ++ DefaultPermissions.registerPermission(PREFIX + "debug.f3n", "Allows the user to use F3+N keybind to swap gamemodes", PermissionDefault.FALSE, 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.click.opens.editor", "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); ++ 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); ++ 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); ++ DefaultPermissions.registerPermission("allow.powered.creeper", "Allows the user to toggle creeper powered state", PermissionDefault.FALSE, powered); ++ powered.recalculatePermissibles(); ++ ++ purpur.recalculatePermissibles(); ++ return purpur; ++ } ++} diff --git a/patches/Purpur/patches/api/0003-Allow-inventory-resizing.patch b/patches/Purpur/patches/api/0003-Allow-inventory-resizing.patch new file mode 100644 index 00000000..e8f24f5a --- /dev/null +++ b/patches/Purpur/patches/api/0003-Allow-inventory-resizing.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 23 Jul 2019 06:50:55 -0500 +Subject: [PATCH] Allow inventory resizing + + +diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java +index f1e9bc9bc797b7216336d3470e3c696a06f2b21a..2351283df16dac808d77b840aa88732d7b28c0a1 100644 +--- a/src/main/java/org/bukkit/event/inventory/InventoryType.java ++++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java +@@ -132,7 +132,7 @@ public enum InventoryType { + STONECUTTER(2, "Stonecutter") + ; + +- private final int size; ++ private int size; // Purpur - remove final + private final String title; + private final boolean isCreatable; + +@@ -146,6 +146,12 @@ public enum InventoryType { + this.isCreatable = isCreatable; + } + ++ // Purpur start ++ public void setDefaultSize(int size) { ++ this.size = size; ++ } ++ // Purpur end ++ + public int getDefaultSize() { + return size; + } diff --git a/patches/Purpur/patches/api/0004-Advancement-API.patch b/patches/Purpur/patches/api/0004-Advancement-API.patch new file mode 100644 index 00000000..20af6e99 --- /dev/null +++ b/patches/Purpur/patches/api/0004-Advancement-API.patch @@ -0,0 +1,123 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 31 May 2019 21:24:21 -0500 +Subject: [PATCH] Advancement API + + +diff --git a/src/main/java/org/bukkit/advancement/Advancement.java b/src/main/java/org/bukkit/advancement/Advancement.java +index 7c5009974ac8d64d0e738e60cec45acb0d4ca89a..432caadba1b08bb94cdb4ccf552e42400e0db338 100644 +--- a/src/main/java/org/bukkit/advancement/Advancement.java ++++ b/src/main/java/org/bukkit/advancement/Advancement.java +@@ -3,6 +3,7 @@ package org.bukkit.advancement; + import java.util.Collection; + import org.bukkit.Keyed; + import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; + + /** + * Represents an advancement that may be awarded to a player. This class is not +@@ -17,4 +18,12 @@ public interface Advancement extends Keyed { + */ + @NotNull + Collection getCriteria(); ++ ++ /** ++ * Gets the display properties of this advancement ++ * ++ * @return The display properties ++ */ ++ @Nullable ++ AdvancementDisplay getDisplay(); + } +diff --git a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bca3d112e2397b26ba6ccb6cd41e406caae27c5c +--- /dev/null ++++ b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java +@@ -0,0 +1,53 @@ ++package org.bukkit.advancement; ++ ++import org.jetbrains.annotations.NotNull; ++ ++public interface AdvancementDisplay { ++ /** ++ * Get the title of this advancement ++ * ++ * @return Title text ++ */ ++ @NotNull ++ String getTitle(); ++ ++ /** ++ * Get the description of this advancement ++ * ++ * @return Description text ++ */ ++ @NotNull ++ String getDescription(); ++ ++ /** ++ * Get the frame type of this advancement ++ * ++ * @return Frame type ++ */ ++ @NotNull ++ FrameType getFrameType(); ++ ++ /** ++ * Get if this advancement should be announced in chat when completed ++ * ++ * @return True if should announce when completed ++ */ ++ boolean shouldAnnounceToChat(); ++ ++ /** ++ * Set if this advancement should be announced in chat when completed ++ * ++ * @param announce True or false ++ * ++ */ ++ void setShouldAnnounceToChat(boolean announce); ++ ++ /** ++ * Get if this advancement (and all it's children) is hidden from the advancement screen until it has been completed ++ *

++ * This has no effect on root advancements themselves, but will alter their children ++ * ++ * @return True if hidden until completed ++ */ ++ boolean isHidden(); ++} +diff --git a/src/main/java/org/bukkit/advancement/FrameType.java b/src/main/java/org/bukkit/advancement/FrameType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d1757f3d456ff9efce26ce8baa1d16d896908cc2 +--- /dev/null ++++ b/src/main/java/org/bukkit/advancement/FrameType.java +@@ -0,0 +1,27 @@ ++package org.bukkit.advancement; ++ ++import org.bukkit.ChatColor; ++import org.jetbrains.annotations.NotNull; ++ ++public enum FrameType { ++ TASK(ChatColor.GREEN), ++ CHALLENGE(ChatColor.DARK_PURPLE), ++ GOAL(ChatColor.GREEN); ++ ++ private final ChatColor color; ++ ++ FrameType(ChatColor color) { ++ this.color = color; ++ } ++ ++ @NotNull ++ public ChatColor getColor() { ++ return color; ++ } ++ ++ @NotNull ++ @Override ++ public String toString() { ++ return "FrameType[name=" + name() + ",color=" + color + "]"; ++ } ++} diff --git a/patches/Purpur/patches/api/0005-Llama-API.patch b/patches/Purpur/patches/api/0005-Llama-API.patch new file mode 100644 index 00000000..8b336a47 --- /dev/null +++ b/patches/Purpur/patches/api/0005-Llama-API.patch @@ -0,0 +1,191 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 18 Oct 2019 22:50:05 -0500 +Subject: [PATCH] Llama API + + +diff --git a/src/main/java/net/pl3x/purpur/event/entity/LlamaJoinCaravanEvent.java b/src/main/java/net/pl3x/purpur/event/entity/LlamaJoinCaravanEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6e68c1399bf30eeef6ce0385867f0cf258698eae +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/LlamaJoinCaravanEvent.java +@@ -0,0 +1,61 @@ ++package net.pl3x.purpur.event.entity; ++ ++import org.bukkit.entity.Llama; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.entity.EntityEvent; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when a Llama tries to join a caravan. ++ *

++ * Cancelling the event will not let the Llama join. To prevent future attempts ++ * at joining a caravan use {@link Llama#setShouldJoinCaravan(boolean)}. ++ */ ++public class LlamaJoinCaravanEvent extends EntityEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean canceled; ++ private final Llama head; ++ ++ public LlamaJoinCaravanEvent(@NotNull Llama llama, @NotNull Llama head) { ++ super(llama); ++ this.head = head; ++ } ++ ++ @Override ++ @NotNull ++ public Llama getEntity() { ++ return (Llama) entity; ++ } ++ ++ /** ++ * Get the Llama that this Llama is about to follow ++ * ++ * @return Llama about to be followed ++ */ ++ @NotNull ++ public Llama getHead() { ++ return head; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return canceled; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ canceled = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/event/entity/LlamaLeaveCaravanEvent.java b/src/main/java/net/pl3x/purpur/event/entity/LlamaLeaveCaravanEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ec8d978c22835e2789ebaaeddf0d13588ed1122a +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/LlamaLeaveCaravanEvent.java +@@ -0,0 +1,34 @@ ++package net.pl3x.purpur.event.entity; ++ ++import org.bukkit.entity.Llama; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.entity.EntityEvent; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when a Llama leaves a caravan ++ */ ++public class LlamaLeaveCaravanEvent extends EntityEvent { ++ private static final HandlerList handlers = new HandlerList(); ++ ++ public LlamaLeaveCaravanEvent(@NotNull Llama llama) { ++ super(llama); ++ } ++ ++ @Override ++ @NotNull ++ public Llama getEntity() { ++ return (Llama) entity; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/bukkit/entity/Llama.java b/src/main/java/org/bukkit/entity/Llama.java +index d23226ccb0f6c25028f000ce31346cd0a8898e6a..1ef9479c962b3f4f6fed46671a1209c34040d16d 100644 +--- a/src/main/java/org/bukkit/entity/Llama.java ++++ b/src/main/java/org/bukkit/entity/Llama.java +@@ -3,6 +3,7 @@ package org.bukkit.entity; + import com.destroystokyo.paper.entity.RangedEntity; + import org.bukkit.inventory.LlamaInventory; + import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; // Purpur + + /** + * Represents a Llama. +@@ -67,4 +68,65 @@ public interface Llama extends ChestedHorse, RangedEntity { // Paper + @NotNull + @Override + LlamaInventory getInventory(); ++ ++ // Purpur start ++ ++ /** ++ * Check if this Llama should attempt to join a caravan ++ * ++ * @return True if Llama is allowed to join a caravan ++ */ ++ boolean shouldJoinCaravan(); ++ ++ /** ++ * Set if this Llama should attempt to join a caravan ++ * ++ * @param shouldJoinCaravan True to allow joining a caravan ++ */ ++ void setShouldJoinCaravan(boolean shouldJoinCaravan); ++ ++ /** ++ * Check if Llama is in a caravan ++ * ++ * @return True if in caravan ++ */ ++ boolean inCaravan(); ++ ++ /** ++ * Join a caravan ++ * ++ * @param llama Head of caravan to join ++ */ ++ void joinCaravan(@NotNull Llama llama); ++ ++ /** ++ * Leave current caravan if in one ++ */ ++ void leaveCaravan(); ++ ++ /** ++ * Check if another Llama is following this Llama ++ * ++ * @return True if being followed in the caravan ++ */ ++ boolean hasCaravanTail(); ++ ++ /** ++ * Get the Llama that this Llama is following ++ *

++ * Does not necessarily mean the leader of the entire caravan ++ * ++ * @return The Llama being followed ++ */ ++ @Nullable ++ Llama getCaravanHead(); ++ ++ /** ++ * Get the Llama following this Llama, if any ++ * ++ * @return The Llama following this one ++ */ ++ @Nullable ++ Llama getCaravanTail(); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0006-AFK-API.patch b/patches/Purpur/patches/api/0006-AFK-API.patch new file mode 100644 index 00000000..1873b51a --- /dev/null +++ b/patches/Purpur/patches/api/0006-AFK-API.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 10 Aug 2019 22:19:56 -0500 +Subject: [PATCH] AFK API + + +diff --git a/src/main/java/net/pl3x/purpur/event/PlayerAFKEvent.java b/src/main/java/net/pl3x/purpur/event/PlayerAFKEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0c8b3e5e4ba412624357ea5662a78862bd9fc4be +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/PlayerAFKEvent.java +@@ -0,0 +1,70 @@ ++package net.pl3x.purpur.event; ++ ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.PlayerEvent; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class PlayerAFKEvent extends PlayerEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private final boolean setAfk; ++ private boolean shouldKick; ++ private String broadcast; ++ private boolean cancel; ++ ++ public PlayerAFKEvent(@NotNull Player player, boolean setAfk, boolean shouldKick, @Nullable String broadcast, boolean async) { ++ super(player, async); ++ this.setAfk = setAfk; ++ this.shouldKick = shouldKick; ++ this.broadcast = broadcast; ++ } ++ ++ /** ++ * Whether player is going afk or coming back ++ * ++ * @return True if going afk. False is coming back ++ */ ++ public boolean isGoingAfk() { ++ return setAfk; ++ } ++ ++ public boolean shouldKick() { ++ return shouldKick; ++ } ++ ++ public void setShouldKick(boolean shouldKick) { ++ this.shouldKick = shouldKick; ++ } ++ ++ @Nullable ++ public String getBroadcastMsg() { ++ return broadcast; ++ } ++ ++ public void setBroadcastMsg(@Nullable String broadcast) { ++ this.broadcast = broadcast; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java +index cb6464c89e02d29484554a9a2184996a256925d2..7fd2085fa24779df1eab354532611d3642b37a27 100644 +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -1938,4 +1938,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM + @Override + Spigot spigot(); + // Spigot end ++ ++ // Purpur start ++ /** ++ * Check if player is AFK ++ * ++ * @return True if AFK ++ */ ++ boolean isAfk(); ++ ++ /** ++ * Set player as AFK ++ * ++ * @param setAfk Whether to set AFK or not ++ */ ++ void setAfk(boolean setAfk); ++ ++ /** ++ * Reset the idle timer back to 0 ++ */ ++ void resetIdleTimer(); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0007-Bring-back-server-name.patch b/patches/Purpur/patches/api/0007-Bring-back-server-name.patch new file mode 100644 index 00000000..8fed27d7 --- /dev/null +++ b/patches/Purpur/patches/api/0007-Bring-back-server-name.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 26 May 2019 15:18:40 -0500 +Subject: [PATCH] Bring back server name + + +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index fecd7b14d317f55eb1ce7b5c6af9913917971427..d070b420f4cb90610d7018a7fbbc88074268b02b 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -1817,4 +1817,15 @@ public final class Bukkit { + public static Server.Spigot spigot() { + return server.spigot(); + } ++ ++ // Purpur start ++ /** ++ * Get the name of this server ++ * @return the name of the server ++ */ ++ @NotNull ++ public static String getServerName() { ++ return server.getServerName(); ++ } ++ // Purpur end + } +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index c69143ba156f6aa3cf3ffb9ee3f6d461867982c5..dce7d8126c4a628270b09608e4561c419238b0f4 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1602,4 +1602,13 @@ public interface Server extends PluginMessageRecipient { + @NotNull + com.destroystokyo.paper.entity.ai.MobGoals getMobGoals(); + // Paper end ++ ++ // Purpur start ++ /** ++ * Get the name of this server ++ * @return the name of the server ++ */ ++ @NotNull ++ String getServerName(); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0008-ExecuteCommandEvent.patch b/patches/Purpur/patches/api/0008-ExecuteCommandEvent.patch new file mode 100644 index 00000000..d5aca414 --- /dev/null +++ b/patches/Purpur/patches/api/0008-ExecuteCommandEvent.patch @@ -0,0 +1,175 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 31 May 2019 00:08:28 -0500 +Subject: [PATCH] ExecuteCommandEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/ExecuteCommandEvent.java b/src/main/java/net/pl3x/purpur/event/ExecuteCommandEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3250bd4dc29a0cf79b08833d95a3321d1a6733f6 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/ExecuteCommandEvent.java +@@ -0,0 +1,130 @@ ++package net.pl3x.purpur.event; ++ ++import org.apache.commons.lang.Validate; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * This event is called whenever someone runs a command ++ */ ++public class ExecuteCommandEvent extends Event implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean cancel = false; ++ private CommandSender sender; ++ private Command command; ++ private String label; ++ private String[] args; ++ ++ public ExecuteCommandEvent(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @Nullable String[] args) { ++ this.sender = sender; ++ this.command = command; ++ this.label = label; ++ this.args = args; ++ } ++ ++ /** ++ * Gets the command that the player is attempting to execute. ++ * ++ * @return Command the player is attempting to execute ++ */ ++ @NotNull ++ public Command getCommand() { ++ return command; ++ } ++ ++ /** ++ * Sets the command that the player will execute. ++ * ++ * @param command New command that the player will execute ++ * @throws IllegalArgumentException if command is null or empty ++ */ ++ public void setCommand(@NotNull Command command) throws IllegalArgumentException { ++ Validate.notNull(command, "Command cannot be null"); ++ this.command = command; ++ } ++ ++ /** ++ * Gets the sender that this command will be executed as. ++ * ++ * @return Sender this command will be executed as ++ */ ++ @NotNull ++ public CommandSender getSender() { ++ return sender; ++ } ++ ++ /** ++ * Sets the sender that this command will be executed as. ++ * ++ * @param sender New sender which this event will execute as ++ * @throws IllegalArgumentException if the sender provided is null ++ */ ++ public void setSender(@NotNull final CommandSender sender) throws IllegalArgumentException { ++ Validate.notNull(sender, "Sender cannot be null"); ++ this.sender = sender; ++ } ++ ++ /** ++ * Get the label used to execute this command ++ * ++ * @return Label used to execute this command ++ */ ++ @NotNull ++ public String getLabel() { ++ return label; ++ } ++ ++ /** ++ * Set the label used to execute this command ++ * ++ * @param label Label used ++ */ ++ public void setLabel(@NotNull String label) { ++ this.label = label; ++ } ++ ++ /** ++ * Get the args passed to the command ++ * ++ * @return Args passed to the command ++ */ ++ @NotNull ++ public String[] getArgs() { ++ return args; ++ } ++ ++ /** ++ * Set the args passed to the command ++ * ++ * @param args Args passed to the command ++ */ ++ public void setArgs(@NotNull String[] args) { ++ this.args = args; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ @NotNull ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java +index 460fda05a62b12db2edcfb7ea8b2a5dd8e4b110d..1e0eb099933dded131d3c4db8f3cca2b6ed8e064 100644 +--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java ++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java +@@ -147,6 +147,19 @@ public class SimpleCommandMap implements CommandMap { + return false; + } + ++ // Purpur start ++ String[] parsedArgs = Arrays.copyOfRange(args, 1, args.length); ++ net.pl3x.purpur.event.ExecuteCommandEvent event = new net.pl3x.purpur.event.ExecuteCommandEvent(sender, target, sentCommandLabel, parsedArgs); ++ if (!event.callEvent()) { ++ return true; // cancelled ++ } ++ ++ sender = event.getSender(); ++ target = event.getCommand(); ++ sentCommandLabel = event.getLabel(); ++ parsedArgs = event.getArgs(); ++ // Purpur end ++ + // Paper start - Plugins do weird things to workaround normal registration + if (target.timings == null) { + target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target); +@@ -156,7 +169,7 @@ public class SimpleCommandMap implements CommandMap { + try { + try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources + // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) +- target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length)); ++ target.execute(sender, sentCommandLabel, parsedArgs); // Purpur + } // target.timings.stopTiming(); // Spigot // Paper + } catch (CommandException ex) { + server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerCommandException(ex, target, sender, args))); // Paper diff --git a/patches/Purpur/patches/api/0009-LivingEntity-safeFallDistance.patch b/patches/Purpur/patches/api/0009-LivingEntity-safeFallDistance.patch new file mode 100644 index 00000000..71e010cd --- /dev/null +++ b/patches/Purpur/patches/api/0009-LivingEntity-safeFallDistance.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 May 2019 12:58:19 -0500 +Subject: [PATCH] LivingEntity safeFallDistance + + +diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java +index 9f0645dc5f76ee9ef73d88f768025429e5a9edf7..4ccbb3ef3c597ef9da2c6744f410283a1dc2538c 100644 +--- a/src/main/java/org/bukkit/entity/LivingEntity.java ++++ b/src/main/java/org/bukkit/entity/LivingEntity.java +@@ -850,4 +850,20 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource + */ + void setHurtDirection(float hurtDirection); + // Paper end ++ ++ // Purpur start ++ /** ++ * Gets the distance (in blocks) this entity can safely fall without taking damage ++ * ++ * @return Safe fall distance ++ */ ++ float getSafeFallDistance(); ++ ++ /** ++ * Set the distance (in blocks) this entity can safely fall without taking damage ++ * ++ * @param safeFallDistance Safe fall distance ++ */ ++ void setSafeFallDistance(float safeFallDistance); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0010-Lagging-threshold.patch b/patches/Purpur/patches/api/0010-Lagging-threshold.patch new file mode 100644 index 00000000..a0e4cefa --- /dev/null +++ b/patches/Purpur/patches/api/0010-Lagging-threshold.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 23 Jul 2019 10:07:24 -0500 +Subject: [PATCH] Lagging threshold + + +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index d070b420f4cb90610d7018a7fbbc88074268b02b..ba8eb67291c9848b367419f4c8110161ac7fab0d 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -1827,5 +1827,14 @@ public final class Bukkit { + public static String getServerName() { + return server.getServerName(); + } ++ ++ /** ++ * Check if server is lagging according to laggy threshold setting ++ * ++ * @return True if lagging ++ */ ++ public static boolean isLagging() { ++ return server.isLagging(); ++ } + // Purpur end + } +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index dce7d8126c4a628270b09608e4561c419238b0f4..05b47c2462a00451fc64c79c3eda116fc8003a9f 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1610,5 +1610,12 @@ public interface Server extends PluginMessageRecipient { + */ + @NotNull + String getServerName(); ++ ++ /** ++ * Check if server is lagging according to laggy threshold setting ++ * ++ * @return True if lagging ++ */ ++ boolean isLagging(); + // Purpur end + } diff --git a/patches/Purpur/patches/api/0011-ItemFactory-getMonsterEgg.patch b/patches/Purpur/patches/api/0011-ItemFactory-getMonsterEgg.patch new file mode 100644 index 00000000..f475acc8 --- /dev/null +++ b/patches/Purpur/patches/api/0011-ItemFactory-getMonsterEgg.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 5 Jul 2019 16:37:04 -0500 +Subject: [PATCH] ItemFactory#getMonsterEgg + + +diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java +index 3f23927e58e0ccf8cf04d4beb4d83346e3f84730..23d55f756b2bb5a557bfae102d7039d8394fbe69 100644 +--- a/src/main/java/org/bukkit/inventory/ItemFactory.java ++++ b/src/main/java/org/bukkit/inventory/ItemFactory.java +@@ -215,4 +215,15 @@ public interface ItemFactory { + @NotNull + net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(@NotNull org.bukkit.entity.Entity entity, @NotNull net.md_5.bungee.api.chat.BaseComponent[] customName); + // Paper end ++ ++ // Purpur start ++ /** ++ * Get a monster egg ItemStack from an EntityType ++ * ++ * @param type EntityType ++ * @return ItemStack spawner egg ++ */ ++ @Nullable ++ ItemStack getMonsterEgg(@Nullable org.bukkit.entity.EntityType type); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0012-PlayerSetSpawnerTypeWithEggEvent.patch b/patches/Purpur/patches/api/0012-PlayerSetSpawnerTypeWithEggEvent.patch new file mode 100644 index 00000000..51843969 --- /dev/null +++ b/patches/Purpur/patches/api/0012-PlayerSetSpawnerTypeWithEggEvent.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 5 Jul 2019 18:21:15 -0500 +Subject: [PATCH] PlayerSetSpawnerTypeWithEggEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java b/src/main/java/net/pl3x/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c050b75e9a11ac728868fe95e3f89e6b99de6ad2 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java +@@ -0,0 +1,85 @@ ++package net.pl3x.purpur.event; ++ ++import org.bukkit.block.Block; ++import org.bukkit.block.CreatureSpawner; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.PlayerEvent; ++import org.jetbrains.annotations.NotNull; ++ ++public class PlayerSetSpawnerTypeWithEggEvent extends PlayerEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private final Block block; ++ private final CreatureSpawner spawner; ++ private EntityType type; ++ private boolean cancel; ++ ++ public PlayerSetSpawnerTypeWithEggEvent(@NotNull Player player, @NotNull Block block, @NotNull CreatureSpawner spawner, @NotNull EntityType type) { ++ super(player); ++ this.block = block; ++ this.spawner = spawner; ++ this.type = type; ++ } ++ ++ /** ++ * Get the spawner Block in the world ++ * ++ * @return Spawner Block ++ */ ++ @NotNull ++ public Block getBlock() { ++ return block; ++ } ++ ++ /** ++ * Get the spawner state ++ * ++ * @return Spawner state ++ */ ++ @NotNull ++ public CreatureSpawner getSpawner() { ++ return spawner; ++ } ++ ++ /** ++ * Gets the EntityType being set on the spawner ++ * ++ * @return EntityType being set ++ */ ++ @NotNull ++ public EntityType getEntityType() { ++ return type; ++ } ++ ++ /** ++ * Sets the EntityType being set on the spawner ++ * ++ * @param type EntityType to set ++ */ ++ public void setEntityType(@NotNull EntityType type) { ++ this.type = type; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/Purpur/patches/api/0013-EMC-MonsterEggSpawnEvent.patch b/patches/Purpur/patches/api/0013-EMC-MonsterEggSpawnEvent.patch new file mode 100644 index 00000000..8846d22f --- /dev/null +++ b/patches/Purpur/patches/api/0013-EMC-MonsterEggSpawnEvent.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 21 Nov 2016 17:02:11 -0500 +Subject: [PATCH] EMC - MonsterEggSpawnEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/entity/MonsterEggSpawnEvent.java b/src/main/java/net/pl3x/purpur/event/entity/MonsterEggSpawnEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..983d67234b15c83c3785d9fbc191da70cf67ccab +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/MonsterEggSpawnEvent.java +@@ -0,0 +1,67 @@ ++package net.pl3x.purpur.event.entity; ++ ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.bukkit.inventory.ItemStack; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class MonsterEggSpawnEvent extends Event implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean canceled; ++ ++ private final Player player; ++ private LivingEntity entity; ++ private final ItemStack item; ++ ++ public MonsterEggSpawnEvent(@Nullable HumanEntity player, @NotNull LivingEntity entity, @NotNull ItemStack item) { ++ this.player = (Player) player; ++ this.entity = entity; ++ this.item = item; ++ } ++ ++ @Nullable ++ public Player getPlayer() { ++ return player; ++ } ++ ++ @NotNull ++ public LivingEntity getEntity() { ++ return entity; ++ } ++ ++ public void setEntity(@Nullable LivingEntity entity) { ++ if (entity == null) { ++ canceled = true; ++ return; ++ } ++ this.entity = entity; ++ } ++ ++ @NotNull ++ public ItemStack getItem() { ++ return item; ++ } ++ ++ public boolean isCancelled() { ++ return canceled; ++ } ++ ++ public void setCancelled(boolean cancel) { ++ canceled = cancel; ++ } ++ ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/Purpur/patches/api/0014-Villager-resetOffers.patch b/patches/Purpur/patches/api/0014-Villager-resetOffers.patch new file mode 100644 index 00000000..ae3bbedf --- /dev/null +++ b/patches/Purpur/patches/api/0014-Villager-resetOffers.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 7 Oct 2019 00:15:28 -0500 +Subject: [PATCH] Villager#resetOffers + + +diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java +index c8777a476e38ef5e72b6709761990a339eb43d2b..ed703af452cd7db5e47608b4ff6ec049f76ed03a 100644 +--- a/src/main/java/org/bukkit/entity/Villager.java ++++ b/src/main/java/org/bukkit/entity/Villager.java +@@ -113,6 +113,13 @@ public interface Villager extends AbstractVillager { + */ + public void wakeup(); + ++ // Purpur start ++ /** ++ * Reset this villager's trade offers ++ */ ++ public void resetOffers(); ++ // Purpur end ++ + /** + * Represents Villager type, usually corresponding to what biome they spawn + * in. diff --git a/patches/Purpur/patches/api/0015-PaperPR-PlayerItemCooldownEvent.patch b/patches/Purpur/patches/api/0015-PaperPR-PlayerItemCooldownEvent.patch new file mode 100644 index 00000000..1844d2c1 --- /dev/null +++ b/patches/Purpur/patches/api/0015-PaperPR-PlayerItemCooldownEvent.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KennyTV +Date: Mon, 20 Apr 2020 13:57:13 +0200 +Subject: [PATCH] PaperPR - PlayerItemCooldownEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/player/PlayerItemCooldownEvent.java b/src/main/java/net/pl3x/purpur/event/player/PlayerItemCooldownEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2002909f30d2bd833dc13cf09b0bc4bdae0d6757 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/player/PlayerItemCooldownEvent.java +@@ -0,0 +1,77 @@ ++package net.pl3x.purpur.event.player; ++ ++import com.google.common.base.Preconditions; ++import org.bukkit.Material; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.PlayerEvent; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when a player receives a cooldown on an item. ++ */ ++public final class PlayerItemCooldownEvent extends PlayerEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ @NotNull ++ private final Material type; ++ private int cooldown; ++ private boolean cancelled; ++ ++ public PlayerItemCooldownEvent(@NotNull Player player, @NotNull Material type, int cooldown) { ++ super(player); ++ this.type = type; ++ this.cooldown = cooldown; ++ } ++ ++ /** ++ * Get the material affected by the cooldown. ++ * ++ * @return material affected by the cooldown ++ */ ++ @NotNull ++ public Material getType() { ++ return type; ++ } ++ ++ /** ++ * Gets the cooldown in ticks. ++ * ++ * @return cooldown in ticks ++ */ ++ public int getCooldown() { ++ return cooldown; ++ } ++ ++ /** ++ * Sets the cooldown of the material in ticks. ++ * Setting the cooldown to 0 results in removing an already existing cooldown for the material. ++ * ++ * @param cooldown cooldown in ticks, has to be a positive number ++ */ ++ public void setCooldown(int cooldown) { ++ Preconditions.checkArgument(cooldown >= 0, "The cooldown has to be equal to or greater than 0!"); ++ this.cooldown = cooldown; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancelled; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ cancelled = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/Purpur/patches/api/0016-EntityMoveEvent.patch b/patches/Purpur/patches/api/0016-EntityMoveEvent.patch new file mode 100644 index 00000000..7329ace3 --- /dev/null +++ b/patches/Purpur/patches/api/0016-EntityMoveEvent.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 11 Feb 2020 21:56:38 -0600 +Subject: [PATCH] EntityMoveEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/entity/EntityMoveEvent.java b/src/main/java/net/pl3x/purpur/event/entity/EntityMoveEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c48c525b8ee527a5766ac679619fd88956002d64 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/EntityMoveEvent.java +@@ -0,0 +1,95 @@ ++package net.pl3x.purpur.event.entity; ++ ++import com.google.common.base.Preconditions; ++import org.bukkit.Location; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.entity.EntityEvent; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Holds information for living entity movement events ++ */ ++public class EntityMoveEvent extends EntityEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean canceled; ++ private Location from; ++ private Location to; ++ ++ public EntityMoveEvent(@NotNull LivingEntity entity, @NotNull Location from, @NotNull Location to) { ++ super(entity); ++ this.from = from; ++ this.to = to; ++ } ++ ++ @Override ++ @NotNull ++ public LivingEntity getEntity() { ++ return (LivingEntity) entity; ++ } ++ ++ public boolean isCancelled() { ++ return canceled; ++ } ++ ++ public void setCancelled(boolean cancel) { ++ canceled = cancel; ++ } ++ ++ /** ++ * Gets the location this entity moved from ++ * ++ * @return Location the entity moved from ++ */ ++ @NotNull ++ public Location getFrom() { ++ return from; ++ } ++ ++ /** ++ * Sets the location to mark as where the entity moved from ++ * ++ * @param from New location to mark as the entity's previous location ++ */ ++ public void setFrom(@NotNull Location from) { ++ validateLocation(from); ++ this.from = from; ++ } ++ ++ /** ++ * Gets the location this entity moved to ++ * ++ * @return Location the entity moved to ++ */ ++ @NotNull ++ public Location getTo() { ++ return to; ++ } ++ ++ /** ++ * Sets the location that this entity will move to ++ * ++ * @param to New Location this entity will move to ++ */ ++ public void setTo(@NotNull Location to) { ++ validateLocation(to); ++ this.to = to; ++ } ++ ++ private void validateLocation(@NotNull Location loc) { ++ Preconditions.checkArgument(loc != null, "Cannot use null location!"); ++ Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!"); ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/Purpur/patches/api/0017-Player-invulnerabilities.patch b/patches/Purpur/patches/api/0017-Player-invulnerabilities.patch new file mode 100644 index 00000000..24d115ce --- /dev/null +++ b/patches/Purpur/patches/api/0017-Player-invulnerabilities.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 2 May 2020 20:55:31 -0500 +Subject: [PATCH] Player invulnerabilities + + +diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java +index 7fd2085fa24779df1eab354532611d3642b37a27..a79703115da811397ee6b7c6c079846af537fd12 100644 +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -1958,5 +1958,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM + * Reset the idle timer back to 0 + */ + void resetIdleTimer(); ++ ++ /** ++ * Check if player is invulnerable from recently spawning or accepting a resource pack ++ * ++ * @return True if invulnerable ++ */ ++ boolean isSpawnInvulnerable(); ++ ++ /** ++ * Get invulnerable ticks remaining ++ * ++ * @return Invulnerable ticks ++ */ ++ int getSpawnInvulnerableTicks(); ++ ++ /** ++ * Set invulnerable ticks remaining ++ * ++ * @param invulnerableTicks Invulnerable ticks remaining ++ */ ++ void setSpawnInvulnerableTicks(int invulnerableTicks); + // Purpur end + } diff --git a/patches/Purpur/patches/api/0018-Anvil-API.patch b/patches/Purpur/patches/api/0018-Anvil-API.patch new file mode 100644 index 00000000..f660b4e7 --- /dev/null +++ b/patches/Purpur/patches/api/0018-Anvil-API.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 19 Apr 2020 00:25:09 -0500 +Subject: [PATCH] Anvil API + + +diff --git a/src/main/java/net/pl3x/purpur/event/inventory/AnvilTakeResultEvent.java b/src/main/java/net/pl3x/purpur/event/inventory/AnvilTakeResultEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..85663c0a44695f7b7f01a68693cac3d99f4b56ca +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/inventory/AnvilTakeResultEvent.java +@@ -0,0 +1,52 @@ ++package net.pl3x.purpur.event.inventory; ++ ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.entity.Player; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.inventory.InventoryEvent; ++import org.bukkit.inventory.AnvilInventory; ++import org.bukkit.inventory.InventoryView; ++import org.bukkit.inventory.ItemStack; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when a player takes the result item out of an anvil ++ */ ++public class AnvilTakeResultEvent extends InventoryEvent { ++ private static final HandlerList handlers = new HandlerList(); ++ private final Player player; ++ private final ItemStack result; ++ ++ public AnvilTakeResultEvent(@NotNull HumanEntity player, @NotNull InventoryView view, @NotNull ItemStack result) { ++ super(view); ++ this.player = (Player) player; ++ this.result = result; ++ } ++ ++ @NotNull ++ public Player getPlayer() { ++ return player; ++ } ++ ++ @NotNull ++ public ItemStack getResult() { ++ return result; ++ } ++ ++ @NotNull ++ @Override ++ public AnvilInventory getInventory() { ++ return (AnvilInventory) super.getInventory(); ++ } ++ ++ @NotNull ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/event/inventory/AnvilUpdateResultEvent.java b/src/main/java/net/pl3x/purpur/event/inventory/AnvilUpdateResultEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2717ad82ccc0d39c5a69b8890303c245e9a17f83 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/inventory/AnvilUpdateResultEvent.java +@@ -0,0 +1,35 @@ ++package net.pl3x.purpur.event.inventory; ++ ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.inventory.InventoryEvent; ++import org.bukkit.inventory.AnvilInventory; ++import org.bukkit.inventory.InventoryView; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when anvil slots change, triggering the result slot to be updated ++ */ ++public class AnvilUpdateResultEvent extends InventoryEvent { ++ private static final HandlerList handlers = new HandlerList(); ++ ++ public AnvilUpdateResultEvent(@NotNull InventoryView view) { ++ super(view); ++ } ++ ++ @NotNull ++ @Override ++ public AnvilInventory getInventory() { ++ return (AnvilInventory) super.getInventory(); ++ } ++ ++ @NotNull ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/bukkit/inventory/AnvilInventory.java b/src/main/java/org/bukkit/inventory/AnvilInventory.java +index b95e563b5454306a9188ae3295309ee86a756477..435026e533ea9edb8c1800d35c63543ca023a904 100644 +--- a/src/main/java/org/bukkit/inventory/AnvilInventory.java ++++ b/src/main/java/org/bukkit/inventory/AnvilInventory.java +@@ -109,4 +109,14 @@ public interface AnvilInventory extends Inventory { + setItem(2, result); + } + // Paper end ++ ++ // Purpur start ++ boolean canBypassCost(); ++ ++ void setBypassCost(boolean bypassCost); ++ ++ boolean canDoUnsafeEnchants(); ++ ++ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0019-ItemStack-convenience-methods.patch b/patches/Purpur/patches/api/0019-ItemStack-convenience-methods.patch new file mode 100644 index 00000000..1d5242c5 --- /dev/null +++ b/patches/Purpur/patches/api/0019-ItemStack-convenience-methods.patch @@ -0,0 +1,698 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 15 Mar 2020 20:52:12 -0500 +Subject: [PATCH] ItemStack convenience methods + + +diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java +index 4ba991b79f13219182df35b4ce0c5cf57cbd208b..a2e476f154344f9473dd9b48866505448de56d84 100644 +--- a/src/main/java/org/bukkit/Material.java ++++ b/src/main/java/org/bukkit/Material.java +@@ -8645,4 +8645,36 @@ public enum Material implements Keyed { + // + } + } ++ ++ // Purpur start ++ public boolean isArmor() { ++ switch (this) { ++ // ++ case LEATHER_BOOTS: ++ case LEATHER_CHESTPLATE: ++ case LEATHER_HELMET: ++ case LEATHER_LEGGINGS: ++ case CHAINMAIL_BOOTS: ++ case CHAINMAIL_CHESTPLATE: ++ case CHAINMAIL_HELMET: ++ case CHAINMAIL_LEGGINGS: ++ case IRON_BOOTS: ++ case IRON_CHESTPLATE: ++ case IRON_HELMET: ++ case IRON_LEGGINGS: ++ case GOLDEN_BOOTS: ++ case GOLDEN_CHESTPLATE: ++ case GOLDEN_HELMET: ++ case GOLDEN_LEGGINGS: ++ case DIAMOND_BOOTS: ++ case DIAMOND_CHESTPLATE: ++ case DIAMOND_HELMET: ++ case DIAMOND_LEGGINGS: ++ case TURTLE_HELMET: ++ return true; ++ default: ++ return false; ++ } ++ } ++ // Purpur end + } +diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java +index 4f2520f7a4ca6d57a85924ada1068a055b9a01fb..23cef1e67236a879525f39da994efc9a9c5cd289 100644 +--- a/src/main/java/org/bukkit/inventory/ItemStack.java ++++ b/src/main/java/org/bukkit/inventory/ItemStack.java +@@ -17,6 +17,18 @@ import org.bukkit.inventory.meta.ItemMeta; + import org.bukkit.material.MaterialData; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; ++// Purpur start ++import com.google.common.collect.Multimap; ++import java.util.Collection; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.block.data.BlockData; ++import org.bukkit.inventory.meta.BlockDataMeta; ++import org.bukkit.inventory.meta.Repairable; ++import org.bukkit.persistence.PersistentDataContainer; ++import org.bukkit.persistence.PersistentDataHolder; ++import com.destroystokyo.paper.Namespaced; ++// Purpur end + + /** + * Represents a stack of items. +@@ -792,4 +804,627 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { + return itemMeta.hasItemFlag(flag); + } + // Paper end ++ ++ // Purpur start ++ /** ++ * Gets the display name that is set. ++ *

++ * Plugins should check that hasDisplayName() returns true ++ * before calling this method. ++ * ++ * @return the display name that is set ++ */ ++ @NotNull ++ public String getDisplayName() { ++ return getItemMeta().getDisplayName(); ++ } ++ ++ /** ++ * Sets the display name. ++ * ++ * @param name the name to set ++ */ ++ public void setDisplayName(@Nullable String name) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setDisplayName(name); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Checks for existence of a display name. ++ * ++ * @return true if this has a display name ++ */ ++ public boolean hasDisplayName() { ++ return getItemMeta().hasDisplayName(); ++ } ++ ++ /** ++ * Gets the localized display name that is set. ++ *

++ * Plugins should check that hasLocalizedName() returns true ++ * before calling this method. ++ * ++ * @return the localized name that is set ++ */ ++ @NotNull ++ public String getLocalizedName() { ++ return getItemMeta().getLocalizedName(); ++ } ++ ++ /** ++ * Sets the localized name. ++ * ++ * @param name the name to set ++ */ ++ public void setLocalizedName(@Nullable String name) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setLocalizedName(name); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Checks for existence of a localized name. ++ * ++ * @return true if this has a localized name ++ */ ++ public boolean hasLocalizedName() { ++ return getItemMeta().hasLocalizedName(); ++ } ++ ++ /** ++ * Checks for existence of lore. ++ * ++ * @return true if this has lore ++ */ ++ public boolean hasLore() { ++ return getItemMeta().hasLore(); ++ } ++ ++ /** ++ * Checks for existence of the specified enchantment. ++ * ++ * @param ench enchantment to check ++ * @return true if this enchantment exists for this meta ++ */ ++ public boolean hasEnchant(@NotNull Enchantment ench) { ++ return getItemMeta().hasEnchant(ench); ++ } ++ ++ /** ++ * Checks for the level of the specified enchantment. ++ * ++ * @param ench enchantment to check ++ * @return The level that the specified enchantment has, or 0 if none ++ */ ++ public int getEnchantLevel(@NotNull Enchantment ench) { ++ return getItemMeta().getEnchantLevel(ench); ++ } ++ ++ /** ++ * Returns a copy the enchantments in this ItemMeta.
++ * Returns an empty map if none. ++ * ++ * @return An immutable copy of the enchantments ++ */ ++ @NotNull ++ public Map getEnchants() { ++ return getItemMeta().getEnchants(); ++ } ++ ++ /** ++ * Adds the specified enchantment to this item meta. ++ * ++ * @param ench Enchantment to add ++ * @param level Level for the enchantment ++ * @param ignoreLevelRestriction this indicates the enchantment should be ++ * applied, ignoring the level limit ++ * @return true if the item meta changed as a result of this call, false ++ * otherwise ++ */ ++ public boolean addEnchant(@NotNull Enchantment ench, int level, boolean ignoreLevelRestriction) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ /** ++ * Removes the specified enchantment from this item meta. ++ * ++ * @param ench Enchantment to remove ++ * @return true if the item meta changed as a result of this call, false ++ * otherwise ++ */ ++ public boolean removeEnchant(@NotNull Enchantment ench) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeEnchant(ench); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ /** ++ * Checks for the existence of any enchantments. ++ * ++ * @return true if an enchantment exists on this meta ++ */ ++ public boolean hasEnchants() { ++ return getItemMeta().hasEnchants(); ++ } ++ ++ /** ++ * Checks if the specified enchantment conflicts with any enchantments in ++ * this ItemMeta. ++ * ++ * @param ench enchantment to test ++ * @return true if the enchantment conflicts, false otherwise ++ */ ++ public boolean hasConflictingEnchant(@NotNull Enchantment ench) { ++ return getItemMeta().hasConflictingEnchant(ench); ++ } ++ ++ /** ++ * Sets the custom model data. ++ *

++ * CustomModelData is an integer that may be associated client side with a ++ * custom item model. ++ * ++ * @param data the data to set, or null to clear ++ */ ++ public void setCustomModelData(@Nullable Integer data) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setCustomModelData(data); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Gets the custom model data that is set. ++ *

++ * CustomModelData is an integer that may be associated client side with a ++ * custom item model. ++ *

++ * Plugins should check that hasCustomModelData() returns true ++ * before calling this method. ++ * ++ * @return the localized name that is set ++ */ ++ public int getCustomModelData() { ++ return getItemMeta().getCustomModelData(); ++ } ++ ++ /** ++ * Checks for existence of custom model data. ++ *

++ * CustomModelData is an integer that may be associated client side with a ++ * custom item model. ++ * ++ * @return true if this has custom model data ++ */ ++ public boolean hasCustomModelData() { ++ return getItemMeta().hasCustomModelData(); ++ } ++ ++ /** ++ * Returns whether the item has block data currently attached to it. ++ * ++ * @return whether block data is already attached ++ */ ++ public boolean hasBlockData() { ++ return ((BlockDataMeta) getItemMeta()).hasBlockData(); ++ } ++ ++ /** ++ * Returns the currently attached block data for this item or creates a new ++ * one if one doesn't exist. ++ * ++ * The state is a copy, it must be set back (or to another item) with ++ * {@link #setBlockData(BlockData)} ++ * ++ * @param material the material we wish to get this data in the context of ++ * @return the attached data or new data ++ */ ++ @NotNull ++ public BlockData getBlockData(@NotNull Material material) { ++ return ((BlockDataMeta) getItemMeta()).getBlockData(material); ++ } ++ ++ /** ++ * Attaches a copy of the passed block data to the item. ++ * ++ * @param blockData the block data to attach to the block. ++ * @throws IllegalArgumentException if the blockData is null or invalid for ++ * this item. ++ */ ++ public void setBlockData(@NotNull BlockData blockData) { ++ ItemMeta itemMeta = getItemMeta(); ++ ((BlockDataMeta) itemMeta).setBlockData(blockData); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Gets the repair penalty ++ * ++ * @return the repair penalty ++ */ ++ public int getRepairCost() { ++ return ((Repairable) getItemMeta()).getRepairCost(); ++ } ++ ++ /** ++ * Sets the repair penalty ++ * ++ * @param cost repair penalty ++ */ ++ public void setRepairCost(int cost) { ++ ItemMeta itemMeta = getItemMeta(); ++ ((Repairable) itemMeta).setRepairCost(cost); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Checks to see if this has a repair penalty ++ * ++ * @return true if this has a repair penalty ++ */ ++ public boolean hasRepairCost() { ++ return ((Repairable) getItemMeta()).hasRepairCost(); ++ } ++ ++ /** ++ * Return if the unbreakable tag is true. An unbreakable item will not lose ++ * durability. ++ * ++ * @return true if the unbreakable tag is true ++ */ ++ public boolean isUnbreakable() { ++ return getItemMeta().isUnbreakable(); ++ } ++ ++ /** ++ * Sets the unbreakable tag. An unbreakable item will not lose durability. ++ * ++ * @param unbreakable true if set unbreakable ++ */ ++ public void setUnbreakable(boolean unbreakable) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setUnbreakable(unbreakable); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Checks for the existence of any AttributeModifiers. ++ * ++ * @return true if any AttributeModifiers exist ++ */ ++ public boolean hasAttributeModifiers() { ++ return getItemMeta().hasAttributeModifiers(); ++ } ++ ++ /** ++ * Return an immutable copy of all Attributes and ++ * their modifiers in this ItemMeta.
++ * Returns null if none exist. ++ * ++ * @return an immutable {@link Multimap} of Attributes ++ * and their AttributeModifiers, or null if none exist ++ */ ++ @Nullable ++ public Multimap getAttributeModifiers() { ++ return getItemMeta().getAttributeModifiers(); ++ } ++ ++ /** ++ * Return an immutable copy of all {@link Attribute}s and their ++ * {@link AttributeModifier}s for a given {@link EquipmentSlot}.
++ * Any {@link AttributeModifier} that does have have a given ++ * {@link EquipmentSlot} will be returned. This is because ++ * AttributeModifiers without a slot are active in any slot.
++ * If there are no attributes set for the given slot, an empty map ++ * will be returned. ++ * ++ * @param slot the {@link EquipmentSlot} to check ++ * @return the immutable {@link Multimap} with the ++ * respective Attributes and modifiers, or an empty map ++ * if no attributes are set. ++ */ ++ @NotNull ++ public Multimap getAttributeModifiers(@Nullable EquipmentSlot slot) { ++ return getItemMeta().getAttributeModifiers(slot); ++ } ++ ++ /** ++ * Return an immutable copy of all {@link AttributeModifier}s ++ * for a given {@link Attribute} ++ * ++ * @param attribute the {@link Attribute} ++ * @return an immutable collection of {@link AttributeModifier}s ++ * or null if no AttributeModifiers exist for the Attribute. ++ * @throws NullPointerException if Attribute is null ++ */ ++ @Nullable ++ public Collection getAttributeModifiers(@NotNull Attribute attribute) { ++ return getItemMeta().getAttributeModifiers(attribute); ++ } ++ ++ /** ++ * Add an Attribute and it's Modifier. ++ * AttributeModifiers can now support {@link EquipmentSlot}s. ++ * If not set, the {@link AttributeModifier} will be active in ALL slots. ++ *
++ * Two {@link AttributeModifier}s that have the same {@link java.util.UUID} ++ * cannot exist on the same Attribute. ++ * ++ * @param attribute the {@link Attribute} to modify ++ * @param modifier the {@link AttributeModifier} specifying the modification ++ * @return true if the Attribute and AttributeModifier were ++ * successfully added ++ * @throws NullPointerException if Attribute is null ++ * @throws NullPointerException if AttributeModifier is null ++ * @throws IllegalArgumentException if AttributeModifier already exists ++ */ ++ public boolean addAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.addAttributeModifier(attribute, modifier); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ /** ++ * Set all {@link Attribute}s and their {@link AttributeModifier}s. ++ * To clear all currently set Attributes and AttributeModifiers use ++ * null or an empty Multimap. ++ * If not null nor empty, this will filter all entries that are not-null ++ * and add them to the ItemStack. ++ * ++ * @param attributeModifiers the new Multimap containing the Attributes ++ * and their AttributeModifiers ++ */ ++ public void setAttributeModifiers(@Nullable Multimap attributeModifiers) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setAttributeModifiers(attributeModifiers); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Remove all {@link AttributeModifier}s associated with the given ++ * {@link Attribute}. ++ * This will return false if nothing was removed. ++ * ++ * @param attribute attribute to remove ++ * @return true if all modifiers were removed from a given ++ * Attribute. Returns false if no attributes were ++ * removed. ++ * @throws NullPointerException if Attribute is null ++ */ ++ public boolean removeAttributeModifier(@NotNull Attribute attribute) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeAttributeModifier(attribute); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ /** ++ * Remove all {@link Attribute}s and {@link AttributeModifier}s for a ++ * given {@link EquipmentSlot}.
++ * If the given {@link EquipmentSlot} is null, this will remove all ++ * {@link AttributeModifier}s that do not have an EquipmentSlot set. ++ * ++ * @param slot the {@link EquipmentSlot} to clear all Attributes and ++ * their modifiers for ++ * @return true if all modifiers were removed that match the given ++ * EquipmentSlot. ++ */ ++ public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeAttributeModifier(slot); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ /** ++ * Remove a specific {@link Attribute} and {@link AttributeModifier}. ++ * AttributeModifiers are matched according to their {@link java.util.UUID}. ++ * ++ * @param attribute the {@link Attribute} to remove ++ * @param modifier the {@link AttributeModifier} to remove ++ * @return if any attribute modifiers were remove ++ * ++ * @throws NullPointerException if the Attribute is null ++ * @throws NullPointerException if the AttributeModifier is null ++ * ++ * @see AttributeModifier#getUniqueId() ++ */ ++ public boolean removeAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) { ++ ItemMeta itemMeta = getItemMeta(); ++ boolean result = itemMeta.removeAttributeModifier(attribute, modifier); ++ setItemMeta(itemMeta); ++ return result; ++ } ++ ++ /** ++ * Returns a custom tag container capable of storing tags on the object. ++ * ++ * Note that the tags stored on this container are all stored under their ++ * own custom namespace therefore modifying default tags using this ++ * {@link PersistentDataHolder} is impossible. ++ * ++ * @return the persistent metadata container ++ */ ++ @NotNull ++ public PersistentDataContainer getPersistentDataContainer() { ++ return getItemMeta().getPersistentDataContainer(); ++ } ++ ++ /** ++ * Checks to see if this item has damage ++ * ++ * @return true if this has damage ++ */ ++ public boolean hasDamage() { ++ return ((Damageable) getItemMeta()).hasDamage(); ++ } ++ ++ /** ++ * Gets the damage ++ * ++ * @return the damage ++ */ ++ public int getDamage() { ++ return ((Damageable) getItemMeta()).getDamage(); ++ } ++ ++ /** ++ * Sets the damage ++ * ++ * @param damage item damage ++ */ ++ public void setDamage(int damage) { ++ ItemMeta itemMeta = getItemMeta(); ++ ((Damageable) itemMeta).setDamage(damage); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Gets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @return Set of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ @NotNull ++ public Set getDestroyableKeys() { ++ return getItemMeta().getDestroyableKeys(); ++ } ++ ++ /** ++ * Sets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @param canDestroy Collection of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ public void setDestroyableKeys(@NotNull Collection canDestroy) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setDestroyableKeys(canDestroy); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Gets the collection of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @return Set of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ @NotNull ++ public Set getPlaceableKeys() { ++ return getItemMeta().getPlaceableKeys(); ++ } ++ ++ /** ++ * Sets the set of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @param canPlaceOn Collection of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ @NotNull ++ public void setPlaceableKeys(@NotNull Collection canPlaceOn) { ++ ItemMeta itemMeta = getItemMeta(); ++ itemMeta.setPlaceableKeys(canPlaceOn); ++ setItemMeta(itemMeta); ++ } ++ ++ /** ++ * Checks for the existence of any keys that the item can be placed on ++ * ++ * @return true if this item has placeable keys ++ */ ++ public boolean hasPlaceableKeys() { ++ return getItemMeta().hasPlaceableKeys(); ++ } ++ ++ /** ++ * Checks for the existence of any keys that the item can destroy ++ * ++ * @return true if this item has destroyable keys ++ */ ++ public boolean hasDestroyableKeys() { ++ return getItemMeta().hasDestroyableKeys(); ++ } ++ ++ /** ++ * Repairs this item by 1 durability ++ */ ++ public void repair() { ++ repair(1); ++ } ++ ++ /** ++ * Damages this item by 1 durability ++ * ++ * @return True if damage broke the item ++ */ ++ public boolean damage() { ++ return damage(1); ++ } ++ ++ /** ++ * Repairs this item's durability by amount ++ * ++ * @param amount Amount of durability to repair ++ */ ++ public void repair(int amount) { ++ damage(-amount); ++ } ++ ++ /** ++ * Damages this item's durability by amount ++ * ++ * @param amount Amount of durability to damage ++ * @return True if damage broke the item ++ */ ++ public boolean damage(int amount) { ++ return damage(amount, false); ++ } ++ ++ /** ++ * Damages this item's durability by amount ++ * ++ * @param amount Amount of durability to damage ++ * @param ignoreUnbreaking Ignores unbreaking enchantment ++ * @return True if damage broke the item ++ */ ++ public boolean damage(int amount, boolean ignoreUnbreaking) { ++ Damageable damageable = (Damageable) getItemMeta(); ++ if (amount > 0) { ++ int unbreaking = getEnchantLevel(Enchantment.DURABILITY); ++ int reduce = 0; ++ for (int i = 0; unbreaking > 0 && i < amount; ++i) { ++ if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) { ++ ++reduce; ++ } ++ } ++ amount -= reduce; ++ if (amount <= 0) { ++ return isBroke(damageable.getDamage()); ++ } ++ } ++ int damage = damageable.getDamage() + amount; ++ damageable.setDamage(damage); ++ setItemMeta((ItemMeta) damageable); ++ return isBroke(damage); ++ } ++ ++ public boolean isBroke(int damage) { ++ if (damage > getType().getMaxDurability()) { ++ if (getAmount() > 0) { ++ // ensure it "breaks" ++ setAmount(0); ++ } ++ return true; ++ } ++ return false; ++ } ++ ++ private boolean reduceDamage(java.util.Random random, int unbreaking) { ++ if (getType().isArmor()) { ++ return random.nextFloat() < 0.6F; ++ } ++ return random.nextInt(unbreaking + 1) > 0; ++ } ++ ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0020-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/Purpur/patches/api/0020-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch new file mode 100644 index 00000000..bdaed9c0 --- /dev/null +++ b/patches/Purpur/patches/api/0020-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 28 Jun 2020 21:50:55 -0500 +Subject: [PATCH] Phantoms attracted to crystals and crystals shoot phantoms + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +index b42091752981a1f309ab350e9a394092cb334824..83c51bb5e09549a8205d27a53ff0102f9439d60a 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -206,4 +206,8 @@ public interface VanillaGoal extends Goal { + GoalKey ZOMBIE_ATTACK = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack")); + GoalKey STROLL_VILLAGE_GOLEM = GoalKey.of(Creature.class, NamespacedKey.minecraft("stroll_village_golem")); + GoalKey UNIVERSAL_ANGER_RESET = GoalKey.of(Mob.class, NamespacedKey.minecraft("universal_anger_reset")); ++ // Purpur start ++ GoalKey FIND_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal_goal")); ++ GoalKey ORBIT_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal_goal")); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0021-ChatColor-conveniences.patch b/patches/Purpur/patches/api/0021-ChatColor-conveniences.patch new file mode 100644 index 00000000..9c0f19ba --- /dev/null +++ b/patches/Purpur/patches/api/0021-ChatColor-conveniences.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 12:43:25 -0500 +Subject: [PATCH] ChatColor conveniences + + +diff --git a/src/main/java/org/bukkit/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java +index 4594701d77c5d0f744bece871b98d9f6f73eb5a7..499b222dee1f11d497a29a9a263a5596401ca1eb 100644 +--- a/src/main/java/org/bukkit/ChatColor.java ++++ b/src/main/java/org/bukkit/ChatColor.java +@@ -413,4 +413,30 @@ public enum ChatColor { + BY_CHAR.put(color.code, color); + } + } ++ ++ // Purpur start ++ public static final Pattern HEX_PATTERN = Pattern.compile("(#[A-Fa-f0-9]{6})"); ++ ++ @Nullable ++ public static String replaceHex(@Nullable String str) { ++ if (str != null) { ++ java.util.regex.Matcher matcher = HEX_PATTERN.matcher(str); ++ while (matcher.find()) { ++ String group = matcher.group(1); ++ str = str.replace(group, net.md_5.bungee.api.ChatColor.of(group).toString()); ++ } ++ } ++ return str; ++ } ++ ++ @Nullable ++ public static String color(@Nullable String str) { ++ return color(str, true); ++ } ++ ++ @Nullable ++ public static String color(@Nullable String str, boolean parseHex) { ++ return str != null ? net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', parseHex ? replaceHex(str) : str) : str; ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0022-DragonEggPlaceEvent.patch b/patches/Purpur/patches/api/0022-DragonEggPlaceEvent.patch new file mode 100644 index 00000000..562967ff --- /dev/null +++ b/patches/Purpur/patches/api/0022-DragonEggPlaceEvent.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 30 Jul 2020 18:15:04 -0500 +Subject: [PATCH] DragonEggPlaceEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/block/DragonEggPlaceEvent.java b/src/main/java/net/pl3x/purpur/event/block/DragonEggPlaceEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bdabfd2b5f64b0e65c4eb09958282962620cdda2 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/block/DragonEggPlaceEvent.java +@@ -0,0 +1,47 @@ ++package net.pl3x.purpur.event.block; ++ ++import org.bukkit.Location; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++ ++public class DragonEggPlaceEvent extends Event implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private Location location; ++ private boolean cancelled; ++ ++ public DragonEggPlaceEvent(@NotNull Location location) { ++ this.location = location; ++ } ++ ++ @NotNull ++ public Location getLocation() { ++ return location; ++ } ++ ++ public void setLocation(@NotNull Location location) { ++ this.location = location; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancelled; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ cancelled = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/Purpur/patches/api/0023-Ridables.patch b/patches/Purpur/patches/api/0023-Ridables.patch new file mode 100644 index 00000000..fc251fc3 --- /dev/null +++ b/patches/Purpur/patches/api/0023-Ridables.patch @@ -0,0 +1,210 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 4 May 2019 00:57:16 -0500 +Subject: [PATCH] Ridables + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +index 83c51bb5e09549a8205d27a53ff0102f9439d60a..177143c9764e82981423879ed35625edd25d3ebf 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -209,5 +209,7 @@ public interface VanillaGoal extends Goal { + // Purpur start + GoalKey FIND_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal_goal")); + GoalKey ORBIT_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal_goal")); ++ GoalKey HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider")); ++ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); + // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/event/entity/RidableMoveEvent.java b/src/main/java/net/pl3x/purpur/event/entity/RidableMoveEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..48e7ac392fe5efac8a4ce549e31a05ed817417e4 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/RidableMoveEvent.java +@@ -0,0 +1,103 @@ ++package net.pl3x.purpur.event.entity; ++ ++import com.google.common.base.Preconditions; ++import org.bukkit.Location; ++import org.bukkit.entity.Mob; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.entity.EntityEvent; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Triggered when a ridable mob moves with a rider ++ */ ++public class RidableMoveEvent extends EntityEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean canceled; ++ private final Player rider; ++ private Location from; ++ private Location to; ++ ++ public RidableMoveEvent(@NotNull Mob entity, @NotNull Player rider, @NotNull Location from, @NotNull Location to) { ++ super(entity); ++ this.rider = rider; ++ this.from = from; ++ this.to = to; ++ } ++ ++ @Override ++ @NotNull ++ public Mob getEntity() { ++ return (Mob) entity; ++ } ++ ++ @NotNull ++ public Player getRider() { ++ return rider; ++ } ++ ++ public boolean isCancelled() { ++ return canceled; ++ } ++ ++ public void setCancelled(boolean cancel) { ++ canceled = cancel; ++ } ++ ++ /** ++ * Gets the location this entity moved from ++ * ++ * @return Location the entity moved from ++ */ ++ @NotNull ++ public Location getFrom() { ++ return from; ++ } ++ ++ /** ++ * Sets the location to mark as where the entity moved from ++ * ++ * @param from New location to mark as the entity's previous location ++ */ ++ public void setFrom(@NotNull Location from) { ++ validateLocation(from); ++ this.from = from; ++ } ++ ++ /** ++ * Gets the location this entity moved to ++ * ++ * @return Location the entity moved to ++ */ ++ @NotNull ++ public Location getTo() { ++ return to; ++ } ++ ++ /** ++ * Sets the location that this entity will move to ++ * ++ * @param to New Location this entity will move to ++ */ ++ public void setTo(@NotNull Location to) { ++ validateLocation(to); ++ this.to = to; ++ } ++ ++ private void validateLocation(@NotNull Location loc) { ++ Preconditions.checkArgument(loc != null, "Cannot use null location!"); ++ Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!"); ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/event/entity/RidableSpacebarEvent.java b/src/main/java/net/pl3x/purpur/event/entity/RidableSpacebarEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0ec5a130985e8da4cc9e596a6b70503d2550f77 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/RidableSpacebarEvent.java +@@ -0,0 +1,37 @@ ++package net.pl3x.purpur.event.entity; ++ ++import org.bukkit.entity.Entity; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.entity.EntityEvent; ++import org.jetbrains.annotations.NotNull; ++ ++public class RidableSpacebarEvent extends EntityEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean cancelled; ++ ++ public RidableSpacebarEvent(@NotNull Entity entity) { ++ super(entity); ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancelled; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ cancelled = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index 76e857c364fe79e20cf9bde54b65e5b7108174fd..5f7947cd6f3bf9f76e8b3bac339f61b9afadaaad 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -698,4 +698,35 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + */ + public boolean isTicking(); + // Paper end ++ ++ // Purpur start ++ /** ++ * Get the riding player ++ * ++ * @return Riding player ++ */ ++ @Nullable ++ Player getRider(); ++ ++ /** ++ * Check if entity is being ridden ++ * ++ * @return True if being ridden ++ */ ++ boolean hasRider(); ++ ++ /** ++ * Check if entity is ridable ++ * ++ * @return True if ridable ++ */ ++ boolean isRidable(); ++ ++ /** ++ * Check if entity is ridable in water ++ * ++ * @return True if ridable in water ++ */ ++ boolean isRidableInWater(); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0024-Configurable-permission-message-upgrades.patch b/patches/Purpur/patches/api/0024-Configurable-permission-message-upgrades.patch new file mode 100644 index 00000000..bda8c7b7 --- /dev/null +++ b/patches/Purpur/patches/api/0024-Configurable-permission-message-upgrades.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 15 Aug 2020 11:18:27 -0500 +Subject: [PATCH] Configurable permission message upgrades + +This allows the configurable permission message in paper.yml to be blank and also support newlines + +diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java +index c10fc8d2386301bc2caddcdb1cd18566bcaa8689..882c565ac2be3df976e7bbeb4dc80c9ac474a8b1 100644 +--- a/src/main/java/org/bukkit/command/Command.java ++++ b/src/main/java/org/bukkit/command/Command.java +@@ -184,9 +184,13 @@ public abstract class Command { + return true; + } + ++ // Purpur start ++ String permissionMessage = this.permissionMessage; + if (permissionMessage == null) { +- target.sendMessage(Bukkit.getPermissionMessage()); // Paper +- } else if (permissionMessage.length() != 0) { ++ permissionMessage = Bukkit.getPermissionMessage(); ++ } ++ if (permissionMessage.length() != 0) { ++ // Purpur end + for (String line : permissionMessage.replace("", permission).split("\n")) { + target.sendMessage(line); + } diff --git a/patches/Purpur/patches/api/0025-LivingEntity-broadcastItemBreak.patch b/patches/Purpur/patches/api/0025-LivingEntity-broadcastItemBreak.patch new file mode 100644 index 00000000..0a327c0a --- /dev/null +++ b/patches/Purpur/patches/api/0025-LivingEntity-broadcastItemBreak.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 17 Aug 2020 21:50:32 -0500 +Subject: [PATCH] LivingEntity#broadcastItemBreak + + +diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java +index 4ccbb3ef3c597ef9da2c6744f410283a1dc2538c..42811d18ff304082f74f45794344891208599c04 100644 +--- a/src/main/java/org/bukkit/entity/LivingEntity.java ++++ b/src/main/java/org/bukkit/entity/LivingEntity.java +@@ -865,5 +865,12 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource + * @param safeFallDistance Safe fall distance + */ + void setSafeFallDistance(float safeFallDistance); ++ ++ /** ++ * Play item break animation for the item in specified equipment slot ++ * ++ * @param slot Equipment slot to play break animation for ++ */ ++ void broadcastItemBreak(@NotNull org.bukkit.inventory.EquipmentSlot slot); + // Purpur end + } diff --git a/patches/Purpur/patches/api/0026-Item-entity-immunities.patch b/patches/Purpur/patches/api/0026-Item-entity-immunities.patch new file mode 100644 index 00000000..7356fb0b --- /dev/null +++ b/patches/Purpur/patches/api/0026-Item-entity-immunities.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 22 Aug 2020 17:42:08 -0500 +Subject: [PATCH] Item entity immunities + + +diff --git a/src/main/java/org/bukkit/entity/Item.java b/src/main/java/org/bukkit/entity/Item.java +index 0ee072645ecf1bf5feb74de6960947ef76db366e..69dae9157053c521a9e2bbdd7f89c17fc8d24840 100644 +--- a/src/main/java/org/bukkit/entity/Item.java ++++ b/src/main/java/org/bukkit/entity/Item.java +@@ -120,4 +120,48 @@ public interface Item extends Entity { + */ + public void setWillAge(boolean willAge); + // Paper end ++ ++ // Purpur start ++ /** ++ * Set whether or not this item is immune to cactus ++ * ++ * @param immuneToCactus True to make immune to cactus ++ */ ++ void setImmuneToCactus(boolean immuneToCactus); ++ ++ /** ++ * Check if item is immune to cactus ++ * ++ * @return True if immune to cactus ++ */ ++ boolean isImmuneToCactus(); ++ ++ /** ++ * Set whether or not this item is immune to explosions ++ * ++ * @param immuneToExplosion True to make immune to explosions ++ */ ++ void setImmuneToExplosion(boolean immuneToExplosion); ++ ++ /** ++ * Check if item is immune to explosions ++ * ++ * @return True if immune to explosions ++ */ ++ boolean isImmuneToExplosion(); ++ ++ /** ++ * Set whether or not this item is immune to fire ++ * ++ * @param immuneToFire True to make immune to fire ++ */ ++ void setImmuneToFire(boolean immuneToFire); ++ ++ /** ++ * Check if item is immune to fire ++ * ++ * @return True if immune to fire ++ */ ++ boolean isImmuneToFire(); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0027-Spigot-Improve-output-of-plugins-command.patch b/patches/Purpur/patches/api/0027-Spigot-Improve-output-of-plugins-command.patch new file mode 100644 index 00000000..bb371528 --- /dev/null +++ b/patches/Purpur/patches/api/0027-Spigot-Improve-output-of-plugins-command.patch @@ -0,0 +1,114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Parker Hawke +Date: Sat, 27 Jun 2020 18:43:37 -0400 +Subject: [PATCH] Spigot - Improve output of plugins command + + +diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java +index 1aa58c59e1e8738bbdc77752885ff3b18b29de42..4974fc518c3645e6e060ff52e71a47a86d52ec5c 100644 +--- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java +@@ -11,6 +11,15 @@ import org.bukkit.ChatColor; + import org.bukkit.command.CommandSender; + import org.bukkit.plugin.Plugin; + import org.jetbrains.annotations.NotNull; ++// Spigot start ++import net.md_5.bungee.api.chat.BaseComponent; ++import net.md_5.bungee.api.chat.ClickEvent; ++import net.md_5.bungee.api.chat.ComponentBuilder; ++import net.md_5.bungee.api.chat.HoverEvent; ++import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention; ++import org.bukkit.entity.Player; ++import org.bukkit.plugin.PluginDescriptionFile; ++// Spigot end + + public class PluginsCommand extends BukkitCommand { + public PluginsCommand(@NotNull String name) { +@@ -25,7 +34,13 @@ public class PluginsCommand extends BukkitCommand { + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { + if (!testPermission(sender)) return true; + +- sender.sendMessage("Plugins " + getPluginList()); ++ // Spigot start ++ if (sender instanceof Player && sender.hasPermission("bukkit.command.version")) { ++ sender.spigot().sendMessage(getPluginListSpigot()); ++ } else { ++ sender.sendMessage("Plugins " + getPluginList()); ++ } ++ // Spigot end + return true; + } + +@@ -71,4 +86,72 @@ public class PluginsCommand extends BukkitCommand { + // Paper end + } + ++ // Spigot start ++ @NotNull ++ private BaseComponent[] getPluginListSpigot() { ++ Plugin[] plugins = Bukkit.getPluginManager().getPlugins(); ++ ComponentBuilder pluginList = new ComponentBuilder("Plugins (" + plugins.length + "): "); ++ ++ int index = 0; ++ for (Plugin plugin : plugins) { ++ if (index++ > 0) { ++ pluginList.append(", ", FormatRetention.NONE).color(net.md_5.bungee.api.ChatColor.WHITE); ++ } ++ ++ // Event components ++ PluginDescriptionFile description = plugin.getDescription(); ++ ComponentBuilder hoverEventComponents = new ComponentBuilder(); ++ hoverEventComponents.append("Version: ").color(net.md_5.bungee.api.ChatColor.WHITE).append(description.getVersion()).color(net.md_5.bungee.api.ChatColor.GREEN); ++ ++ if (description.getDescription() != null) { ++ hoverEventComponents.append("\nDescription: ").color(net.md_5.bungee.api.ChatColor.WHITE).append(description.getDescription()).color(net.md_5.bungee.api.ChatColor.GREEN); ++ } ++ ++ if (description.getWebsite() != null) { ++ hoverEventComponents.append("\nWebsite: ").color(net.md_5.bungee.api.ChatColor.WHITE).append(description.getWebsite()).color(net.md_5.bungee.api.ChatColor.GREEN); ++ } ++ ++ if (!description.getAuthors().isEmpty()) { ++ if (description.getAuthors().size() == 1) { ++ hoverEventComponents.append("\nAuthor: "); ++ } else { ++ hoverEventComponents.append("\nAuthors: "); ++ } ++ ++ hoverEventComponents.color(net.md_5.bungee.api.ChatColor.WHITE).append(getAuthors(description)); ++ } ++ ++ HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverEventComponents.create()); ++ ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/version " + description.getName()); ++ ++ // Plugin list entry ++ pluginList.append(plugin.getDescription().getName()); ++ pluginList.color(plugin.isEnabled() ? net.md_5.bungee.api.ChatColor.GREEN : net.md_5.bungee.api.ChatColor.RED); ++ pluginList.event(hoverEvent).event(clickEvent); ++ ++ if (plugin.getDescription().getProvides().size() > 0) { ++ pluginList.append("( ", FormatRetention.NONE).color(net.md_5.bungee.api.ChatColor.WHITE).append(String.join(", ", plugin.getDescription().getProvides())).append(")"); ++ } ++ } ++ ++ return pluginList.create(); ++ } ++ ++ @NotNull ++ private BaseComponent[] getAuthors(@NotNull final PluginDescriptionFile description) { ++ ComponentBuilder result = new ComponentBuilder(); ++ List authors = description.getAuthors(); ++ ++ for (int i = 0; i < authors.size(); i++) { ++ if (i > 0) { ++ result.append(i < authors.size() - 1 ? ", " : " and ", FormatRetention.NONE); ++ result.color(net.md_5.bungee.api.ChatColor.WHITE); ++ } ++ ++ result.append(authors.get(i)).color(net.md_5.bungee.api.ChatColor.GREEN); ++ } ++ ++ return result.create(); ++ } ++ // Spigot end + } diff --git a/patches/Purpur/patches/api/0028-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/Purpur/patches/api/0028-Add-option-to-disable-zombie-aggressiveness-towards-.patch new file mode 100644 index 00000000..45b72c6b --- /dev/null +++ b/patches/Purpur/patches/api/0028-Add-option-to-disable-zombie-aggressiveness-towards-.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nitricspace +Date: Wed, 23 Sep 2020 22:14:38 +0100 +Subject: [PATCH] Add option to disable zombie aggressiveness towards villagers + when lagging + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +index 177143c9764e82981423879ed35625edd25d3ebf..da638f9745aceebe4f2ca90823308c6879c75ae7 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -211,5 +211,7 @@ public interface VanillaGoal extends Goal { + GoalKey ORBIT_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal_goal")); + GoalKey HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider")); + GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); ++ GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager")); ++ GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager")); + // Purpur end + } diff --git a/patches/Purpur/patches/api/0029-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch b/patches/Purpur/patches/api/0029-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch new file mode 100644 index 00000000..008ba61e --- /dev/null +++ b/patches/Purpur/patches/api/0029-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 2 Oct 2020 17:43:24 -0500 +Subject: [PATCH] Add predicate to recipe's ExactChoice ingredient + + +diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java +index 3d325cab6b106ce8617e321d7a733eca91ba93e5..4dedbdc1cc8b34b73a1a32b35d1985284da6fc08 100644 +--- a/src/main/java/org/bukkit/inventory/RecipeChoice.java ++++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java +@@ -10,6 +10,7 @@ import java.util.function.Predicate; + import org.bukkit.Material; + import org.bukkit.Tag; + import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; // Purpur + + /** + * Represents a potential item match within a recipe. All choices within a +@@ -155,6 +156,7 @@ public interface RecipeChoice extends Predicate, Cloneable { + public static class ExactChoice implements RecipeChoice { + + private List choices; ++ private Predicate predicate; // Purpur + + public ExactChoice(@NotNull ItemStack stack) { + this(Arrays.asList(stack)); +@@ -199,6 +201,7 @@ public interface RecipeChoice extends Predicate, Cloneable { + + @Override + public boolean test(@NotNull ItemStack t) { ++ if (predicate != null) return predicate.test(t); // Purpur + for (ItemStack match : choices) { + if (t.isSimilar(match)) { + return true; +@@ -208,6 +211,17 @@ public interface RecipeChoice extends Predicate, Cloneable { + return false; + } + ++ // Purpur start ++ @Nullable ++ public Predicate getPredicate() { ++ return predicate; ++ } ++ ++ public void setPredicate(@Nullable Predicate predicate) { ++ this.predicate = predicate; ++ } ++ // Purpur end ++ + @Override + public int hashCode() { + int hash = 7; diff --git a/patches/Purpur/patches/api/0030-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch b/patches/Purpur/patches/api/0030-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch new file mode 100644 index 00000000..ebfe2021 --- /dev/null +++ b/patches/Purpur/patches/api/0030-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 24 Nov 2020 04:30:34 -0600 +Subject: [PATCH] Add critical hit check to EntityDamagedByEntityEvent + + +diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java +index 869bad7405ec7fa67728e90d8b9f2e11b542611f..05fde759bbdf6068f140b4428bbcb355e22d6b28 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java +@@ -10,15 +10,28 @@ import org.jetbrains.annotations.NotNull; + */ + public class EntityDamageByEntityEvent extends EntityDamageEvent { + private final Entity damager; ++ private final boolean isCritical; // Purpur + + public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) { ++ // Purpur start ++ this(damager, damagee, cause, damage, false); ++ } ++ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage, boolean isCritical) { ++ // Purpur end + super(damagee, cause, damage); + this.damager = damager; ++ this.isCritical = isCritical; // Purpur + } + + public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { ++ // Purpur start ++ this(damager, damagee, cause, modifiers, modifierFunctions, false); ++ } ++ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions, boolean isCritical) { ++ // Purpur end + super(damagee, cause, modifiers, modifierFunctions); + this.damager = damager; ++ this.isCritical = isCritical; // Purpur + } + + /** +@@ -30,4 +43,16 @@ public class EntityDamageByEntityEvent extends EntityDamageEvent { + public Entity getDamager() { + return damager; + } ++ ++ // Purpur start ++ ++ /** ++ * Whether this damage was done by a critical hit ++ * ++ * @return True if critical hit ++ */ ++ public boolean isCritical() { ++ return this.isCritical; ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0031-Left-handed-API.patch b/patches/Purpur/patches/api/0031-Left-handed-API.patch new file mode 100644 index 00000000..f5bb5e3c --- /dev/null +++ b/patches/Purpur/patches/api/0031-Left-handed-API.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 30 Nov 2020 06:02:54 -0600 +Subject: [PATCH] Left handed API + + +diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java +index d726453c041a980576312b6bee96a07837f37974..9d4eae5cf0bf8d01954db85b431bcdca8490ee8f 100644 +--- a/src/main/java/org/bukkit/entity/Mob.java ++++ b/src/main/java/org/bukkit/entity/Mob.java +@@ -64,4 +64,20 @@ public interface Mob extends LivingEntity, Lootable { + * @return whether the mob is aware + */ + public boolean isAware(); ++ ++ // Purpur start ++ /** ++ * Check if Mob is left-handed ++ * ++ * @return True if left-handed ++ */ ++ public boolean isLeftHanded(); ++ ++ /** ++ * Set if Mob is left-handed ++ * ++ * @param leftHanded True if left-handed ++ */ ++ public void setLeftHanded(boolean leftHanded); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0032-Alphabetize-in-game-plugins-list.patch b/patches/Purpur/patches/api/0032-Alphabetize-in-game-plugins-list.patch new file mode 100644 index 00000000..0e1b879b --- /dev/null +++ b/patches/Purpur/patches/api/0032-Alphabetize-in-game-plugins-list.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 09:48:18 -0600 +Subject: [PATCH] Alphabetize in-game /plugins list + + +diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java +index 4974fc518c3645e6e060ff52e71a47a86d52ec5c..37cc5d7e9db89e4ef7ab16da1b159bd19134a4ff 100644 +--- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java +@@ -2,6 +2,7 @@ package org.bukkit.command.defaults; + + import java.util.Arrays; + import java.util.Collections; ++import java.util.Comparator; + import java.util.List; + import java.util.Map; + import java.util.TreeMap; +@@ -89,7 +90,7 @@ public class PluginsCommand extends BukkitCommand { + // Spigot start + @NotNull + private BaseComponent[] getPluginListSpigot() { +- Plugin[] plugins = Bukkit.getPluginManager().getPlugins(); ++ Plugin[] plugins = Arrays.stream(Bukkit.getPluginManager().getPlugins()).sorted(Comparator.comparing(plugin -> plugin.getName().toLowerCase())).toArray(Plugin[]::new); // Purpur + ComponentBuilder pluginList = new ComponentBuilder("Plugins (" + plugins.length + "): "); + + int index = 0; diff --git a/patches/Purpur/patches/api/0033-Rabid-Wolf-API.patch b/patches/Purpur/patches/api/0033-Rabid-Wolf-API.patch new file mode 100644 index 00000000..a3c3a541 --- /dev/null +++ b/patches/Purpur/patches/api/0033-Rabid-Wolf-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Encode42 +Date: Tue, 8 Dec 2020 17:15:15 -0500 +Subject: [PATCH] Rabid Wolf API + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +index da638f9745aceebe4f2ca90823308c6879c75ae7..39f77041133228c4bd4cec2427ad0bae8e739d4a 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -213,5 +213,6 @@ public interface VanillaGoal extends Goal { + GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); + GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager")); + GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager")); ++ GoalKey AVOID_RABID_WOLVES = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolves")); + // Purpur end + } +diff --git a/src/main/java/org/bukkit/entity/Wolf.java b/src/main/java/org/bukkit/entity/Wolf.java +index 0e5decadf31140d6cb121c298f935ccc12c7a7e7..c1fd30fe4cd4eec11eb8298f059d14584b7dd7ec 100644 +--- a/src/main/java/org/bukkit/entity/Wolf.java ++++ b/src/main/java/org/bukkit/entity/Wolf.java +@@ -39,4 +39,20 @@ public interface Wolf extends Tameable, Sittable { + * @param color the color to apply + */ + public void setCollarColor(@NotNull DyeColor color); ++ ++ // Purpur start ++ /** ++ * Checks if this wolf is rabid ++ * ++ * @return whether the wolf is rabid ++ */ ++ public boolean isRabid(); ++ ++ /** ++ * Sets this wolf to be rabid or not ++ * ++ * @param rabid whether the wolf should be rabid ++ */ ++ public void setRabid(boolean rabid); ++ // Purpur end + } diff --git a/patches/Purpur/patches/api/0034-Fix-javadoc-warnings-missing-param-and-return.patch b/patches/Purpur/patches/api/0034-Fix-javadoc-warnings-missing-param-and-return.patch new file mode 100644 index 00000000..91716e00 --- /dev/null +++ b/patches/Purpur/patches/api/0034-Fix-javadoc-warnings-missing-param-and-return.patch @@ -0,0 +1,1588 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 18 Dec 2020 21:21:48 -0600 +Subject: [PATCH] Fix javadoc warnings (missing @param and @return) + + +diff --git a/src/main/java/com/destroystokyo/paper/ClientOption.java b/src/main/java/com/destroystokyo/paper/ClientOption.java +index 9dad814cf51bc59ec5dfbf14474fea6557de38aa..7baf7ee3b62135eda8f0d9c1d761b79f596061f1 100644 +--- a/src/main/java/com/destroystokyo/paper/ClientOption.java ++++ b/src/main/java/com/destroystokyo/paper/ClientOption.java +@@ -4,6 +4,11 @@ import org.jetbrains.annotations.NotNull; + + import org.bukkit.inventory.MainHand; + ++/** ++ * Represents a client option ++ * ++ * @param Client option type ++ */ + public final class ClientOption { + + public static final ClientOption SKIN_PARTS = new ClientOption<>(SkinParts.class); +@@ -19,6 +24,11 @@ public final class ClientOption { + this.type = type; + } + ++ /** ++ * Get the option's type ++ * ++ * @return Option's type ++ */ + @NotNull + public Class getType() { + return type; +diff --git a/src/main/java/com/destroystokyo/paper/MaterialSetTag.java b/src/main/java/com/destroystokyo/paper/MaterialSetTag.java +index a02a02aa0c87e0f0ed9e509e4dcab01565b3d92a..6c99f4b4960f8f982557bb42717a2868d57ce4b7 100644 +--- a/src/main/java/com/destroystokyo/paper/MaterialSetTag.java ++++ b/src/main/java/com/destroystokyo/paper/MaterialSetTag.java +@@ -21,10 +21,14 @@ import java.util.stream.Stream; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Material set tag ++ */ + public class MaterialSetTag extends BaseTag { + + /** + * @deprecated Use NamespacedKey version of constructor ++ * @param filter Filter predicate + */ + @Deprecated + public MaterialSetTag(@NotNull Predicate filter) { +@@ -33,6 +37,7 @@ public class MaterialSetTag extends BaseTag { + + /** + * @deprecated Use NamespacedKey version of constructor ++ * @param materials Materials to include + */ + @Deprecated + public MaterialSetTag(@NotNull Collection materials) { +@@ -41,6 +46,7 @@ public class MaterialSetTag extends BaseTag { + + /** + * @deprecated Use NamespacedKey version of constructor ++ * @param materials Materials to include + */ + @Deprecated + public MaterialSetTag(@NotNull Material... materials) { +diff --git a/src/main/java/com/destroystokyo/paper/SkinParts.java b/src/main/java/com/destroystokyo/paper/SkinParts.java +index 4a0c39405d4fbed457787e3c6ded4cc6591bc8c2..9b269a51928bc5cc35431855c79bd33ce9031bf1 100644 +--- a/src/main/java/com/destroystokyo/paper/SkinParts.java ++++ b/src/main/java/com/destroystokyo/paper/SkinParts.java +@@ -1,5 +1,8 @@ + package com.destroystokyo.paper; + ++/** ++ * Skin parts ++ */ + public interface SkinParts { + + boolean hasCapeEnabled(); +diff --git a/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java b/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java +index 18a96dbb01d3b34476652264b2d6be3782a154ec..1bae8e5df78bb88deec212eedf855cc69bfbe913 100644 +--- a/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java ++++ b/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java +@@ -46,6 +46,9 @@ public class TargetBlockInfo { + return block.getRelative(blockFace); + } + ++ /** ++ * Fluid mode ++ */ + public enum FluidMode { + NEVER, + SOURCE_ONLY, +diff --git a/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java b/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java +index f2e3233a3d1744e32fb76d3731b9858ef0067e30..e66e8b28c673216dd1587582f44c26f6ca877c23 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java ++++ b/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java +@@ -4,6 +4,9 @@ import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Mob; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents an entity with ranged attacks ++ */ + public interface RangedEntity extends Mob { + /** + * Attack the specified entity using a ranged attack. +diff --git a/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java b/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java +index 73dabb82c7fbea3f0cccade0a2944b11a80ede06..b065f8f8af6ed9cc7b1c8a671488a6424662d14c 100644 +--- a/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java +@@ -83,6 +83,9 @@ public class TNTPrimeEvent extends BlockEvent implements Cancellable { + return handlers; + } + ++ /** ++ * TnT prime reason ++ */ + public enum PrimeReason { + /** + * When TNT prime was caused by other explosion (chain reaction) +diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java +index 806112a8b5a7ce31166675f5b074ceaf42e364b6..a2635e3b76780a51f87f6329b0db3a08dd08b0e6 100644 +--- a/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java +@@ -8,6 +8,9 @@ import org.bukkit.event.HandlerList; + import org.bukkit.event.entity.EntityEvent; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Called when an enderman tries to teleport to escape ++ */ + public class EndermanEscapeEvent extends EntityEvent implements Cancellable { + @NotNull private final Reason reason; + +@@ -62,6 +65,9 @@ public class EndermanEscapeEvent extends EntityEvent implements Cancellable { + cancelled = cancel; + } + ++ /** ++ * Escape reason ++ */ + public enum Reason { + /** + * The enderman has stopped attacking and ran away +diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java +index 12194f1fc7f03ca6785904b6187b3dfd03b16461..e974323d0193e6b5a6fe43979c6c24d78107a5cc 100644 +--- a/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java +@@ -66,6 +66,9 @@ public class EntityTransformedEvent extends EntityEvent implements Cancellable { + cancelled = cancel; + } + ++ /** ++ * Transformed reason ++ */ + public enum TransformedReason { + /** + * When a zombie drowns +diff --git a/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java +index 5351b523defa054ba56ae3fb591029283ca7510d..f00594fba37c8b6264f940c84ed5c40c09879d2f 100644 +--- a/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java +@@ -9,6 +9,9 @@ import org.bukkit.inventory.ItemStack; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Called when a witch prepares a potion ++ */ + public class WitchReadyPotionEvent extends EntityEvent implements Cancellable { + private ItemStack potion; + +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java +index 5b28e9b1daba7834af67dbc193dd656bedd9a994..dbda663489e82b89646975b56462c4ff38a5fde9 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java +@@ -11,6 +11,9 @@ import org.bukkit.event.Listener; + import org.bukkit.plugin.EventExecutor; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Method handle event executor ++ */ + public class MethodHandleEventExecutor implements EventExecutor { + private final Class eventClass; + private final MethodHandle handle; +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java +index c83672427324bd068ed52916f700b68446a226f6..d28f8f0fb6dcf34453a75af9e9efd18a89b2b94f 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java +@@ -15,6 +15,9 @@ import org.bukkit.event.Listener; + import org.bukkit.plugin.EventExecutor; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Static method handle event executor ++ */ + public class StaticMethodHandleEventExecutor implements EventExecutor { + private final Class eventClass; + private final MethodHandle handle; +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java +index b6e7d8ee8d903ebf975d60bec0e08603d9a49fdb..55770ba8a2461c782f311aeb8abc79ec5f53ea81 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java +@@ -11,6 +11,9 @@ import org.objectweb.asm.commons.GeneratorAdapter; + + import static org.objectweb.asm.Opcodes.*; + ++/** ++ * ASM event executor generator ++ */ + public class ASMEventExecutorGenerator { + @NotNull + public static byte[] generateEventExecutor(@NotNull Method m, @NotNull String name) { +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java +index f79685b48bb581277a6891927988b6f7a4389dc4..75810a098791b5f758a3fbb212d80643b1cb505b 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java +@@ -2,6 +2,9 @@ package com.destroystokyo.paper.event.executor.asm; + + import org.jetbrains.annotations.NotNull; + ++/** ++ * Class definer ++ */ + public interface ClassDefiner { + + /** +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java +index ac99477e9f2c08041aeff31abc1d1edee58d0a67..d2bd9211046dea646f0c0954a932859ba1d0fb15 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java +@@ -9,6 +9,9 @@ import com.google.common.collect.MapMaker; + import org.jetbrains.annotations.NotNull; + import org.objectweb.asm.Type; + ++/** ++ * Safe class definer ++ */ + public class SafeClassDefiner implements ClassDefiner { + /* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner(); + +diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java +index e406ce639a2e88b78f82f25e71678a669d0a958b..4cc1012c33c6f76255ac075ace1d8ee638091eed 100644 +--- a/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java +@@ -80,6 +80,9 @@ public class PlayerArmorChangeEvent extends PlayerEvent { + return HANDLERS; + } + ++ /** ++ * Armor slot type ++ */ + public enum SlotType { + HEAD(NETHERITE_HELMET, DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, CHAINMAIL_HELMET, LEATHER_HELMET, CARVED_PUMPKIN, PLAYER_HEAD, SKELETON_SKULL, ZOMBIE_HEAD, CREEPER_HEAD, WITHER_SKELETON_SKULL, TURTLE_HELMET), + CHEST(NETHERITE_CHESTPLATE, DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE, ELYTRA), +diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java +index 12c1c6fe9dc8dc5f5faf6dcf99f6857219ef22b8..1f623010d3eddc2bee8f4b8fb410a9509a57b5ae 100644 +--- a/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java +@@ -59,7 +59,9 @@ public class PlayerConnectionCloseEvent extends Event { + } + + /** +- * Returns the {@code UUID} of the player disconnecting. ++ * Get the {@code UUID} of the player disconnecting. ++ * ++ * @return the {@code UUID} of the player disconnecting + */ + @NotNull + public UUID getPlayerUniqueId() { +@@ -67,7 +69,9 @@ public class PlayerConnectionCloseEvent extends Event { + } + + /** +- * Returns the name of the player disconnecting. ++ * Get the name of the player disconnecting. ++ * ++ * @return the name of the player disconnecting + */ + @NotNull + public String getPlayerName() { +@@ -75,7 +79,9 @@ public class PlayerConnectionCloseEvent extends Event { + } + + /** +- * Returns the player's IP address. ++ * Get the player's IP address. ++ * ++ * @return the player's IP address + */ + @NotNull + public InetAddress getIpAddress() { +diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java +index 09cfdf48ead8f03f3497646537292174241b0868..20f8201d2278f6fcac38913638510f33b0750b28 100644 +--- a/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java +@@ -6,6 +6,9 @@ import org.bukkit.event.player.PlayerEvent; + import org.bukkit.inventory.EquipmentSlot; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Called when a player uses (interacts with) an unknown entity ++ */ + public class PlayerUseUnknownEntityEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); +diff --git a/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java +index 77a19995f6792a182c5a43d6714e7bda0f42df5b..cb43359e012630b3126564c068d81e9b20845a80 100644 +--- a/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java +@@ -105,6 +105,9 @@ public final class GS4QueryEvent extends Event { + ; + } + ++ /** ++ * Query response ++ */ + public final static class QueryResponse { + private final String motd; + private final String gameVersion; +diff --git a/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java b/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java +index eac85f1f49088bb71afb01eff4d5f53887306461..3ae058eb9433885920715408ebbe303d02f99c47 100644 +--- a/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java +@@ -4,6 +4,9 @@ import org.bukkit.event.Event; + import org.bukkit.event.HandlerList; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Called at the beginning of the server tick ++ */ + public class ServerTickStartEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); +diff --git a/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java b/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java +index 7e4acfff16db80a75e1ff2fee1972b16955b0918..333812f322f1f0a06d654581103aeb0123daef60 100644 +--- a/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java ++++ b/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java +@@ -2,6 +2,9 @@ package com.destroystokyo.paper.inventory.meta; + + import org.bukkit.inventory.meta.ItemMeta; + ++/** ++ * Armor stand meta ++ */ + public interface ArmorStandMeta extends ItemMeta { + + /** +diff --git a/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java b/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java +index fd184f13f5e8ee5cf829fff4f44696e1f760430b..302059e9fc048a63fd9cd2e1ed5aa27ef5811637 100644 +--- a/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java ++++ b/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java +@@ -6,6 +6,9 @@ import org.bukkit.event.HandlerList; + import org.bukkit.event.player.PlayerEvent; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Called when a lootable inventory replenishes it's contents ++ */ + public class LootableInventoryReplenishEvent extends PlayerEvent implements Cancellable { + @NotNull private final LootableInventory inventory; + +diff --git a/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java b/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java +index 9db0056ab94145819628b3ad8d8d26130d117fcf..680410d8404a6d3b0ac91aa5fc4cd9d7f5c8fa93 100644 +--- a/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java ++++ b/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java +@@ -2,6 +2,9 @@ package com.destroystokyo.paper.util; + + import org.jetbrains.annotations.NotNull; + ++/** ++ * Sneaky sneaky ++ */ + public class SneakyThrow { + + public static void sneaky(@NotNull Throwable exception) { +diff --git a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java +index 2a2651299e8dc631938ba4b4078dc694764d784c..62fafc206e7f1d8fdc0b0dfa2c1c6f1d280f4f5e 100644 +--- a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java +@@ -3,6 +3,9 @@ package com.destroystokyo.paper.util; + import org.bukkit.Bukkit; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Version fetcher ++ */ + public interface VersionFetcher { + /** + * Amount of time to cache results for in milliseconds +@@ -25,6 +28,9 @@ public interface VersionFetcher { + @NotNull + String getVersionMessage(@NotNull String serverVersion); + ++ /** ++ * Dummy version fetcher ++ */ + class DummyVersionFetcher implements VersionFetcher { + + @Override +diff --git a/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java b/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java +index 5bb677ce585b856b3d3e589e29786a29619c56a7..613f00fa387dcc5af3191e550dea9d4d76fda02f 100644 +--- a/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java ++++ b/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java +@@ -5,6 +5,11 @@ import java.util.concurrent.atomic.LongAdder; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Cached size concurrent linked queue ++ * ++ * @param Element type ++ */ + public class CachedSizeConcurrentLinkedQueue extends ConcurrentLinkedQueue { + private final LongAdder cachedSize = new LongAdder(); + +diff --git a/src/main/java/io/papermc/paper/world/MoonPhase.java b/src/main/java/io/papermc/paper/world/MoonPhase.java +index df05153397b42930cd53d37b30824c7e5f008f7e..ebf70fea04a9d37aa5f2ad8e7d6cef73cd3a4541 100644 +--- a/src/main/java/io/papermc/paper/world/MoonPhase.java ++++ b/src/main/java/io/papermc/paper/world/MoonPhase.java +@@ -5,6 +5,9 @@ import org.jetbrains.annotations.NotNull; + import java.util.HashMap; + import java.util.Map; + ++/** ++ * Moon phase ++ */ + public enum MoonPhase { + FULL_MOON(0L), + WANING_GIBBOUS(1L), +diff --git a/src/main/java/org/bukkit/Fluid.java b/src/main/java/org/bukkit/Fluid.java +index 525ede42137cc27cf20cf713478e85292455676e..a0279fcbfc76ad97a61fc191424e876d517acb46 100644 +--- a/src/main/java/org/bukkit/Fluid.java ++++ b/src/main/java/org/bukkit/Fluid.java +@@ -3,6 +3,9 @@ package org.bukkit; + import java.util.Locale; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents a fluid ++ */ + public enum Fluid implements Keyed { + + WATER, +diff --git a/src/main/java/org/bukkit/Nameable.java b/src/main/java/org/bukkit/Nameable.java +index fee814e01a653d2b53c56e8b566383ca44aa5346..cda8b54d73edf47ef8d5a1bba25478b187fe1cde 100644 +--- a/src/main/java/org/bukkit/Nameable.java ++++ b/src/main/java/org/bukkit/Nameable.java +@@ -2,6 +2,9 @@ package org.bukkit; + + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents something that can be named ++ */ + public interface Nameable { + + /** +diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java +index 3afd5f5c0208a4ee93b5dbfc2aab2b9d2e8a7544..7838731e0e16bdccfb79e74ceb64148f7c52db79 100644 +--- a/src/main/java/org/bukkit/OfflinePlayer.java ++++ b/src/main/java/org/bukkit/OfflinePlayer.java +@@ -9,6 +9,9 @@ import org.bukkit.permissions.ServerOperator; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents an offline player ++ */ + public interface OfflinePlayer extends ServerOperator, AnimalTamer, ConfigurationSerializable { + + /** +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 05b47c2462a00451fc64c79c3eda116fc8003a9f..26c1953d23efd370ac7fd47fc3432edba4724139 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1455,6 +1455,9 @@ public interface Server extends PluginMessageRecipient { + UnsafeValues getUnsafe(); + + // Spigot start ++ /** ++ * Spigot stuffs ++ */ + public class Spigot { + + @NotNull +diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java +index 931ffa38faab86445a5d63364a47cb653ca3d4ed..97b9ade0e771eae663fb42f91e15545034d58fc9 100644 +--- a/src/main/java/org/bukkit/UnsafeValues.java ++++ b/src/main/java/org/bukkit/UnsafeValues.java +@@ -80,6 +80,8 @@ public interface UnsafeValues { + + /** + * Called once by the version command on first use, then cached. ++ * ++ * @return Paper's VersionFetcher + */ + default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); +@@ -98,6 +100,8 @@ public interface UnsafeValues { + /** + * Return the translation key for the Material, so the client can translate it into the active + * locale when using a TranslatableComponent. ++ * ++ * @param mat Material to check + * @return the translation key + */ + String getTranslationKey(Material mat); +@@ -105,6 +109,8 @@ public interface UnsafeValues { + /** + * Return the translation key for the Block, so the client can translate it into the active + * locale when using a TranslatableComponent. ++ * ++ * @param block Block to check + * @return the translation key + */ + String getTranslationKey(org.bukkit.block.Block block); +@@ -113,6 +119,8 @@ public interface UnsafeValues { + * Return the translation key for the EntityType, so the client can translate it into the active + * locale when using a TranslatableComponent.
+ * This is null, when the EntityType isn't known to NMS (custom entities) ++ * ++ * @param type EntityType to check + * @return the translation key + */ + String getTranslationKey(org.bukkit.entity.EntityType type); +@@ -121,6 +129,8 @@ public interface UnsafeValues { + * Creates and returns the next EntityId available. + *

+ * Use this when sending custom packets, so that there are no collisions on the client or server. ++ * ++ * @return the next available entity id + */ + public int nextEntityId(); + +diff --git a/src/main/java/org/bukkit/WorldBorder.java b/src/main/java/org/bukkit/WorldBorder.java +index afb7b136b461202026290624836446cff9f9e45d..087579fdff09237409c9f80446e7a15a78f9040c 100644 +--- a/src/main/java/org/bukkit/WorldBorder.java ++++ b/src/main/java/org/bukkit/WorldBorder.java +@@ -2,6 +2,9 @@ package org.bukkit; + + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents a world border ++ */ + public interface WorldBorder { + + /** +diff --git a/src/main/java/org/bukkit/WorldCreator.java b/src/main/java/org/bukkit/WorldCreator.java +index 6e6945dd4c770be04ec09da3958fae751717527a..7d1030d4573e3843cae9ad4e39cef8baa2af39b0 100644 +--- a/src/main/java/org/bukkit/WorldCreator.java ++++ b/src/main/java/org/bukkit/WorldCreator.java +@@ -238,11 +238,8 @@ public class WorldCreator { + * is as follows: + * {"structures": {"structures": {"village": {"salt": 8015723, "spacing": 32, "separation": 8}}}, "layers": [{"block": "stone", "height": 1}, {"block": "grass", "height": 1}], "biome":"plains"} + * +- * @see Custom +- * dimension (scroll to "When the generator ID type is +- * minecraft:flat)" +- * @param generatorSettings The settings that should be used by the +- * generator ++ * @see Custom dimension ++ * @param generatorSettings The settings that should be used by the generator + * @return This object, for chaining + */ + @NotNull +diff --git a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java +index bca3d112e2397b26ba6ccb6cd41e406caae27c5c..f4e076d6f3b05c9de85dcd65b95c1088a094249c 100644 +--- a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java ++++ b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java +@@ -2,6 +2,9 @@ package org.bukkit.advancement; + + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents an advancement's display ++ */ + public interface AdvancementDisplay { + /** + * Get the title of this advancement +diff --git a/src/main/java/org/bukkit/advancement/FrameType.java b/src/main/java/org/bukkit/advancement/FrameType.java +index d1757f3d456ff9efce26ce8baa1d16d896908cc2..a5db52386e11e4b5511ae417a0e7ac92e001de71 100644 +--- a/src/main/java/org/bukkit/advancement/FrameType.java ++++ b/src/main/java/org/bukkit/advancement/FrameType.java +@@ -3,6 +3,9 @@ package org.bukkit.advancement; + import org.bukkit.ChatColor; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents an advancement's display's frame type ++ */ + public enum FrameType { + TASK(ChatColor.GREEN), + CHALLENGE(ChatColor.DARK_PURPLE), +diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java +index 0c72d00ad238ab69d7ae0941e3ecb6c86e71624d..73cf7437795ef185860bfefe51d9481fa297a939 100644 +--- a/src/main/java/org/bukkit/block/Block.java ++++ b/src/main/java/org/bukkit/block/Block.java +@@ -183,6 +183,9 @@ public interface Block extends Metadatable { + * {@code int z = (int) ((packed << 10) >> 37);} + *

+ * ++ * @param x X coordinate ++ * @param y Y coordinate ++ * @param z Z coordinate + * @return This block's x, y, and z coordinates packed into a long value + */ + public static long getBlockKey(int x, int y, int z) { +diff --git a/src/main/java/org/bukkit/block/Lidded.java b/src/main/java/org/bukkit/block/Lidded.java +index 9da2566e02e63be1a0188deaa27b841fa61688ea..882980b3ada3d6048019a90159fa37639b773d5e 100644 +--- a/src/main/java/org/bukkit/block/Lidded.java ++++ b/src/main/java/org/bukkit/block/Lidded.java +@@ -1,5 +1,8 @@ + package org.bukkit.block; + ++/** ++ * Represents something that has a lid ++ */ + public interface Lidded { + + /** +diff --git a/src/main/java/org/bukkit/boss/BarColor.java b/src/main/java/org/bukkit/boss/BarColor.java +index e191d9ffe8d6fdeaef77313535a697b6038a0550..2ce3201079701de65c434b8f1e390bed27364370 100644 +--- a/src/main/java/org/bukkit/boss/BarColor.java ++++ b/src/main/java/org/bukkit/boss/BarColor.java +@@ -1,5 +1,8 @@ + package org.bukkit.boss; + ++/** ++ * Bar color ++ */ + public enum BarColor { + PINK, + BLUE, +diff --git a/src/main/java/org/bukkit/boss/BarFlag.java b/src/main/java/org/bukkit/boss/BarFlag.java +index 69e02998d062f5b52ef4e5cdd4dbb29384eb9f3c..0d8f617dc33b828bdadf3e8112b4c545625a55b2 100644 +--- a/src/main/java/org/bukkit/boss/BarFlag.java ++++ b/src/main/java/org/bukkit/boss/BarFlag.java +@@ -1,5 +1,8 @@ + package org.bukkit.boss; + ++/** ++ * Bar flag ++ */ + public enum BarFlag { + + /** +diff --git a/src/main/java/org/bukkit/boss/BarStyle.java b/src/main/java/org/bukkit/boss/BarStyle.java +index 3e499eb77957851ca67ca37bd116c617b44b6478..6907889ba91a32583cf62749a3148d3d2cd93925 100644 +--- a/src/main/java/org/bukkit/boss/BarStyle.java ++++ b/src/main/java/org/bukkit/boss/BarStyle.java +@@ -1,5 +1,8 @@ + package org.bukkit.boss; + ++/** ++ * Boss bar style ++ */ + public enum BarStyle { + /** + * Makes the boss bar solid (no segments) +diff --git a/src/main/java/org/bukkit/boss/BossBar.java b/src/main/java/org/bukkit/boss/BossBar.java +index 70274f2e2a1d6f27c4febd9d5d5fa3ee1b49f100..3b98e6e3e6dea0df5fb9462c78e8c142fde64723 100644 +--- a/src/main/java/org/bukkit/boss/BossBar.java ++++ b/src/main/java/org/bukkit/boss/BossBar.java +@@ -5,6 +5,9 @@ import org.bukkit.entity.Player; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents a boss bar ++ */ + public interface BossBar { + + /** +diff --git a/src/main/java/org/bukkit/command/CommandSender.java b/src/main/java/org/bukkit/command/CommandSender.java +index de4370233e0358da30d3704145044a99d8369f52..29d2077926620786c74b1f2f6ec6067a818d6e82 100644 +--- a/src/main/java/org/bukkit/command/CommandSender.java ++++ b/src/main/java/org/bukkit/command/CommandSender.java +@@ -55,6 +55,9 @@ public interface CommandSender extends Permissible { + public String getName(); + + // Spigot start ++ /** ++ * Spigot stuffs ++ */ + public class Spigot { + + /** +diff --git a/src/main/java/org/bukkit/conversations/Conversation.java b/src/main/java/org/bukkit/conversations/Conversation.java +index bf2407c838bc20197802687c150d513f4e86ed2b..ae09abfe9fe2979e89cfb4bb5c9cc0e7760943e7 100644 +--- a/src/main/java/org/bukkit/conversations/Conversation.java ++++ b/src/main/java/org/bukkit/conversations/Conversation.java +@@ -295,6 +295,9 @@ public class Conversation { + } + } + ++ /** ++ * Conversation state ++ */ + public enum ConversationState { + UNSTARTED, + STARTED, +diff --git a/src/main/java/org/bukkit/entity/AbstractArrow.java b/src/main/java/org/bukkit/entity/AbstractArrow.java +index b1d8007eed489aa061c1a6813bcdafc101231e56..eb847e3bb110f73695ba9b4191e69e6ea8a6ffc8 100644 +--- a/src/main/java/org/bukkit/entity/AbstractArrow.java ++++ b/src/main/java/org/bukkit/entity/AbstractArrow.java +@@ -176,6 +176,9 @@ public interface AbstractArrow extends Projectile { + this.setPickupStatus(PickupStatus.valueOf(rule.name())); + } + ++ /** ++ * Pickup rule ++ */ + @Deprecated + enum PickupRule { + DISALLOWED, +diff --git a/src/main/java/org/bukkit/entity/AnimalTamer.java b/src/main/java/org/bukkit/entity/AnimalTamer.java +index 2e17b2d4f759531fbe9ee8e9b00c839186af09ca..9382234722792b5920a2456187e079581c2e2f2a 100644 +--- a/src/main/java/org/bukkit/entity/AnimalTamer.java ++++ b/src/main/java/org/bukkit/entity/AnimalTamer.java +@@ -4,6 +4,9 @@ import java.util.UUID; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents an animal tamer ++ */ + public interface AnimalTamer { + + /** +diff --git a/src/main/java/org/bukkit/entity/ArmorStand.java b/src/main/java/org/bukkit/entity/ArmorStand.java +index 8ca6c9eba926f436203af211c6e274a59ddb15e8..f61419d3ce15bd553a864e4e9cd988b57d8f9695 100644 +--- a/src/main/java/org/bukkit/entity/ArmorStand.java ++++ b/src/main/java/org/bukkit/entity/ArmorStand.java +@@ -7,6 +7,9 @@ import org.bukkit.util.EulerAngle; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents an armor stand ++ */ + public interface ArmorStand extends LivingEntity { + + /** +diff --git a/src/main/java/org/bukkit/entity/Arrow.java b/src/main/java/org/bukkit/entity/Arrow.java +index ec8443b67014c0129256c9227cc89686422b9217..6b41e09d6ed075aae5455929b5b29efb2c6287f6 100644 +--- a/src/main/java/org/bukkit/entity/Arrow.java ++++ b/src/main/java/org/bukkit/entity/Arrow.java +@@ -8,6 +8,9 @@ import org.bukkit.potion.PotionEffectType; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents an arrow ++ */ + public interface Arrow extends AbstractArrow { + + /** +diff --git a/src/main/java/org/bukkit/entity/Dolphin.java b/src/main/java/org/bukkit/entity/Dolphin.java +index f00eaadcdde7ceef95def2d8ec6eb63a76c177bd..a4a645799d82c730e3280519facf1347d26a859f 100644 +--- a/src/main/java/org/bukkit/entity/Dolphin.java ++++ b/src/main/java/org/bukkit/entity/Dolphin.java +@@ -1,3 +1,6 @@ + package org.bukkit.entity; + ++/** ++ * Represents a dolphin ++ */ + public interface Dolphin extends WaterMob { } +diff --git a/src/main/java/org/bukkit/entity/DragonFireball.java b/src/main/java/org/bukkit/entity/DragonFireball.java +index 6c475a3723721b33bb7709d8c1bbf487a10f9bbe..210d955e9bbb669c8ce644c935c1607ae8e7419b 100644 +--- a/src/main/java/org/bukkit/entity/DragonFireball.java ++++ b/src/main/java/org/bukkit/entity/DragonFireball.java +@@ -1,3 +1,6 @@ + package org.bukkit.entity; + ++/** ++ * Represents a dragon's fireball ++ */ + public interface DragonFireball extends Fireball {} +diff --git a/src/main/java/org/bukkit/entity/Endermite.java b/src/main/java/org/bukkit/entity/Endermite.java +index d9be83961b28b927a587f6dbb339b531520e4865..1ff4c5e283ac05c405c09bd4b853066452614696 100644 +--- a/src/main/java/org/bukkit/entity/Endermite.java ++++ b/src/main/java/org/bukkit/entity/Endermite.java +@@ -1,5 +1,8 @@ + package org.bukkit.entity; + ++/** ++ * Represents an endermite ++ */ + public interface Endermite extends Monster { + + /** +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index 5f7947cd6f3bf9f76e8b3bac339f61b9afadaaad..08dbe8208fad174f03a0e08c26bb48a0729ec0ce 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -622,6 +622,9 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + Pose getPose(); + + // Spigot start ++ /** ++ * Spigot stuffs ++ */ + public class Spigot extends CommandSender.Spigot { + + } +@@ -665,36 +668,50 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + + /** + * Check if entity is in rain ++ * ++ * @return True if entity is in rain + */ + public boolean isInRain(); + + /** + * Check if entity is in bubble column ++ * ++ * @return True if entity is in bubble column + */ + public boolean isInBubbleColumn(); + + /** + * Check if entity is in water or rain ++ * ++ * @return True if entity is in water or rain + */ + public boolean isInWaterOrRain(); + + /** + * Check if entity is in water or bubble column ++ * ++ * @return True if entity is in water or bubble column + */ + public boolean isInWaterOrBubbleColumn(); + + /** + * Check if entity is in water or rain or bubble column ++ * ++ * @return True if entity is in water or rain or bubble column + */ + public boolean isInWaterOrRainOrBubbleColumn(); + + /** + * Check if entity is in lava ++ * ++ * @return True if entity is in lava + */ + public boolean isInLava(); + + /** + * Check if entity is inside a ticking chunk ++ * ++ * @return True if entity is ticking + */ + public boolean isTicking(); + // Paper end +diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java +index 692b75eb78405874077c850bfc72e247ccc80860..31fc511edc33635438e93d3c14292305ac30a38f 100644 +--- a/src/main/java/org/bukkit/entity/EntityType.java ++++ b/src/main/java/org/bukkit/entity/EntityType.java +@@ -20,6 +20,9 @@ import org.jetbrains.annotations.Contract; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Entity type ++ */ + public enum EntityType implements Keyed { + + // These strings MUST match the strings in nms.EntityTypes and are case sensitive. +diff --git a/src/main/java/org/bukkit/entity/Firework.java b/src/main/java/org/bukkit/entity/Firework.java +index d616d5941b3c7b85e350e845901da798601b9a3c..14bdddb7ced9c4cd92a8ad96d97a08a6ed4c25bf 100644 +--- a/src/main/java/org/bukkit/entity/Firework.java ++++ b/src/main/java/org/bukkit/entity/Firework.java +@@ -3,6 +3,9 @@ package org.bukkit.entity; + import org.bukkit.inventory.meta.FireworkMeta; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents a firework ++ */ + public interface Firework extends Projectile { + + /** +diff --git a/src/main/java/org/bukkit/entity/Guardian.java b/src/main/java/org/bukkit/entity/Guardian.java +index 4da9f3c5f1423bf8f9eeb490736cabf027853e60..082e90859e6c965029606d7d395673a81bff2cb4 100644 +--- a/src/main/java/org/bukkit/entity/Guardian.java ++++ b/src/main/java/org/bukkit/entity/Guardian.java +@@ -1,5 +1,8 @@ + package org.bukkit.entity; + ++/** ++ * Represents a guardian ++ */ + public interface Guardian extends Monster { + + /** +diff --git a/src/main/java/org/bukkit/entity/LightningStrike.java b/src/main/java/org/bukkit/entity/LightningStrike.java +index 2c81a3f685588431a3c7675c84b35a28975232af..efb308c82580722e5106d5d1c7512d99c38e536a 100644 +--- a/src/main/java/org/bukkit/entity/LightningStrike.java ++++ b/src/main/java/org/bukkit/entity/LightningStrike.java +@@ -15,6 +15,9 @@ public interface LightningStrike extends Entity { + public boolean isEffect(); + + // Spigot start ++ /** ++ * Spigot stuffs ++ */ + public class Spigot extends Entity.Spigot { + + /* +diff --git a/src/main/java/org/bukkit/entity/Panda.java b/src/main/java/org/bukkit/entity/Panda.java +index a6a7429ed2e1eefb2b12b7480ed74fcc3963a864..e8027e1d505dda6effbb1698550016e87b2e581f 100644 +--- a/src/main/java/org/bukkit/entity/Panda.java ++++ b/src/main/java/org/bukkit/entity/Panda.java +@@ -37,6 +37,9 @@ public interface Panda extends Animals { + */ + void setHiddenGene(@NotNull Gene gene); + ++ /** ++ * Panda gene type ++ */ + public enum Gene { + + NORMAL(false), +diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java +index a79703115da811397ee6b7c6c079846af537fd12..5ad9de1f34b1cca3e31b8a142e3831739a3e824a 100644 +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -1776,6 +1776,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM + void resetCooldown(); + + /** ++ * @param ClientOption type ++ * @param option ClientOption + * @return the client option value of the player + */ + @NotNull +@@ -1807,6 +1809,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM + // Paper end + + // Spigot start ++ /** ++ * Spigot stuffs ++ */ + public class Spigot extends Entity.Spigot { + + /** +diff --git a/src/main/java/org/bukkit/entity/Rabbit.java b/src/main/java/org/bukkit/entity/Rabbit.java +index e88154283a8ef594e160d25005870053de15568a..24c81708dc6691e220e278e92c07b9d51072fb88 100644 +--- a/src/main/java/org/bukkit/entity/Rabbit.java ++++ b/src/main/java/org/bukkit/entity/Rabbit.java +@@ -2,6 +2,9 @@ package org.bukkit.entity; + + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents a rabbit ++ */ + public interface Rabbit extends Animals { + + /** +diff --git a/src/main/java/org/bukkit/entity/Raider.java b/src/main/java/org/bukkit/entity/Raider.java +index 9a99b8ca1ec9c3c88b29275c88b1221e1b22bcef..f1763f75d5f223ef70b968e4633616731b727df5 100644 +--- a/src/main/java/org/bukkit/entity/Raider.java ++++ b/src/main/java/org/bukkit/entity/Raider.java +@@ -3,6 +3,9 @@ package org.bukkit.entity; + import org.bukkit.block.Block; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents a raider entity ++ */ + public interface Raider extends Monster { + + /** +diff --git a/src/main/java/org/bukkit/entity/Shulker.java b/src/main/java/org/bukkit/entity/Shulker.java +index 3441bdb7fcb99dab67bfe9dad5ed989009e443ad..52927a7d84a2bfcae04526f38bf5efd75b3459bb 100644 +--- a/src/main/java/org/bukkit/entity/Shulker.java ++++ b/src/main/java/org/bukkit/entity/Shulker.java +@@ -2,4 +2,7 @@ package org.bukkit.entity; + + import org.bukkit.material.Colorable; + ++/** ++ * Represents a shulker ++ */ + public interface Shulker extends Golem, Colorable {} +diff --git a/src/main/java/org/bukkit/entity/ShulkerBullet.java b/src/main/java/org/bukkit/entity/ShulkerBullet.java +index 4623e0d767b343cbdc6fcf20b3b2ff7ff14863cf..ca3f98a8272bab3c9f57f59b077b206c6503de80 100644 +--- a/src/main/java/org/bukkit/entity/ShulkerBullet.java ++++ b/src/main/java/org/bukkit/entity/ShulkerBullet.java +@@ -2,6 +2,9 @@ package org.bukkit.entity; + + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents a shulker bullet ++ */ + public interface ShulkerBullet extends Projectile { + + /** +diff --git a/src/main/java/org/bukkit/entity/Skeleton.java b/src/main/java/org/bukkit/entity/Skeleton.java +index 1c367f78eadf24850061a84ce63b950b79d3c435..9021865244a7eacf0477b0db790e0ff41fc8ddfd 100644 +--- a/src/main/java/org/bukkit/entity/Skeleton.java ++++ b/src/main/java/org/bukkit/entity/Skeleton.java +@@ -27,7 +27,9 @@ public interface Skeleton extends Monster, RangedEntity { // Paper + @Contract("_ -> fail") + public void setSkeletonType(SkeletonType type); + +- /* ++ /** ++ * Skeleton type ++ * + * @deprecated classes are different types + */ + @Deprecated +diff --git a/src/main/java/org/bukkit/entity/Tameable.java b/src/main/java/org/bukkit/entity/Tameable.java +index 65e68da98ab66ed781bce2f0dbe0913be48d2990..cb708ae66f60a36ac0f529614743e33511e4bd90 100644 +--- a/src/main/java/org/bukkit/entity/Tameable.java ++++ b/src/main/java/org/bukkit/entity/Tameable.java +@@ -3,6 +3,9 @@ package org.bukkit.entity; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents a tameable entity ++ */ + public interface Tameable extends Animals { + + /** +diff --git a/src/main/java/org/bukkit/entity/ThrowableProjectile.java b/src/main/java/org/bukkit/entity/ThrowableProjectile.java +index ceb3e2c5117740ce284e893fff8e41a002d78649..fab5c3c90f55c113cae2bca2354a94e88c1aaece 100644 +--- a/src/main/java/org/bukkit/entity/ThrowableProjectile.java ++++ b/src/main/java/org/bukkit/entity/ThrowableProjectile.java +@@ -3,6 +3,9 @@ package org.bukkit.entity; + import org.bukkit.inventory.ItemStack; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents a throwable projectile ++ */ + public interface ThrowableProjectile extends Projectile { + + /** +diff --git a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java +index 63c80b4ee1f7adc8a9efc3b607993104b1991f90..b5d6dc0d864833880b59fc52a0b49d37b0904f98 100644 +--- a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java ++++ b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java +@@ -4,6 +4,9 @@ import org.bukkit.entity.Minecart; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents a minecart with command block ++ */ + public interface CommandMinecart extends Minecart { + + /** +diff --git a/src/main/java/org/bukkit/event/Cancellable.java b/src/main/java/org/bukkit/event/Cancellable.java +index 799b0b0f3cd842edd2bc1005c2e848f9a0b7b43c..7f02db9d1660b7b33d8c3825114b5040e5461696 100644 +--- a/src/main/java/org/bukkit/event/Cancellable.java ++++ b/src/main/java/org/bukkit/event/Cancellable.java +@@ -1,5 +1,8 @@ + package org.bukkit.event; + ++/** ++ * Represents a cancellable event ++ */ + public interface Cancellable { + + /** +diff --git a/src/main/java/org/bukkit/event/Event.java b/src/main/java/org/bukkit/event/Event.java +index 8ec56cd6b8e0f5c5dd8c7c88b4671e18dcf109d0..740bbce54140480039a637c9fee6ccb3f4da1027 100644 +--- a/src/main/java/org/bukkit/event/Event.java ++++ b/src/main/java/org/bukkit/event/Event.java +@@ -95,6 +95,9 @@ public abstract class Event { + return async; + } + ++ /** ++ * Event result ++ */ + public enum Result { + + /** +diff --git a/src/main/java/org/bukkit/event/EventException.java b/src/main/java/org/bukkit/event/EventException.java +index 84638e852501cc804c13c188c90c38b163657c36..a32f7d86407a36d34932101a8b46751c5bed0d75 100644 +--- a/src/main/java/org/bukkit/event/EventException.java ++++ b/src/main/java/org/bukkit/event/EventException.java +@@ -1,5 +1,8 @@ + package org.bukkit.event; + ++/** ++ * Event exception ++ */ + public class EventException extends Exception { + private static final long serialVersionUID = 3532808232324183999L; + private final Throwable cause; +diff --git a/src/main/java/org/bukkit/event/block/Action.java b/src/main/java/org/bukkit/event/block/Action.java +index 25d26e3fe713311e66d7e634a6c32af61f4cef59..2825263c102d3f9ed37f6884e09ec5efb8105fb9 100644 +--- a/src/main/java/org/bukkit/event/block/Action.java ++++ b/src/main/java/org/bukkit/event/block/Action.java +@@ -1,5 +1,8 @@ + package org.bukkit.event.block; + ++/** ++ * Block action ++ */ + public enum Action { + + /** +diff --git a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java +index 4aaa78afdda2d2351f8c4ed46a52e0cf77ec437c..4d2d821003840b7fc1ca412d71b841341c7b51ec 100644 +--- a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java ++++ b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java +@@ -8,6 +8,9 @@ import org.bukkit.event.HandlerList; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Called when a cauldron changes fluid level ++ */ + public class CauldronLevelChangeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); +@@ -75,6 +78,9 @@ public class CauldronLevelChangeEvent extends BlockEvent implements Cancellable + return handlers; + } + ++ /** ++ * Cauldron level change reason ++ */ + public enum ChangeReason { + /** + * Player emptying the cauldron by filling their bucket. +diff --git a/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java b/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java +index 7d23333e71482920cc42a4d8f3f38a7525aefe1f..765aa67f4cbb535128070d3310d1be9ecede3bf8 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java +@@ -79,6 +79,9 @@ public class EntityTransformEvent extends EntityEvent implements Cancellable { + return handlers; + } + ++ /** ++ * Entity transform reason ++ */ + public enum TransformReason { + /** + * When a zombie gets cured and a villager is spawned. +diff --git a/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java +index a33986a0c437a673435206fc337031a7eebdab3b..99a8e452904de21a5bd82f13f6b2d46537d07289 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java +@@ -37,6 +37,9 @@ public class EntityUnleashEvent extends EntityEvent { + return handlers; + } + ++ /** ++ * Entity unleash reason ++ */ + public enum UnleashReason { + /** + * When the entity's leashholder has died or logged out, and so is +diff --git a/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java b/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java +index e378cc29b47238fe12ae9aff5171edcff6b456f5..f5b9fd0b6f9512e425e1cc6103f80ba198c6db5b 100644 +--- a/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java ++++ b/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java +@@ -5,6 +5,9 @@ import org.bukkit.event.Cancellable; + import org.bukkit.event.HandlerList; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Called when an item merges with another ++ */ + public class ItemMergeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); +diff --git a/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java b/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java +index b550029cf3a7bc55137851eab734abab8965306d..d070baf9587edccdd95204771f59491f5c4ba10d 100644 +--- a/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java ++++ b/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java +@@ -6,6 +6,9 @@ import org.bukkit.event.Cancellable; + import org.bukkit.event.HandlerList; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Called when a villager changes career ++ */ + public class VillagerCareerChangeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); +diff --git a/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java +index 21ad8888c0e403bfc63518502577d651c02dda05..295cbe558ace7b55c80fc84256808d2f505ea734 100644 +--- a/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java ++++ b/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java +@@ -18,6 +18,9 @@ public class InventoryCloseEvent extends InventoryEvent { + return reason; + } + ++ /** ++ * Inventory close reason ++ */ + public enum Reason { + /** + * Unknown reason +diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java +index 2351283df16dac808d77b840aa88732d7b28c0a1..007d3e45095a5b6e35b6af681e6e042830a5432d 100644 +--- a/src/main/java/org/bukkit/event/inventory/InventoryType.java ++++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java +@@ -171,6 +171,9 @@ public enum InventoryType { + return isCreatable; + } + ++ /** ++ * Inventory slot type ++ */ + public enum SlotType { + /** + * A result slot in a furnace or crafting inventory. +diff --git a/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java b/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java +index efd29d198dd847e22988963f70ad57e1b810aeb7..b5de6e77a030057f923a5d82ea0054b9e138009d 100644 +--- a/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java ++++ b/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java +@@ -7,6 +7,9 @@ import org.bukkit.inventory.Recipe; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Prepare item craft event ++ */ + public class PrepareItemCraftEvent extends InventoryEvent { + private static final HandlerList handlers = new HandlerList(); + private boolean repair; +diff --git a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java +index af52a5dfb452da11e51cad9c882cae1533cba520..30a7ac800096866d2a09dd304ffd17c42c5b2d9a 100644 +--- a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java ++++ b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java +@@ -60,6 +60,9 @@ public class PlayerQuitEvent extends PlayerEvent { + return this.reason; + } + ++ /** ++ * Player quit reason ++ */ + public enum QuitReason { + /** + * The player left on their own behalf. +diff --git a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java +index 553d7740489fe729166c8ca8ef8c7834db3663ad..4a2d61912ffed137b2b3e4cc4d9b32a11207f6ba 100644 +--- a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java ++++ b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java +@@ -33,6 +33,9 @@ public class PlayerTeleportEvent extends PlayerMoveEvent { + return cause; + } + ++ /** ++ * Player teleport cause ++ */ + public enum TeleportCause { + /** + * Indicates the teleporation was caused by a player throwing an Ender +diff --git a/src/main/java/org/bukkit/event/raid/RaidStopEvent.java b/src/main/java/org/bukkit/event/raid/RaidStopEvent.java +index 9e852ac973d7a38c075249360be483ed0e5f5ac6..55db1a074144c709489d7f6a4e353b8fd94d312e 100644 +--- a/src/main/java/org/bukkit/event/raid/RaidStopEvent.java ++++ b/src/main/java/org/bukkit/event/raid/RaidStopEvent.java +@@ -40,6 +40,9 @@ public class RaidStopEvent extends RaidEvent { + return handlers; + } + ++ /** ++ * Raid stop reason ++ */ + public enum Reason { + + /** +diff --git a/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java b/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java +index 418f9391d86fff0d0a75da0574edccbb29aa9931..921d964d7e40e7710b5a5db18bd9329ca40c1ece 100644 +--- a/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java ++++ b/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java +@@ -67,6 +67,9 @@ public class LightningStrikeEvent extends WeatherEvent implements Cancellable { + return handlers; + } + ++ /** ++ * Lightning strike cause ++ */ + public enum Cause { + /** + * Triggered by the /summon command. +diff --git a/src/main/java/org/bukkit/help/HelpTopicComparator.java b/src/main/java/org/bukkit/help/HelpTopicComparator.java +index 75bb69283f509e8f4fec772714a509a51be9de19..e156847f5b7b86155a7a0a0b8cefd8ac1530171e 100644 +--- a/src/main/java/org/bukkit/help/HelpTopicComparator.java ++++ b/src/main/java/org/bukkit/help/HelpTopicComparator.java +@@ -31,6 +31,9 @@ public final class HelpTopicComparator implements Comparator { + return tnc.compare(lhs.getName(), rhs.getName()); + } + ++ /** ++ * Topic name comparator ++ */ + public static final class TopicNameComparator implements Comparator { + private TopicNameComparator(){} + +diff --git a/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java b/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java +index 163ffe8ff76ded6265d865901d5110fb6a56950d..36145294db34d273bb767cc928453b765a30e9db 100644 +--- a/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java ++++ b/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java +@@ -2,6 +2,9 @@ package org.bukkit.inventory; + + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents an armored horse's inventory ++ */ + public interface ArmoredHorseInventory extends AbstractHorseInventory { + + /** +diff --git a/src/main/java/org/bukkit/inventory/EquipmentSlot.java b/src/main/java/org/bukkit/inventory/EquipmentSlot.java +index 1e7d77118a55ca9db99eabb94894e6ef3409946b..ad7db4407c83b19bc8ecc9b849152af42d5c4ddb 100644 +--- a/src/main/java/org/bukkit/inventory/EquipmentSlot.java ++++ b/src/main/java/org/bukkit/inventory/EquipmentSlot.java +@@ -1,5 +1,8 @@ + package org.bukkit.inventory; + ++/** ++ * Equipment slot ++ */ + public enum EquipmentSlot { + + HAND, +diff --git a/src/main/java/org/bukkit/inventory/InventoryHolder.java b/src/main/java/org/bukkit/inventory/InventoryHolder.java +index c7b17eabf07b829a02afe7c1f27a5127b6bfea70..d4e2bcf8ce8fc2af851b471490147f0092ea456a 100644 +--- a/src/main/java/org/bukkit/inventory/InventoryHolder.java ++++ b/src/main/java/org/bukkit/inventory/InventoryHolder.java +@@ -2,6 +2,9 @@ package org.bukkit.inventory; + + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents an inventory holder ++ */ + public interface InventoryHolder { + + /** +diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java +index 23d55f756b2bb5a557bfae102d7039d8394fbe69..50e58cf9a494c2cf17b7f55918e3d21f63437b3c 100644 +--- a/src/main/java/org/bukkit/inventory/ItemFactory.java ++++ b/src/main/java/org/bukkit/inventory/ItemFactory.java +@@ -169,7 +169,7 @@ public interface ItemFactory { + /** + * Creates a {@link net.md_5.bungee.api.chat.hover.content.Content} of that ItemStack for displaying. + * +- * @param itemStack ++ * @param itemStack ItemStack to check + * @return the {@link net.md_5.bungee.api.chat.hover.content.Content} of that ItemStack + */ + @NotNull +diff --git a/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java b/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java +index 7944f26a3e2a92601c3be0e55c00c39cc16cf177..8e7bb66c96d34b73959c0653b2a8e7b422da35fe 100644 +--- a/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java ++++ b/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java +@@ -1,3 +1,6 @@ + package org.bukkit.inventory; + ++/** ++ * Represents a saddled horse's inventory ++ */ + public interface SaddledHorseInventory extends AbstractHorseInventory {} +diff --git a/src/main/java/org/bukkit/inventory/meta/BannerMeta.java b/src/main/java/org/bukkit/inventory/meta/BannerMeta.java +index 4739d2ecc26e7e4adc1b297013da98e12fe58783..45ebb3ca8d628b708419bd2beedd94ee4c819b8a 100644 +--- a/src/main/java/org/bukkit/inventory/meta/BannerMeta.java ++++ b/src/main/java/org/bukkit/inventory/meta/BannerMeta.java +@@ -6,6 +6,9 @@ import org.bukkit.block.banner.Pattern; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents metadata on a banner ++ */ + public interface BannerMeta extends ItemMeta { + + /** +diff --git a/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java b/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java +index 473c72dcd34d3f6be72e2ab87c5af51819a00e33..a73b59f40eb3c4d94074154591f9f6885fb287ca 100644 +--- a/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java ++++ b/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java +@@ -4,6 +4,9 @@ import org.bukkit.Material; + import org.bukkit.block.data.BlockData; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents metadata on a block ++ */ + public interface BlockDataMeta extends ItemMeta { + + /** +diff --git a/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java b/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java +index e7d905b1146b2bdd2da5bdeb6bf3541fb181d59e..1fab68c9de96b0d362ebf85fd675cc19099aefa1 100644 +--- a/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java ++++ b/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java +@@ -4,6 +4,9 @@ package org.bukkit.inventory.meta; + import org.bukkit.block.BlockState; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents metadata on a blockstate ++ */ + public interface BlockStateMeta extends ItemMeta { + + /** +diff --git a/src/main/java/org/bukkit/inventory/meta/BookMeta.java b/src/main/java/org/bukkit/inventory/meta/BookMeta.java +index 94852d50e88d0594b84b581cd627174043629995..c63257f38dffd05977e3676e7c341123f01fe282 100644 +--- a/src/main/java/org/bukkit/inventory/meta/BookMeta.java ++++ b/src/main/java/org/bukkit/inventory/meta/BookMeta.java +@@ -188,6 +188,9 @@ public interface BookMeta extends ItemMeta { + BookMeta clone(); + + // Spigot start ++ /** ++ * Spigot stuffs ++ */ + public class Spigot { + + /** +diff --git a/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java b/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java +index 35c6594fd1040a1af1029e7260e5e3a9307b107d..47975b24ffa01c9872f6e910d14e1c8e0d0481b9 100644 +--- a/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java ++++ b/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java +@@ -5,6 +5,9 @@ import org.bukkit.inventory.ItemStack; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Represents metadata on a crossbow ++ */ + public interface CrossbowMeta extends ItemMeta { + + /** +diff --git a/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java b/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java +index 736c60c71d112e8c017473a93091b4e5336a996f..88c7b311128d605c8d33e1b075795a3a1a434fa5 100644 +--- a/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java ++++ b/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java +@@ -4,6 +4,9 @@ import java.util.List; + import org.bukkit.NamespacedKey; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents metadata on a knowledge book ++ */ + public interface KnowledgeBookMeta extends ItemMeta { + + /** +diff --git a/src/main/java/org/bukkit/material/CocoaPlant.java b/src/main/java/org/bukkit/material/CocoaPlant.java +index b1b1c729d182b676d8ea69a8d3c942c6820863dd..222c2ae29bc150bbc44c74885b6565911a666911 100644 +--- a/src/main/java/org/bukkit/material/CocoaPlant.java ++++ b/src/main/java/org/bukkit/material/CocoaPlant.java +@@ -12,6 +12,9 @@ import org.bukkit.block.BlockFace; + @Deprecated + public class CocoaPlant extends MaterialData implements Directional, Attachable { + ++ /** ++ * Cocoa plant size ++ */ + public enum CocoaPlantSize { + SMALL, + MEDIUM, +diff --git a/src/main/java/org/bukkit/material/Directional.java b/src/main/java/org/bukkit/material/Directional.java +index 8c1c7b0a258bd4e601955827c4f5a72b81a60db2..f188563dd0db1d7e1dab5e1cce5d76339061df3e 100644 +--- a/src/main/java/org/bukkit/material/Directional.java ++++ b/src/main/java/org/bukkit/material/Directional.java +@@ -3,6 +3,9 @@ package org.bukkit.material; + import org.bukkit.block.BlockFace; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents something that can face a direction ++ */ + public interface Directional { + + /** +diff --git a/src/main/java/org/bukkit/material/Openable.java b/src/main/java/org/bukkit/material/Openable.java +index 0ae54f973d11df74abb3105cf9226afb130b4f33..6541bca9c6c4ccedf059d2297b54b738588a02dc 100644 +--- a/src/main/java/org/bukkit/material/Openable.java ++++ b/src/main/java/org/bukkit/material/Openable.java +@@ -1,5 +1,8 @@ + package org.bukkit.material; + ++/** ++ * Represents something that can be opened ++ */ + public interface Openable { + + /** +diff --git a/src/main/java/org/bukkit/material/PressureSensor.java b/src/main/java/org/bukkit/material/PressureSensor.java +index de20bd39c532e94a11536a67c1af71bea203aedc..aa14be496bfe05bf3882f8ac50ef88b8ad655302 100644 +--- a/src/main/java/org/bukkit/material/PressureSensor.java ++++ b/src/main/java/org/bukkit/material/PressureSensor.java +@@ -1,5 +1,8 @@ + package org.bukkit.material; + ++/** ++ * Represents a pressure sensor ++ */ + public interface PressureSensor { + public boolean isPressed(); + } +diff --git a/src/main/java/org/bukkit/metadata/MetadataStore.java b/src/main/java/org/bukkit/metadata/MetadataStore.java +index 29f86fa938c2758cbdf8dec22519a18c3e119818..8fca91925ce7d3fdcec838a3f1c9ba3e4ddc5a9c 100644 +--- a/src/main/java/org/bukkit/metadata/MetadataStore.java ++++ b/src/main/java/org/bukkit/metadata/MetadataStore.java +@@ -4,6 +4,11 @@ import java.util.List; + import org.bukkit.plugin.Plugin; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Metadata store ++ * ++ * @param Type ++ */ + public interface MetadataStore { + /** + * Adds a metadata value to an object. +diff --git a/src/main/java/org/bukkit/metadata/MetadataStoreBase.java b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java +index abbe545af572687a0399c2387434863cd2b70f68..81024450c3cf28657e2c38fd164dad034f47af22 100644 +--- a/src/main/java/org/bukkit/metadata/MetadataStoreBase.java ++++ b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java +@@ -12,6 +12,11 @@ import org.apache.commons.lang.Validate; + import org.bukkit.plugin.Plugin; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Metadata store base ++ * ++ * @param Type ++ */ + public abstract class MetadataStoreBase { + private Map> metadataMap = new java.util.concurrent.ConcurrentHashMap>(); // Paper + +diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java +index 4b4d57924b8b2aecf4ebf92edc805334ffa53d0e..9df3d1c71a399c4d3f610bcd96aa401b4ea0c708 100644 +--- a/src/main/java/org/bukkit/metadata/MetadataValue.java ++++ b/src/main/java/org/bukkit/metadata/MetadataValue.java +@@ -4,6 +4,9 @@ import org.bukkit.plugin.Plugin; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + ++/** ++ * Metadata value ++ */ + public interface MetadataValue { + + /** +diff --git a/src/main/java/org/bukkit/plugin/AuthorNagException.java b/src/main/java/org/bukkit/plugin/AuthorNagException.java +index 6565a441467e323b3e1871485a9e09e4cfbea050..20985f022afa077ba0907f3404175cb4500fa29f 100644 +--- a/src/main/java/org/bukkit/plugin/AuthorNagException.java ++++ b/src/main/java/org/bukkit/plugin/AuthorNagException.java +@@ -1,5 +1,8 @@ + package org.bukkit.plugin; + ++/** ++ * Author nag exception ++ */ + @SuppressWarnings("serial") + public class AuthorNagException extends RuntimeException { + private final String message; +diff --git a/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java b/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java +index 21a3d767baf9f76746b2a5f2b3af134fe1e96e8a..6d7a29554f337333f4cf6095d9d0ca9e275f8f4f 100644 +--- a/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java ++++ b/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java +@@ -3,6 +3,9 @@ package org.bukkit.projectiles; + import org.bukkit.block.Block; + import org.jetbrains.annotations.NotNull; + ++/** ++ * Represents a block projectile source ++ */ + public interface BlockProjectileSource extends ProjectileSource { + + /** diff --git a/patches/Purpur/patches/api/0035-PlayerBookTooLargeEvent.patch b/patches/Purpur/patches/api/0035-PlayerBookTooLargeEvent.patch new file mode 100644 index 00000000..dc89fde7 --- /dev/null +++ b/patches/Purpur/patches/api/0035-PlayerBookTooLargeEvent.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 23 Dec 2020 00:43:27 -0600 +Subject: [PATCH] PlayerBookTooLargeEvent + + +diff --git a/src/main/java/net/pl3x/purpur/event/player/PlayerBookTooLargeEvent.java b/src/main/java/net/pl3x/purpur/event/player/PlayerBookTooLargeEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..39378ee2bfadf42ff358cc7b42dd75ac61615e15 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/player/PlayerBookTooLargeEvent.java +@@ -0,0 +1,65 @@ ++package net.pl3x.purpur.event.player; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.entity.Player; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.PlayerEvent; ++import org.bukkit.inventory.ItemStack; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when a player tries to bypass book limitations ++ */ ++public class PlayerBookTooLargeEvent extends PlayerEvent { ++ private static final HandlerList handlers = new HandlerList(); ++ private final ItemStack book; ++ private boolean kickPlayer = true; ++ ++ /** ++ * @param player The player ++ * @param book The book ++ */ ++ public PlayerBookTooLargeEvent(@NotNull Player player, @NotNull ItemStack book) { ++ super(player, !Bukkit.isPrimaryThread()); ++ this.book = book; ++ } ++ ++ /** ++ * Get the book containing the wanted edits ++ * ++ * @return The book ++ */ ++ @NotNull ++ public ItemStack getBook() { ++ return book; ++ } ++ ++ /** ++ * Whether server should kick the player or not ++ * ++ * @return True to kick player ++ */ ++ public boolean shouldKickPlayer() { ++ return kickPlayer; ++ } ++ ++ /** ++ * Whether server should kick the player or not ++ * ++ * @param kickPlayer True to kick player ++ */ ++ public void setShouldKickPlayer(boolean kickPlayer) { ++ this.kickPlayer = kickPlayer; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/Purpur/patches/api/0036-Full-netherite-armor-grants-fire-resistance.patch b/patches/Purpur/patches/api/0036-Full-netherite-armor-grants-fire-resistance.patch new file mode 100644 index 00000000..4cf6d1a0 --- /dev/null +++ b/patches/Purpur/patches/api/0036-Full-netherite-armor-grants-fire-resistance.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 24 Dec 2020 11:00:04 -0600 +Subject: [PATCH] Full netherite armor grants fire resistance + + +diff --git a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java +index 16b5fd279b0cb926900247618bcdb381a93f5a35..d592c62aadb3245396865c098c5979f2a162f868 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java +@@ -213,6 +213,12 @@ public class EntityPotionEffectEvent extends EntityEvent implements Cancellable + * When all effects are removed due to a bucket of milk. + */ + MILK, ++ // Purpur start ++ /** ++ * When a player wears full netherite armor ++ */ ++ NETHERITE_ARMOR, ++ // Purpur end + /** + * When a player gets bad omen after killing a patrol captain. + */ diff --git a/patches/Purpur/patches/api/0037-Add-EntityTeleportHinderedEvent.patch b/patches/Purpur/patches/api/0037-Add-EntityTeleportHinderedEvent.patch new file mode 100644 index 00000000..587b3f0e --- /dev/null +++ b/patches/Purpur/patches/api/0037-Add-EntityTeleportHinderedEvent.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 9 Jan 2021 15:26:04 +0100 +Subject: [PATCH] Add EntityTeleportHinderedEvent + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/pl3x/purpur/event/entity/EntityTeleportHinderedEvent.java b/src/main/java/net/pl3x/purpur/event/entity/EntityTeleportHinderedEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e6fcc4027cb70061b804460b39e00cca273d35ad +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/entity/EntityTeleportHinderedEvent.java +@@ -0,0 +1,117 @@ ++package net.pl3x.purpur.event.entity; ++ ++import org.bukkit.entity.Entity; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.entity.EntityEvent; ++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * Fired when an entity is hindered from teleporting. ++ */ ++public class EntityTeleportHinderedEvent extends EntityEvent { ++ private static final HandlerList handlers = new HandlerList(); ++ ++ @NotNull ++ private final Reason reason; ++ ++ @Nullable ++ private final TeleportCause teleportCause; ++ ++ private boolean retry = false; ++ ++ public EntityTeleportHinderedEvent(@NotNull Entity what, @NotNull Reason reason, ++ @Nullable TeleportCause teleportCause) { ++ super(what); ++ this.reason = reason; ++ this.teleportCause = teleportCause; ++ } ++ ++ /** ++ * @return why the teleport was hindered. ++ */ ++ @NotNull ++ public Reason getReason() { ++ return reason; ++ } ++ ++ /** ++ * @return why the teleport occurred if cause was given, otherwise {@code null}. ++ */ ++ @Nullable ++ public TeleportCause getTeleportCause() { ++ return teleportCause; ++ } ++ ++ /** ++ * Whether the teleport should be retried. ++ *

++ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack ++ * overflow. Do not retry more than necessary. ++ *

++ * ++ * @return whether the teleport should be retried. ++ */ ++ public boolean shouldRetry() { ++ return retry; ++ } ++ ++ /** ++ * Sets whether the teleport should be retried. ++ *

++ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack ++ * overflow. Do not retry more than necessary. ++ *

++ * ++ * @param retry whether the teleport should be retried. ++ */ ++ public void setShouldRetry(boolean retry) { ++ this.retry = retry; ++ } ++ ++ /** ++ * Calls the event and tests if should retry. ++ * ++ * @return whether the teleport should be retried. ++ */ ++ @Override ++ public boolean callEvent() { ++ super.callEvent(); ++ return shouldRetry(); ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++ ++ /** ++ * Reason for hindrance in teleports. ++ */ ++ public enum Reason { ++ /** ++ * The teleported entity is a passenger of another entity. ++ */ ++ IS_PASSENGER, ++ ++ /** ++ * The teleported entity has passengers. ++ */ ++ IS_VEHICLE, ++ ++ /** ++ * The teleport event was cancelled. ++ *

++ * This is only caused by players teleporting. ++ *

++ */ ++ EVENT_CANCELLED, ++ } ++} diff --git a/patches/Purpur/patches/api/0038-Add-StructureGenerateEvent.patch b/patches/Purpur/patches/api/0038-Add-StructureGenerateEvent.patch new file mode 100644 index 00000000..6ef11884 --- /dev/null +++ b/patches/Purpur/patches/api/0038-Add-StructureGenerateEvent.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nahuel +Date: Sat, 9 Jan 2021 15:33:52 +0100 +Subject: [PATCH] Add StructureGenerateEvent + +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 . + +Co-authored-by: Mariell Hoversholm + +diff --git a/src/main/java/net/pl3x/purpur/event/world/StructureGenerateEvent.java b/src/main/java/net/pl3x/purpur/event/world/StructureGenerateEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e77f45f761368da9b230c425d975a717cf4d10fd +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/event/world/StructureGenerateEvent.java +@@ -0,0 +1,68 @@ ++package net.pl3x.purpur.event.world; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.StructureType; ++import org.bukkit.World; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.world.WorldEvent; ++import org.jetbrains.annotations.NotNull; ++ ++public class StructureGenerateEvent extends WorldEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ ++ @NotNull ++ private final StructureType structureType; ++ private final int chunkX; ++ private final int chunkZ; ++ ++ private boolean cancel = false; ++ ++ public StructureGenerateEvent(@NotNull World world, ++ @NotNull StructureType structureType, int chunkX, int chunkZ) { ++ super(!Bukkit.isPrimaryThread(), world); // Structure generation is not necessarily on the main thread as of 1.16. ++ this.structureType = structureType; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ } ++ ++ @NotNull ++ @Override ++ public World getWorld() { ++ return super.getWorld(); ++ } ++ ++ @NotNull ++ public StructureType getStructureType() { ++ return structureType; ++ } ++ ++ public int getChunkX() { ++ return chunkX; ++ } ++ ++ public int getChunkZ() { ++ return chunkZ; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return this.cancel; ++ } ++ ++ @NotNull ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/bukkit/event/world/WorldEvent.java b/src/main/java/org/bukkit/event/world/WorldEvent.java +index cffeff33f007d3b03b7c862b25be453f705da739..1fa083d53dce161ef9e9f19407f230c94b2d7d15 100644 +--- a/src/main/java/org/bukkit/event/world/WorldEvent.java ++++ b/src/main/java/org/bukkit/event/world/WorldEvent.java +@@ -10,6 +10,13 @@ import org.jetbrains.annotations.NotNull; + public abstract class WorldEvent extends Event { + private final World world; + ++ // Purpur start ++ public WorldEvent(boolean isAsync, @NotNull final World world) { ++ super(isAsync); ++ this.world = world; ++ } ++ // Purpur end ++ + public WorldEvent(@NotNull final World world) { + this.world = world; + } diff --git a/patches/Purpur/patches/server/0001-Rebrand.patch b/patches/Purpur/patches/server/0001-Rebrand.patch new file mode 100644 index 00000000..3603fd92 --- /dev/null +++ b/patches/Purpur/patches/server/0001-Rebrand.patch @@ -0,0 +1,222 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 4 May 2019 01:02:11 -0500 +Subject: [PATCH] Rebrand + + +diff --git a/pom.xml b/pom.xml +index e83e4241a56fe131a75fe21cc1518992c089da2c..752d62eb3b87ab24260ec2c029bae0d2b0e3b908 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -27,8 +27,10 @@ + + + +- com.tuinity +- tuinity-api ++ ++ net.pl3x.purpur ++ purpur-api ++ + ${project.version} + compile + +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index 74ed02fa9296583977bb721014b10ff8b708b43c..c1280478ee4565003883df9607d4a8a0e8fe4faa 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) { + return super.buildReader(builder +- .appName("Paper") ++ .appName("Purpur") // Purpur + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) + ); +diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java +index 229c3b0f0c650b501f31147adaa17194af57fedd..f88cf526d272fe47b5a474c0b344b748ee4009fa 100644 +--- a/src/main/java/net/minecraft/server/EULA.java ++++ b/src/main/java/net/minecraft/server/EULA.java +@@ -70,7 +70,7 @@ public class EULA { + Properties properties = new Properties(); + + properties.setProperty("eula", "false"); +- properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); // Paper - fix lag; // Tuinity - Tacos are disgusting ++ properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; // Tuinity - Tacos are disgusting // Purpur - no they're not + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 45e310e249a83714d0001d85b2ead8d4f8a2d742..af5c1479d2cb8092d84e2d3d5166060d9ff2df71 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1511,7 +1511,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // Spigot - Spigot > // CraftBukkit - cb > vanilla! ++ return "Purpur"; // Purpur // Tuinity // Paper // Spigot // CraftBukkit + } + + public CrashReport b(CrashReport crashreport) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurVersionFetcher.java b/src/main/java/net/pl3x/purpur/PurpurVersionFetcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d8b408f061d96e2fa8e2e587462e2221aaee80ce +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/PurpurVersionFetcher.java +@@ -0,0 +1,115 @@ ++package net.pl3x.purpur; ++ ++import com.destroystokyo.paper.VersionHistoryManager; ++import com.destroystokyo.paper.util.VersionFetcher; ++import com.google.common.base.Charsets; ++import com.google.common.io.Resources; ++import com.google.gson.Gson; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonSyntaxException; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.io.BufferedReader; ++import java.io.IOException; ++import java.io.InputStreamReader; ++import java.net.HttpURLConnection; ++import java.net.URL; ++ ++public class PurpurVersionFetcher implements VersionFetcher { ++ private static final String JENKINS_URL = "https://ci.pl3x.net/job/Purpur/lastSuccessfulBuild/buildNumber"; ++ private static final String GITHUB_BRANCH_NAME = "master"; ++ ++ @Override ++ public long getCacheTime() { ++ return 720000; ++ } ++ ++ @Nonnull ++ @Override ++ public String getVersionMessage(@Nonnull String serverVersion) { ++ String[] parts = serverVersion.substring("git-Purpur-".length()).split("[-\\s]"); ++ String updateMessage = getUpdateStatusMessage("pl3xgaming/Purpur", GITHUB_BRANCH_NAME, parts[0]); ++ String history = getHistory(); ++ ++ return history != null ? history + "\n" + updateMessage : updateMessage; ++ } ++ ++ private static String getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { ++ int distance; ++ try { ++ int jenkinsBuild = Integer.parseInt(versionInfo); ++ distance = fetchDistanceFromJenkins(jenkinsBuild); ++ } catch (NumberFormatException ignored) { ++ versionInfo = versionInfo.replace("\"", ""); ++ distance = fetchDistanceFromGitHub(repo, branch, versionInfo); ++ } ++ ++ switch (distance) { ++ case -1: ++ return "Error obtaining version information"; ++ case 0: ++ return "You are running the latest version"; ++ case -2: ++ return "Unknown version"; ++ default: ++ return "You are " + distance + " version(s) behind"; ++ } ++ } ++ ++ private static int fetchDistanceFromJenkins(int jenkinsBuild) { ++ try { ++ try (BufferedReader reader = Resources.asCharSource(new URL(JENKINS_URL), Charsets.UTF_8).openBufferedStream()) { ++ return Integer.decode(reader.readLine()) - jenkinsBuild; ++ } catch (NumberFormatException ex) { ++ ex.printStackTrace(); ++ return -2; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++ ++ // Contributed by Techcable in GH-65 ++ private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { ++ try { ++ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection(); ++ connection.connect(); ++ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit ++ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { ++ JsonObject obj = new Gson().fromJson(reader, JsonObject.class); ++ String status = obj.get("status").getAsString(); ++ switch (status) { ++ case "identical": ++ return 0; ++ case "behind": ++ return obj.get("behind_by").getAsInt(); ++ default: ++ return -1; ++ } ++ } catch (JsonSyntaxException | NumberFormatException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++ ++ @Nullable ++ private String getHistory() { ++ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); ++ if (data == null) { ++ return null; ++ } ++ ++ final String oldVersion = data.getOldVersion(); ++ if (oldVersion == null) { ++ return null; ++ } ++ ++ return "Previous version: " + oldVersion; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index bd7bccbea1a0a052ef7bd6ab299ae72336874911..5100460bab83cd75ac8dcdcc50ea663b1c486d00 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -232,7 +232,7 @@ import javax.annotation.Nullable; // Paper + import javax.annotation.Nonnull; // Paper + + public final class CraftServer implements Server { +- private final String serverName = "Tuinity"; // Paper // Tuinity ++ private final String serverName = "Purpur"; // Paper // Tuinity // Purpur + 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 ac5003dc827217bd1947c71044abcbcbd2210dcd..37c561fb775cf7dd955b185b4ea94fecc574be63 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -370,7 +370,7 @@ public final class CraftMagicNumbers implements UnsafeValues { + + @Override + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { +- return new com.destroystokyo.paper.PaperVersionFetcher(); ++ return new net.pl3x.purpur.PurpurVersionFetcher(); + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 001b1e5197eaa51bfff9031aa6c69876c9a47960..13b98439320ac1401a920c01d7cf5a4b3a23deff 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/com.tuinity/tuinity-api/pom.properties"); // Tuinity ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/net.pl3x.purpur/purpur-api/pom.properties"); // Tuinity // Purpur + Properties properties = new Properties(); + + if (stream != null) { diff --git a/patches/Purpur/patches/server/0002-Purpur-config-files.patch b/patches/Purpur/patches/server/0002-Purpur-config-files.patch new file mode 100644 index 00000000..d5809047 --- /dev/null +++ b/patches/Purpur/patches/server/0002-Purpur-config-files.patch @@ -0,0 +1,440 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 9 May 2019 18:09:43 -0500 +Subject: [PATCH] Purpur config files + + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +index 52c0ab1ce46e1f3233ef746d9bc699356fa9fae4..4d8740678049aa749b42618470e9cc838555528d 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("Tuinity", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page ++ Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Purpur + + metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { + String minecraftVersion = Bukkit.getVersion(); +@@ -602,8 +602,8 @@ public class Metrics { + })); + + metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); +- metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() || PaperConfig.isProxyOnlineMode() ? "online" : "offline")); +- metrics.addCustomChart(new Metrics.SimplePie("tuinity_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page ++ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (PaperConfig.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur ++ metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Purpur + + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 673c40d952bae6ae9e92aac9742e58ffb6a8b1bb..ce14283dd1a1fddbea17c2fbaf1c4ef9d7a7479f 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -118,6 +118,11 @@ public class PaperConfig { + } + } + ++ // Purpur start - public save method for config migration ++ saveConfig(); ++ } ++ public static void saveConfig() { ++ // Purpur end + try { + config.save(CONFIG_FILE); + } catch (IOException ex) { +diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java +index ecff0657e5666ddc2e6a5c3111bfb2b8dd2b78d3..3ee8d31c453105eca7b96bede39a9ebbf40e1c2c 100644 +--- a/src/main/java/net/minecraft/server/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/DedicatedServer.java +@@ -167,6 +167,15 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + return false; + } + com.destroystokyo.paper.PaperConfig.registerCommands(); ++ // Purpur start ++ try { ++ net.pl3x.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); ++ } catch (Exception e) { ++ DedicatedServer.LOGGER.error("Unable to load server configuration", e); ++ return false; ++ } ++ net.pl3x.purpur.PurpurConfig.registerCommands(); ++ // Purpur end + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now + // Paper end + com.tuinity.tuinity.config.TuinityConfig.init((java.io.File) options.valueOf("tuinity-settings")); // Tuinity - Server Config +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index de9ea6770b8afc5e1020bef04ac6cca93b6b420c..21d0570a59240e955ff148bac0226b220a7dec36 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -166,7 +166,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + protected void mobTick() { mobTick(false); } + protected void mobTick(boolean inactive) { + this.world.getMethodProfiler().enter("villagerBrain"); +- if (!inactive) this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper ++ if (!inactive) this.getBehaviorController().Wa((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper + this.world.getMethodProfiler().exit(); + if (this.bF) { + this.bF = false; +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 28ee325fcc8b50397768363403823f2e3391d8c8..fb650c09dbcefa0ff021f7c508ff6811a48bee7a 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -95,6 +95,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + + public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config ++ public final net.pl3x.purpur.PurpurWorldConfig purpurConfig; // Purpur + + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPosition lastPhysicsProblem; // Spigot +@@ -154,8 +155,9 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper +- this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config ++ this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((((WorldDataServer)worlddatamutable).getName())); // Purpur ++ this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; + this.world = new CraftWorld((WorldServer) this, gen, env); + this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd3272520eb78a9c663bac3bfdb2b63d611d48a1 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -0,0 +1,130 @@ ++package net.pl3x.purpur; ++ ++import com.google.common.base.Throwables; ++import net.minecraft.server.MinecraftServer; ++import net.pl3x.purpur.command.PurpurCommand; ++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.logging.Level; ++ ++public class PurpurConfig { ++ private static final String HEADER = "This is the main configuration file for Purpur.\n" ++ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" ++ + "with caution, and make sure you know what each option does before configuring.\n" ++ + "\n" ++ + "If you need help with the configuration or have any questions related to Purpur,\n" ++ + "join us in our Discord guild.\n" ++ + "\n" ++ + "Website: https://github.com/pl3xgaming/Purpur \n" ++ + "Wiki: https://github.com/pl3xgaming/Purpur/wiki \n"; ++ private static File CONFIG_FILE; ++ public static YamlConfiguration config; ++ ++ private static Map commands; ++ ++ static int version; ++ static boolean verbose; ++ ++ public static void init(File configFile) { ++ CONFIG_FILE = configFile; ++ config = new YamlConfiguration(); ++ try { ++ config.load(CONFIG_FILE); ++ } catch (IOException ignore) { ++ } catch (InvalidConfigurationException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.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("purpur", new PurpurCommand("purpur")); ++ ++ version = getInt("config-version", 10); ++ set("config-version", 10); ++ ++ readConfig(PurpurConfig.class, null); ++ } ++ ++ protected static void log(String s) { ++ if (verbose) { ++ log(Level.INFO, s); ++ } ++ } ++ ++ protected static void log(Level level, String s) { ++ Bukkit.getLogger().log(level, s); ++ } ++ ++ public static void registerCommands() { ++ for (Map.Entry entry : commands.entrySet()) { ++ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", 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 void set(String path, Object val) { ++ config.addDefault(path, 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 int getInt(String path, int def) { ++ config.addDefault(path, def); ++ return config.getInt(path, config.getInt(path)); ++ } ++ ++ private static List getList(String path, T def) { ++ config.addDefault(path, def); ++ return config.getList(path, config.getList(path)); ++ } ++ ++ private static String getString(String path, String def) { ++ config.addDefault(path, def); ++ return config.getString(path, config.getString(path)); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..361f7857e461578e90cb71e15027dadaf794cb69 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -0,0 +1,59 @@ ++package net.pl3x.purpur; ++ ++import org.bukkit.configuration.ConfigurationSection; ++import java.util.List; ++import static net.pl3x.purpur.PurpurConfig.log; ++ ++public class PurpurWorldConfig { ++ ++ private final String worldName; ++ ++ public PurpurWorldConfig(String worldName) { ++ this.worldName = worldName; ++ init(); ++ } ++ ++ public void init() { ++ log("-------- World Settings For [" + worldName + "] --------"); ++ PurpurConfig.readConfig(PurpurWorldConfig.class, this); ++ } ++ ++ private void set(String path, Object val) { ++ PurpurConfig.config.addDefault("world-settings.default." + path, val); ++ PurpurConfig.config.set("world-settings.default." + path, val); ++ if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) { ++ PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val); ++ PurpurConfig.config.set("world-settings." + worldName + "." + path, val); ++ } ++ } ++ ++ private ConfigurationSection getConfigurationSection(String path) { ++ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); ++ return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); ++ } ++ ++ private boolean getBoolean(String path, boolean def) { ++ PurpurConfig.config.addDefault("world-settings.default." + path, def); ++ return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); ++ } ++ ++ private double getDouble(String path, double def) { ++ PurpurConfig.config.addDefault("world-settings.default." + path, def); ++ return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path)); ++ } ++ ++ private int getInt(String path, int def) { ++ PurpurConfig.config.addDefault("world-settings.default." + path, def); ++ return PurpurConfig.config.getInt("world-settings." + worldName + "." + path, PurpurConfig.config.getInt("world-settings.default." + path)); ++ } ++ ++ private List getList(String path, T def) { ++ PurpurConfig.config.addDefault("world-settings.default." + path, def); ++ return PurpurConfig.config.getList("world-settings." + worldName + "." + path, PurpurConfig.config.getList("world-settings.default." + path)); ++ } ++ ++ private String getString(String path, String def) { ++ PurpurConfig.config.addDefault("world-settings.default." + path, def); ++ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/command/PurpurCommand.java b/src/main/java/net/pl3x/purpur/command/PurpurCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4904be939c7a4b1d1583fd7b6232c930b79caba6 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/command/PurpurCommand.java +@@ -0,0 +1,65 @@ ++package net.pl3x.purpur.command; ++ ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.WorldServer; ++import net.pl3x.purpur.PurpurConfig; ++import org.bukkit.ChatColor; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++import java.io.File; ++import java.util.Collections; ++import java.util.List; ++import java.util.stream.Collectors; ++import java.util.stream.Stream; ++ ++public class PurpurCommand extends Command { ++ public PurpurCommand(String name) { ++ super(name); ++ this.description = "Purpur related commands"; ++ this.usageMessage = "/purpur [reload | version]"; ++ this.setPermission("bukkit.command.purpur"); ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ if (args.length == 1) { ++ return Stream.of("reload", "version") ++ .filter(arg -> arg.startsWith(args[0].toLowerCase())) ++ .collect(Collectors.toList()); ++ } ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ if (args.length != 1) { ++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ return false; ++ } ++ ++ if (args[0].equalsIgnoreCase("reload")) { ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); ++ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); ++ ++ MinecraftServer console = MinecraftServer.getServer(); ++ PurpurConfig.init((File) console.options.valueOf("purpur-settings")); ++ for (WorldServer world : console.getWorlds()) { ++ world.purpurConfig.init(); ++ } ++ console.server.reloadCount++; ++ ++ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete."); ++ } else if (args[0].equalsIgnoreCase("version")) { ++ Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); ++ if (verCmd != null) { ++ return verCmd.execute(sender, commandLabel, new String[0]); ++ } ++ } ++ ++ return true; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 5100460bab83cd75ac8dcdcc50ea663b1c486d00..b5d274c1fe214ea274057084bc40d6eeb618b21d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -862,6 +862,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 + com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config ++ net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur + for (WorldServer world : console.getWorlds()) { + world.worldDataServer.setDifficulty(config.difficulty); + world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); +@@ -897,6 +898,7 @@ public final class CraftServer implements Server { + world.spigotConfig.init(); // Spigot + world.paperConfig.init(); // Paper + world.tuinityConfig.init(); // Tuinity - Server Config ++ world.purpurConfig.init(); // Purpur + } + + Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper +@@ -915,6 +917,7 @@ public final class CraftServer implements Server { + reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot + com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper ++ net.pl3x.purpur.PurpurConfig.registerCommands(); // Purpur + overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*"); + ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions"); + +@@ -2298,6 +2301,18 @@ public final class CraftServer implements Server { + } + // Tuinity end - add config to timings report + ++ // Purpur start ++ @Override ++ public YamlConfiguration getPurpurConfig() { ++ return net.pl3x.purpur.PurpurConfig.config; ++ } ++ ++ @Override ++ public java.util.Properties getServerProperties() { ++ return getProperties().properties; ++ } ++ // Purpur end ++ + @Override + public void restart() { + org.spigotmc.RestartCommand.restart(); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 0f6cb508a170360b6479f9c34048412453fbb89d..a92721dff5c2a9a2a167b36c23d1ef22d2bbd3e1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -146,6 +146,14 @@ public class Main { + .describedAs("Yml file"); + /* Conctete End - Server Config */ + ++ // Purpur Start ++ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") ++ .withRequiredArg() ++ .ofType(File.class) ++ .defaultsTo(new File("purpur.yml")) ++ .describedAs("Yml file"); ++ // Purpur end ++ + // Paper start + acceptsAll(asList("server-name"), "Name of the server") + .withRequiredArg() diff --git a/patches/Purpur/patches/server/0003-Timings-stuff.patch b/patches/Purpur/patches/server/0003-Timings-stuff.patch new file mode 100644 index 00000000..151e6d7d --- /dev/null +++ b/patches/Purpur/patches/server/0003-Timings-stuff.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 5 Jun 2020 21:30:19 -0500 +Subject: [PATCH] Timings stuff + + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 5dfa0658838c4801cdf260eae8b98163f729e5af..dae2e5d70756c5b61163d57099b65f7e415b288c 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -227,10 +227,14 @@ public class TimingsExport extends Thread { + // Information on the users Config + + parent.put("config", createObject( +- pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), +- pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), +- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report +- pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report ++ // Purpur start ++ pair("server.properties", mapAsJSON(Bukkit.spigot().getServerProperties())), ++ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), ++ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), ++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), ++ pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)), // Tuinity - add config to timings report ++ pair("purpur", mapAsJSON(Bukkit.spigot().getPurpurConfig(), null)) ++ // Purpur end + )); + + new TimingsExport(listeners, parent, history).start(); +@@ -271,6 +275,19 @@ public class TimingsExport extends Thread { + return timingsCost; + } + ++ // Purpur start ++ private static JSONObject mapAsJSON(java.util.Properties properties) { ++ JSONObject object = new JSONObject(); ++ for (String key : properties.stringPropertyNames()) { ++ if (key.startsWith("rcon") || key.startsWith("query") || key.equals("level-seed") || TimingsManager.hiddenConfigs.contains(key)) { ++ continue; ++ } ++ object.put(key, valAsJSON(properties.get(key), key)); ++ } ++ return object; ++ } ++ // Purpur end ++ + private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) { + + JSONObject object = new JSONObject(); +@@ -307,7 +324,7 @@ public class TimingsExport extends Thread { + String response = null; + String timingsURL = null; + try { +- HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection(); ++ HttpURLConnection con = (HttpURLConnection) new URL(net.pl3x.purpur.PurpurConfig.timingsUrl + "/post").openConnection(); // Purpur + con.setDoOutput(true); + String hostName = "BrokenHost"; + try { +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index cd3272520eb78a9c663bac3bfdb2b63d611d48a1..00eb196f8caa2e4f2478972c14f4596071adbd2a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -127,4 +127,10 @@ public class PurpurConfig { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } ++ ++ public static String timingsUrl = "https://timings.pl3x.net"; ++ private static void timingsSettings() { ++ timingsUrl = getString("settings.timings.url", timingsUrl); ++ if (!TimingsManager.hiddenConfigs.contains("server-ip")) TimingsManager.hiddenConfigs.add("server-ip"); ++ } + } diff --git a/patches/Purpur/patches/server/0004-Add-component-util.patch b/patches/Purpur/patches/server/0004-Add-component-util.patch new file mode 100644 index 00000000..f63b095a --- /dev/null +++ b/patches/Purpur/patches/server/0004-Add-component-util.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 15 Aug 2020 03:49:33 -0500 +Subject: [PATCH] Add component util + + +diff --git a/src/main/java/net/pl3x/purpur/ComponentUtil.java b/src/main/java/net/pl3x/purpur/ComponentUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3f7bc68d1a6fb00758b178bb46113e38b8bc24bc +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/ComponentUtil.java +@@ -0,0 +1,32 @@ ++package net.pl3x.purpur; ++ ++import net.md_5.bungee.api.chat.BaseComponent; ++import net.md_5.bungee.api.chat.TextComponent; ++import net.md_5.bungee.chat.ComponentSerializer; ++import net.minecraft.server.IChatBaseComponent; ++import net.minecraft.server.MinecraftServer; ++ ++import java.util.List; ++ ++public class ComponentUtil { ++ public static String fromComponent(IChatBaseComponent component) { ++ String json = ""; ++ try { ++ int chop; ++ List siblings = component.getSiblings(); ++ if (siblings.size() > 0) chop = siblings.get(0).getChatModifier().getColor() == null ? 4 : 2; ++ else chop = component.getChatModifier().getColor() == null ? 2 : 0; ++ json = IChatBaseComponent.ChatSerializer.componentToJson(component); ++ BaseComponent[] parsed = ComponentSerializer.parse(json); ++ return TextComponent.toLegacyText(parsed).substring(chop); ++ } catch (Exception e) { ++ MinecraftServer.LOGGER.warn("There was a problem processing a chat component!"); ++ MinecraftServer.LOGGER.warn("We have fallen back to legacy colorless string to prevent real errors"); ++ MinecraftServer.LOGGER.warn("Please report this to Purpur!"); ++ MinecraftServer.LOGGER.warn("JSON: " + json); ++ MinecraftServer.LOGGER.warn("The following error describes what went wrong:"); ++ e.printStackTrace(); ++ return component.getString(); ++ } ++ } ++} diff --git a/patches/Purpur/patches/server/0005-Barrels-and-enderchests-6-rows.patch b/patches/Purpur/patches/server/0005-Barrels-and-enderchests-6-rows.patch new file mode 100644 index 00000000..c8bd7f2b --- /dev/null +++ b/patches/Purpur/patches/server/0005-Barrels-and-enderchests-6-rows.patch @@ -0,0 +1,173 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 23 May 2019 21:50:37 -0500 +Subject: [PATCH] Barrels and enderchests 6 rows + + +diff --git a/src/main/java/net/minecraft/server/BlockEnderChest.java b/src/main/java/net/minecraft/server/BlockEnderChest.java +index 896d99d404419fef5bdf6f9083e07dfc978f4e67..9ab8336df4f1702e9cabefb63f279034fdd57486 100644 +--- a/src/main/java/net/minecraft/server/BlockEnderChest.java ++++ b/src/main/java/net/minecraft/server/BlockEnderChest.java +@@ -48,6 +48,27 @@ public class BlockEnderChest extends BlockChestAbstract im + + inventoryenderchest.a(tileentityenderchest); + entityhuman.openContainer(new TileInventory((i, playerinventory, entityhuman1) -> { ++ // Purpur start ++ if (net.pl3x.purpur.PurpurConfig.enderChestSixRows) { ++ if (net.pl3x.purpur.PurpurConfig.enderChestPermissionRows) { ++ org.bukkit.craftbukkit.entity.CraftHumanEntity player = entityhuman.getBukkitEntity(); ++ if (player.hasPermission("purpur.enderchest.rows.six")) { ++ return new ContainerChest(Containers.GENERIC_9X6, i, playerinventory, inventoryenderchest, 6); ++ } else if (player.hasPermission("purpur.enderchest.rows.five")) { ++ return new ContainerChest(Containers.GENERIC_9X5, i, playerinventory, inventoryenderchest, 5); ++ } else if (player.hasPermission("purpur.enderchest.rows.four")) { ++ return new ContainerChest(Containers.GENERIC_9X4, i, playerinventory, inventoryenderchest, 4); ++ } else if (player.hasPermission("purpur.enderchest.rows.three")) { ++ return new ContainerChest(Containers.GENERIC_9X3, i, playerinventory, inventoryenderchest, 3); ++ } else if (player.hasPermission("purpur.enderchest.rows.two")) { ++ return new ContainerChest(Containers.GENERIC_9X2, i, playerinventory, inventoryenderchest, 2); ++ } else if (player.hasPermission("purpur.enderchest.rows.one")) { ++ return new ContainerChest(Containers.GENERIC_9X1, i, playerinventory, inventoryenderchest, 1); ++ } ++ } ++ return new ContainerChest(Containers.GENERIC_9X6, i, playerinventory, inventoryenderchest, 6); ++ } ++ // Purpur end + return ContainerChest.a(i, playerinventory, inventoryenderchest); + }, BlockEnderChest.e)); + entityhuman.a(StatisticList.OPEN_ENDERCHEST); +diff --git a/src/main/java/net/minecraft/server/InventoryEnderChest.java b/src/main/java/net/minecraft/server/InventoryEnderChest.java +index fa56d6e3a9f0bfad3961697a3bae98205f32ae9c..3fc6298a5ef81c4203a79f1d9e87b0a9913255b6 100644 +--- a/src/main/java/net/minecraft/server/InventoryEnderChest.java ++++ b/src/main/java/net/minecraft/server/InventoryEnderChest.java +@@ -19,11 +19,34 @@ public class InventoryEnderChest extends InventorySubcontainer { + } + + public InventoryEnderChest(EntityHuman owner) { +- super(27); ++ super(net.pl3x.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur + this.owner = owner; + // CraftBukkit end + } + ++ // Purpur start ++ @Override ++ public int getSize() { ++ if (net.pl3x.purpur.PurpurConfig.enderChestSixRows && net.pl3x.purpur.PurpurConfig.enderChestPermissionRows && owner != null && owner.getProfile() != null) { ++ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = owner.getBukkitEntity(); ++ if (bukkit.hasPermission("purpur.enderchest.rows.six")) { ++ return 54; ++ } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) { ++ return 45; ++ } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) { ++ return 36; ++ } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) { ++ return 27; ++ } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) { ++ return 18; ++ } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) { ++ return 9; ++ } ++ } ++ return super.getSize(); ++ } ++ // Purpur end ++ + public void a(TileEntityEnderChest tileentityenderchest) { + this.a = tileentityenderchest; + } +diff --git a/src/main/java/net/minecraft/server/TileEntityBarrel.java b/src/main/java/net/minecraft/server/TileEntityBarrel.java +index a1c3942cbf9a6c0adc4943b05a1c3859c5f0aed6..953741c1cfd9b5c1e0eac80d1e4c7890f94e3bb1 100644 +--- a/src/main/java/net/minecraft/server/TileEntityBarrel.java ++++ b/src/main/java/net/minecraft/server/TileEntityBarrel.java +@@ -55,7 +55,7 @@ public class TileEntityBarrel extends TileEntityLootable { + + private TileEntityBarrel(TileEntityTypes tileentitytypes) { + super(tileentitytypes); +- this.items = NonNullList.a(27, ItemStack.b); ++ this.items = NonNullList.a(net.pl3x.purpur.PurpurConfig.barrelSixRows ? 54 : 27, ItemStack.b); // Purpur + } + + public TileEntityBarrel() { +@@ -84,7 +84,7 @@ public class TileEntityBarrel extends TileEntityLootable { + + @Override + public int getSize() { +- return 27; ++ return net.pl3x.purpur.PurpurConfig.barrelSixRows ? 54 : 27; // Purpur + } + + @Override +@@ -104,6 +104,7 @@ public class TileEntityBarrel extends TileEntityLootable { + + @Override + protected Container createContainer(int i, PlayerInventory playerinventory) { ++ if (net.pl3x.purpur.PurpurConfig.barrelSixRows) return new ContainerChest(Containers.GENERIC_9X6, i, playerinventory, this, 6); // Purpur + return ContainerChest.a(i, playerinventory, this); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 00eb196f8caa2e4f2478972c14f4596071adbd2a..cb7e34924cb5dbff25d1ffe05cfe5bc22e4a90ed 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -7,6 +7,7 @@ import org.bukkit.Bukkit; + import org.bukkit.command.Command; + import org.bukkit.configuration.InvalidConfigurationException; + import org.bukkit.configuration.file.YamlConfiguration; ++import org.bukkit.event.inventory.InventoryType; + + import java.io.File; + import java.io.IOException; +@@ -133,4 +134,23 @@ public class PurpurConfig { + timingsUrl = getString("settings.timings.url", timingsUrl); + if (!TimingsManager.hiddenConfigs.contains("server-ip")) TimingsManager.hiddenConfigs.add("server-ip"); + } ++ ++ public static boolean barrelSixRows = false; ++ public static boolean enderChestSixRows = false; ++ public static boolean enderChestPermissionRows = false; ++ private static void blockSettings() { ++ if (version < 3) { ++ boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); ++ set("settings.blocks.barrel.six-rows", oldValue); ++ set("settings.packed-barrels", null); ++ oldValue = getBoolean("settings.large-ender-chests", true); ++ set("settings.blocks.ender_chest.six-rows", oldValue); ++ set("settings.large-ender-chests", null); ++ } ++ barrelSixRows = getBoolean("settings.blocks.barrel.six-rows", barrelSixRows); ++ InventoryType.BARREL.setDefaultSize(barrelSixRows ? 54 : 27); ++ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); ++ InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); ++ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +index 8c714c7430c0a6b8fd7f4a158d9a271e1642bd7a..cae362bae9e1e253c34bc81813d251fece839de3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +@@ -198,8 +198,10 @@ public class CraftContainer extends Container { + case PLAYER: + case CHEST: + case ENDER_CHEST: ++ delegate = new ContainerChest(net.pl3x.purpur.PurpurConfig.enderChestSixRows ? Containers.GENERIC_9X6 : Containers.GENERIC_9X3, windowId, bottom, top, top.getSize() / 9); // Purpur ++ break; // Purpur + case BARREL: +- delegate = new ContainerChest(Containers.GENERIC_9X3, windowId, bottom, top, top.getSize() / 9); ++ delegate = new ContainerChest(net.pl3x.purpur.PurpurConfig.barrelSixRows ? Containers.GENERIC_9X6 : Containers.GENERIC_9X3, windowId, bottom, top, top.getSize() / 9); // Purpur + break; + case DISPENSER: + case DROPPER: +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index bba9bddc1c0aacade9b7ad56afb1e630caa078fc..c2802c5bfb5ec82daad32d3a3375f4428ae76dfd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -81,7 +81,7 @@ public class CraftInventory implements Inventory { + + @Override + public void setContents(ItemStack[] items) { +- if (getSize() < items.length) { ++ if (false && getSize() < items.length) { // Purpur + throw new IllegalArgumentException("Invalid inventory size; expected " + getSize() + " or less"); + } + diff --git a/patches/Purpur/patches/server/0006-Advancement-API.patch b/patches/Purpur/patches/server/0006-Advancement-API.patch new file mode 100644 index 00000000..1ce8a56b --- /dev/null +++ b/patches/Purpur/patches/server/0006-Advancement-API.patch @@ -0,0 +1,181 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 31 May 2019 21:24:33 -0500 +Subject: [PATCH] Advancement API + + +diff --git a/src/main/java/net/minecraft/server/Advancement.java b/src/main/java/net/minecraft/server/Advancement.java +index c405047c00d354bbc1449fd2f917b73f980ef1a5..384d4090f8ff1ea718de16affa5c146a2f58d28a 100644 +--- a/src/main/java/net/minecraft/server/Advancement.java ++++ b/src/main/java/net/minecraft/server/Advancement.java +@@ -64,7 +64,7 @@ public class Advancement { + } + + @Nullable +- public AdvancementDisplay c() { ++ public AdvancementDisplay c() { return getDisplay(); } public AdvancementDisplay getDisplay() { // Purpur + return this.display; + } + +diff --git a/src/main/java/net/minecraft/server/AdvancementDisplay.java b/src/main/java/net/minecraft/server/AdvancementDisplay.java +index b0d4b7a67679a35fa8f88c241193c0f3814f1e7b..ac4fac89837f4e77dcaec6f9ca90c5aa8a78c4be 100644 +--- a/src/main/java/net/minecraft/server/AdvancementDisplay.java ++++ b/src/main/java/net/minecraft/server/AdvancementDisplay.java +@@ -15,10 +15,11 @@ public class AdvancementDisplay { + private final MinecraftKey d; + private final AdvancementFrameType e; + private final boolean f; +- private final boolean g; +- private final boolean h; ++ private boolean g; // Purpur - un-finalize ++ private boolean h; // Purpur - un-finalize + private float i; + private float j; ++ public final org.bukkit.advancement.AdvancementDisplay bukkit = new org.bukkit.craftbukkit.advancement.CraftAdvancementDisplay(this); // Purpur + + public AdvancementDisplay(ItemStack itemstack, IChatBaseComponent ichatbasecomponent, IChatBaseComponent ichatbasecomponent1, @Nullable MinecraftKey minecraftkey, AdvancementFrameType advancementframetype, boolean flag, boolean flag1, boolean flag2) { + this.a = ichatbasecomponent; +@@ -36,22 +37,29 @@ public class AdvancementDisplay { + this.j = f1; + } + ++ public IChatBaseComponent getTitle() { return a(); } // Purpur - OBFHELPER + public IChatBaseComponent a() { + return this.a; + } + ++ public IChatBaseComponent getDescription() { return b(); } // Purpur - OBFHELPER + public IChatBaseComponent b() { + return this.b; + } + ++ public AdvancementFrameType getFrameType() { return e(); } // Purpur - OBFHELPER + public AdvancementFrameType e() { + return this.e; + } + ++ public void setShouldAnnounceToChat(boolean announce) { this.g = announce; } // Purpur - OBFHELPER ++ public boolean shouldAnnounceToChat() { return i(); } // Purpur - OBFHELPER + public boolean i() { + return this.g; + } + ++ public void setHidden(boolean hidden) { this.h = hidden; } // Purpur - OBFHELPER ++ public boolean isHidden() { return j(); } // Purpur - OBFHELPER + public boolean j() { + return this.h; + } +diff --git a/src/main/java/net/minecraft/server/AdvancementFrameType.java b/src/main/java/net/minecraft/server/AdvancementFrameType.java +index 90b78e49c0688dc2fb02df0b6784cd82fad4bc07..9a3a53cf3576c299629a84ba76cb5b9b86a14491 100644 +--- a/src/main/java/net/minecraft/server/AdvancementFrameType.java ++++ b/src/main/java/net/minecraft/server/AdvancementFrameType.java +@@ -1,15 +1,26 @@ + package net.minecraft.server; + ++import org.bukkit.advancement.FrameType; // Purpur ++ + public enum AdvancementFrameType { + +- TASK("task", 0, EnumChatFormat.GREEN), CHALLENGE("challenge", 26, EnumChatFormat.DARK_PURPLE), GOAL("goal", 52, EnumChatFormat.GREEN); ++ // Purpur start ++ TASK("task", 0, EnumChatFormat.GREEN, FrameType.TASK), ++ CHALLENGE("challenge", 26, EnumChatFormat.DARK_PURPLE, FrameType.CHALLENGE), ++ GOAL("goal", 52, EnumChatFormat.GREEN, FrameType.GOAL); ++ // Purpur end + + private final String d; + private final int e; + private final EnumChatFormat f; + private final IChatBaseComponent g; + +- private AdvancementFrameType(String s, int i, EnumChatFormat enumchatformat) { ++ // Purpur start ++ public final FrameType bukkit; ++ ++ AdvancementFrameType(String s, int i, EnumChatFormat enumchatformat, FrameType bukkit) { ++ this.bukkit = bukkit; ++ // Purpur end + this.d = s; + this.e = i; + this.f = enumchatformat; +diff --git a/src/main/java/net/minecraft/server/CriterionTrigger.java b/src/main/java/net/minecraft/server/CriterionTrigger.java +index cfb420a9c7e64ec240fff81d2e3fd32f607847b3..6fd3671c37a4fc42aa438a93d5a749b52f618b1e 100644 +--- a/src/main/java/net/minecraft/server/CriterionTrigger.java ++++ b/src/main/java/net/minecraft/server/CriterionTrigger.java +@@ -26,6 +26,7 @@ public interface CriterionTrigger { + this.c = s; + } + ++ public T getInstance() { return a(); } // Purpur - OBFHELPER + public T a() { + return this.a; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java +index a5aadf2850f273e258f84b6c7bc9ca3649fb884d..b0a7092d623adccd61fd3e094f1ec5e8d95c3691 100644 +--- a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java ++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java +@@ -27,4 +27,11 @@ public class CraftAdvancement implements org.bukkit.advancement.Advancement { + public Collection getCriteria() { + return Collections.unmodifiableCollection(handle.getCriteria().keySet()); + } ++ ++ // Purpur start ++ @Override ++ public org.bukkit.advancement.AdvancementDisplay getDisplay() { ++ return getHandle().getDisplay() == null ? null : getHandle().getDisplay().bukkit; ++ } ++ // Purpur end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1cbb1e67b64a7e830cfabcd1fc07e998434476c3 +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java +@@ -0,0 +1,47 @@ ++package org.bukkit.craftbukkit.advancement; ++ ++import net.minecraft.server.AdvancementDisplay; ++import org.bukkit.advancement.FrameType; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++ ++public class CraftAdvancementDisplay implements org.bukkit.advancement.AdvancementDisplay { ++ private final AdvancementDisplay handle; ++ ++ public CraftAdvancementDisplay(AdvancementDisplay handle) { ++ this.handle = handle; ++ } ++ ++ public AdvancementDisplay getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public String getTitle() { ++ return CraftChatMessage.fromComponent(handle.getTitle()); ++ } ++ ++ @Override ++ public String getDescription() { ++ return CraftChatMessage.fromComponent(handle.getDescription()); ++ } ++ ++ @Override ++ public FrameType getFrameType() { ++ return handle.getFrameType().bukkit; ++ } ++ ++ @Override ++ public boolean shouldAnnounceToChat() { ++ return handle.shouldAnnounceToChat(); ++ } ++ ++ @Override ++ public void setShouldAnnounceToChat(boolean announce) { ++ handle.setShouldAnnounceToChat(announce); ++ } ++ ++ @Override ++ public boolean isHidden() { ++ return handle.isHidden(); ++ } ++} diff --git a/patches/Purpur/patches/server/0007-Llama-API.patch b/patches/Purpur/patches/server/0007-Llama-API.patch new file mode 100644 index 00000000..4986a46a --- /dev/null +++ b/patches/Purpur/patches/server/0007-Llama-API.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 18 Oct 2019 22:50:12 -0500 +Subject: [PATCH] Llama API + + +diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java +index d9e1b43283bee15c659dd3a99e45d9412aedd0bc..e61f53816cbf09e775762403d97e9c591fb405a6 100644 +--- a/src/main/java/net/minecraft/server/EntityLlama.java ++++ b/src/main/java/net/minecraft/server/EntityLlama.java +@@ -13,7 +13,8 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + @Nullable + private EntityLlama bB; + @Nullable +- private EntityLlama bC; ++ private EntityLlama bC; public EntityLlama getCaravanTail() { return bC; } // Purpur - OBFHELPER ++ public boolean shouldJoinCaravan = true; // Purpur + + public EntityLlama(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -42,6 +43,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + nbttagcompound.set("DecorItem", this.inventoryChest.getItem(1).save(new NBTTagCompound())); + } + ++ nbttagcompound.setBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); // Purpur + } + + @Override +@@ -53,6 +55,11 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + this.inventoryChest.setItem(1, ItemStack.a(nbttagcompound.getCompound("DecorItem"))); + } + ++ // Purpur start ++ if (nbttagcompound.hasKey("Purpur.ShouldJoinCaravan")) { ++ nbttagcompound.setBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); ++ } ++ // Purpur end + this.fe(); + } + +@@ -387,19 +394,24 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + } + } + ++ public void leaveCaravan() { fA(); } // Purpur - OBFHELPER + public void fA() { + if (this.bB != null) { ++ new net.pl3x.purpur.event.entity.LlamaLeaveCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity()).callEvent(); // Purpur + this.bB.bC = null; + } + + this.bB = null; + } + ++ public void joinCaravan(EntityLlama entitiyllama) { a(entitiyllama); } // Purpur - OBFHELPER + public void a(EntityLlama entityllama) { ++ if (!shouldJoinCaravan || !new net.pl3x.purpur.event.entity.LlamaJoinCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity(), (org.bukkit.entity.Llama) entityllama.getBukkitEntity()).callEvent()) return; // Purpur + this.bB = entityllama; + this.bB.bC = this; + } + ++ public boolean hasCaravanTail() { return fB(); } // Purpur - OBFHELPER + public boolean fB() { + return this.bC != null; + } +@@ -410,7 +422,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + } + + @Nullable +- public EntityLlama fD() { ++ public EntityLlama fD() { return getCaravanHead(); } public EntityLlama getCaravanHead() { // Purpur - OBFHELPER + return this.bB; + } + +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java b/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java +index 1b29ca2ca0bc5d17673de43bdc854d5b4c96b8b6..47ffa669681da7512ee594ecb643f28576dee444 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java +@@ -6,7 +6,7 @@ import java.util.List; + + public class PathfinderGoalLlamaFollow extends PathfinderGoal { + +- public final EntityLlama a; ++ public final EntityLlama a; public EntityLlama getLlama() { return a; } // Purpur + private double b; + private int c; + +@@ -18,6 +18,7 @@ public class PathfinderGoalLlamaFollow extends PathfinderGoal { + + @Override + public boolean a() { ++ if (!getLlama().shouldJoinCaravan) return false; // Purpur + if (!this.a.isLeashed() && !this.a.fC()) { + List list = this.a.world.getEntities(this.a, this.a.getBoundingBox().grow(9.0D, 4.0D, 9.0D), (entity) -> { + EntityTypes entitytypes = entity.getEntityType(); +@@ -77,6 +78,7 @@ public class PathfinderGoalLlamaFollow extends PathfinderGoal { + + @Override + public boolean b() { ++ if (!getLlama().shouldJoinCaravan) return false; // Purpur + if (this.a.fC() && this.a.fD().isAlive() && this.a(this.a, 0)) { + double d0 = this.a.h((Entity) this.a.fD()); + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index 3f94c5a9206e2da9c852d282e267ab4d9f7324c4..a02763480149dc7fb0f07f17ef8530a2e76d99bc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -65,4 +65,48 @@ public class CraftLlama extends CraftChestedHorse implements Llama, CraftRangedE + public EntityType getType() { + return EntityType.LLAMA; + } ++ ++ // Purpur start ++ @Override ++ public boolean shouldJoinCaravan() { ++ return getHandle().shouldJoinCaravan; ++ } ++ ++ @Override ++ public void setShouldJoinCaravan(boolean shouldJoinCaravan) { ++ getHandle().shouldJoinCaravan = shouldJoinCaravan; ++ } ++ ++ @Override ++ public boolean inCaravan() { ++ return getHandle().inCaravan(); ++ } ++ ++ @Override ++ public void joinCaravan(Llama llama) { ++ if (llama != null) { ++ getHandle().joinCaravan(((CraftLlama) llama).getHandle()); ++ } ++ } ++ ++ @Override ++ public void leaveCaravan() { ++ getHandle().leaveCaravan(); ++ } ++ ++ @Override ++ public boolean hasCaravanTail() { ++ return getHandle().hasCaravanTail(); ++ } ++ ++ @Override ++ public Llama getCaravanHead() { ++ return getHandle().getCaravanHead() == null ? null : (Llama) getHandle().getCaravanHead().getBukkitEntity(); ++ } ++ ++ @Override ++ public Llama getCaravanTail() { ++ return getHandle().getCaravanTail() == null ? null : (Llama) getHandle().getCaravanTail().getBukkitEntity(); ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0008-AFK-API.patch b/patches/Purpur/patches/server/0008-AFK-API.patch new file mode 100644 index 00000000..86e3c73d --- /dev/null +++ b/patches/Purpur/patches/server/0008-AFK-API.patch @@ -0,0 +1,302 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 8 Aug 2019 15:29:15 -0500 +Subject: [PATCH] AFK API + + +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index 9796b4e57d6680c9f0dc76decdd985572daafb7e..f15ec5c45d95c6828ed628451917ac3426a76f1f 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -84,6 +84,15 @@ public abstract class EntityHuman extends EntityLiving { + } + // CraftBukkit end + ++ // Purpur start ++ public void setAfk(boolean setAfk){ ++ } ++ ++ public boolean isAfk() { ++ return false; ++ } ++ // Purpur end ++ + public EntityHuman(World world, BlockPosition blockposition, float f, GameProfile gameprofile) { + super(EntityTypes.PLAYER, world); + this.bL = ItemStack.b; +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 9f5b7243ccbe0729a061345c25033d9145b91b3f..6bab47ab2583735c36d74d849ab0923494a265db 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1904,8 +1904,54 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + public void resetIdleTimer() { + this.ca = SystemUtils.getMonotonicMillis(); ++ setAfk(false); // Purpur + } + ++ // Purpur start ++ private boolean isAfk = false; ++ ++ @Override ++ public void setAfk(boolean setAfk) { ++ if (this.isAfk == setAfk) { ++ return; ++ } ++ ++ String msg = setAfk ? net.pl3x.purpur.PurpurConfig.afkBroadcastAway : net.pl3x.purpur.PurpurConfig.afkBroadcastBack; ++ ++ net.pl3x.purpur.event.PlayerAFKEvent event = new net.pl3x.purpur.event.PlayerAFKEvent(getBukkitEntity(), setAfk, world.purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread()); ++ if (!event.callEvent() || event.shouldKick()) { ++ return; ++ } ++ ++ this.isAfk = setAfk; ++ ++ if (!setAfk) { ++ resetIdleTimer(); ++ } ++ ++ msg = event.getBroadcastMsg(); ++ if (msg != null && !msg.isEmpty()) { ++ server.getPlayerList().sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(String.format(msg, getProfile().getName()))); ++ } ++ ++ if (world.purpurConfig.idleTimeoutUpdateTabList) { ++ getBukkitEntity().setPlayerListName((setAfk ? net.pl3x.purpur.PurpurConfig.afkTabListPrefix : "") + getName()); ++ } ++ ++ ((WorldServer) world).everyoneSleeping(); ++ } ++ ++ @Override ++ public boolean isAfk() { ++ return isAfk; ++ } ++ ++ @Override ++ public boolean isCollidable() { ++ return !isAfk() && super.isCollidable(); ++ } ++ // Purpur end ++ + public ServerStatisticManager getStatisticManager() { + return this.serverStatisticManager; + } +diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java +index cbaf18af1066e8bde10293bba5eb3060bae1e66f..0c98a436021cbdedba5352073b1f8bf9852298eb 100644 +--- a/src/main/java/net/minecraft/server/IEntityAccess.java ++++ b/src/main/java/net/minecraft/server/IEntityAccess.java +@@ -174,28 +174,18 @@ public interface IEntityAccess { + } + // Paper end + +- default boolean isPlayerNearby(double d0, double d1, double d2, double d3) { +- Iterator iterator = this.getPlayers().iterator(); +- +- double d4; +- +- do { +- EntityHuman entityhuman; +- +- do { +- do { +- if (!iterator.hasNext()) { +- return false; +- } +- +- entityhuman = (EntityHuman) iterator.next(); +- } while (!IEntitySelector.g.test(entityhuman)); +- } while (!IEntitySelector.b.test(entityhuman)); +- +- d4 = entityhuman.h(d0, d1, d2); +- } while (d3 >= 0.0D && d4 >= d3 * d3); +- +- return true; ++ // Purpur start ++ default boolean isPlayerNearby(double x, double y, double z, double distance) { ++ double distanceSq = distance * distance; ++ for (EntityHuman player : getPlayers()) { ++ if (IEntitySelector.notSpectator().test(player) && IEntitySelector.isLivingAlive().test(player) && IEntitySelector.notAfk.test(player)) { ++ if (distance < 0.0D || player.getDistanceSquared(x, y, z) < distanceSq) { ++ return true; ++ } ++ } ++ } ++ return false; ++ // Purpur end + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/IEntitySelector.java b/src/main/java/net/minecraft/server/IEntitySelector.java +index b5e1a860a2569d7668330827614d221b60f3fc78..5f85a1d513f4fdc21b64e1a2b6882e3325b98ddd 100644 +--- a/src/main/java/net/minecraft/server/IEntitySelector.java ++++ b/src/main/java/net/minecraft/server/IEntitySelector.java +@@ -7,6 +7,7 @@ import javax.annotation.Nullable; + public final class IEntitySelector { + + public static final Predicate a = Entity::isAlive; ++ public static Predicate isLivingAlive() { return b; } // Purpur - OBFHELPER + public static final Predicate b = EntityLiving::isAlive; + public static final Predicate c = (entity) -> { + return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger(); +@@ -27,6 +28,7 @@ public final class IEntitySelector { + return !entity.isSpectator(); + }; + public static Predicate isInsomniac = (player) -> MathHelper.clamp(((EntityPlayer) player).getStatisticManager().getStatisticValue(StatisticList.CUSTOM.get(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper ++ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur + + // Paper start + public static final Predicate affectsSpawning = (entity) -> { +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 4058c1f7ada7d0c9e4ba73a0073b4f94bf410a8f..caf9ce94a7cb6154981d42953c81b588b19e3814 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -250,6 +250,12 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + if (this.player.F() > 0L && this.minecraftServer.getIdleTimeout() > 0 && SystemUtils.getMonotonicMillis() - this.player.F() > (long) (this.minecraftServer.getIdleTimeout() * 1000 * 60)) { ++ // Purpur start ++ this.player.setAfk(true); ++ if (!this.player.world.purpurConfig.idleTimeoutKick) { ++ return; ++ } ++ // Purpur end + this.player.resetIdleTimer(); // CraftBukkit - SPIGOT-854 + this.disconnect(new ChatMessage("multiplayer.disconnect.idling")); + } +@@ -517,6 +523,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.lastYaw = to.getYaw(); + this.lastPitch = to.getPitch(); + ++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetIdleTimer(); // Purpur ++ + // Skip the first time we do this + if (true) { // Spigot - don't skip any move events + Location oldTo = to.clone(); +@@ -1228,7 +1236,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + + if (!this.player.H() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.playerInteractManager.isCreative() && this.player.playerInteractManager.getGameMode() != EnumGamemode.SPECTATOR) { // Spigot + flag1 = true; // Tuinity - diff on change, this should be moved wrongly +- PlayerConnection.LOGGER.warn("{} moved wrongly!", this.player.getDisplayName().getString()); ++ PlayerConnection.LOGGER.warn("{} moved wrongly! ({})", this.player.getDisplayName().getString(), d11); // Purpur + } + + this.player.setLocation(d4, d5, d6, f, f1); +@@ -1278,6 +1286,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.lastYaw = to.getYaw(); + this.lastPitch = to.getPitch(); + ++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetIdleTimer(); // Purpur ++ + // Skip the first time we do this + if (from.getX() != Double.MAX_VALUE) { + Location oldTo = to.clone(); +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 6ff5ef6b710652f1c4fe6461ff230ee78988f623..efe2a9ad2ece23bd71f4ad63b2c6f54f42345b55 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -781,7 +781,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit end + + if (this.everyoneSleeping && this.players.stream().noneMatch((entityplayer) -> { +- return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping; // CraftBukkit ++ return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping && !(purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk()); // CraftBukkit // Purpur + })) { + // CraftBukkit start + long l = this.worldData.getDayTime() + 24000L; +@@ -1118,7 +1118,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + while (iterator.hasNext()) { + EntityPlayer entityplayer = (EntityPlayer) iterator.next(); + +- if (entityplayer.isSpectator() || (entityplayer.fauxSleeping && !entityplayer.isSleeping())) { // CraftBukkit ++ if (entityplayer.isSpectator() || (entityplayer.fauxSleeping && !entityplayer.isSleeping()) || (purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur + ++i; + } else if (entityplayer.isSleeping()) { + ++j; +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index cb7e34924cb5dbff25d1ffe05cfe5bc22e4a90ed..406e840499e09638e8b325d0e52b764e80acc777 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -1,6 +1,7 @@ + package net.pl3x.purpur; + + import com.google.common.base.Throwables; ++import net.minecraft.server.LocaleLanguage; + import net.minecraft.server.MinecraftServer; + import net.pl3x.purpur.command.PurpurCommand; + import org.bukkit.Bukkit; +@@ -129,6 +130,15 @@ public class PurpurConfig { + return config.getString(path, config.getString(path)); + } + ++ public static String afkBroadcastAway = "§e§o%s is now AFK"; ++ public static String afkBroadcastBack = "§e§o%s is no longer AFK"; ++ public static String afkTabListPrefix = "[AFK] "; ++ private static void messages() { ++ afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); ++ afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); ++ afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix); ++ } ++ + public static String timingsUrl = "https://timings.pl3x.net"; + private static void timingsSettings() { + timingsUrl = getString("settings.timings.url", timingsUrl); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 361f7857e461578e90cb71e15027dadaf794cb69..2578a4677d1ee060f687be531e696b7c7be89e84 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -56,4 +56,15 @@ public class PurpurWorldConfig { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); + } ++ ++ public boolean idleTimeoutKick = true; ++ public boolean idleTimeoutTickNearbyEntities = true; ++ public boolean idleTimeoutCountAsSleeping = false; ++ public boolean idleTimeoutUpdateTabList = false; ++ private void playerIdleTimeoutSettings() { ++ idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); ++ idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); ++ idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping); ++ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 7c18b22c7b93b6ca1189e481dde17476797b8fd5..debf252a23d0178f06fdadb9c27c3c66b9bbc2d0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2223,4 +2223,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return spigot; + } + // Spigot end ++ ++ // Purpur start ++ @Override ++ public boolean isAfk() { ++ return getHandle().isAfk(); ++ } ++ ++ @Override ++ public void setAfk(boolean setAfk) { ++ getHandle().setAfk(setAfk); ++ } ++ ++ @Override ++ public void resetIdleTimer() { ++ getHandle().resetIdleTimer(); ++ } ++ // Purpur end + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 0b93635ba59df4eb4456a97c5e9b51ab5aeda53f..b47d6fa2de3368d1afe329573bc18c3541bb7377 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -207,6 +207,7 @@ public class ActivationRange + { + + player.activatedTick = MinecraftServer.currentTick; ++ if (!player.world.purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur + maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange ); + ActivationType.MISC.boundingBox = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange ); + ActivationType.RAIDER.boundingBox = player.getBoundingBox().grow( raiderActivationRange, 256, raiderActivationRange ); diff --git a/patches/Purpur/patches/server/0009-Bring-back-server-name.patch b/patches/Purpur/patches/server/0009-Bring-back-server-name.patch new file mode 100644 index 00000000..4fc877ce --- /dev/null +++ b/patches/Purpur/patches/server/0009-Bring-back-server-name.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 26 May 2019 15:19:14 -0500 +Subject: [PATCH] Bring back server name + + +diff --git a/src/main/java/net/minecraft/server/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/DedicatedServerProperties.java +index 65961a03728852bd75367083a0de6fd0082b17cb..780474397acb4d0e7ecb4540e1a2db5721e59d3d 100644 +--- a/src/main/java/net/minecraft/server/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/DedicatedServerProperties.java +@@ -10,6 +10,7 @@ public class DedicatedServerProperties extends PropertyManager +Date: Sat, 21 Mar 2020 11:47:39 -0500 +Subject: [PATCH] Configurable server mod name + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index af5c1479d2cb8092d84e2d3d5166060d9ff2df71..90556fca9315e71c4f3fbd231ac6d765677ca271 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1511,7 +1511,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Sun, 5 May 2019 12:58:45 -0500 +Subject: [PATCH] LivingEntity safeFallDistance + + +diff --git a/src/main/java/net/minecraft/server/EntityGiantZombie.java b/src/main/java/net/minecraft/server/EntityGiantZombie.java +index 702242653a47051c9ed32304c427c27652af6157..9f4f56c47ecd4b35ebf33ca0bf9a040074ababf2 100644 +--- a/src/main/java/net/minecraft/server/EntityGiantZombie.java ++++ b/src/main/java/net/minecraft/server/EntityGiantZombie.java +@@ -4,6 +4,7 @@ public class EntityGiantZombie extends EntityMonster { + + public EntityGiantZombie(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ this.safeFallDistance = 10.0F; // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorseAbstract.java b/src/main/java/net/minecraft/server/EntityHorseAbstract.java +index 2a91f07ca9c4dc0cb3b5aef5c9c1db7f69773530..7604fd83de9cfe93d427a9a1f6bbbee76aa861e8 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseAbstract.java ++++ b/src/main/java/net/minecraft/server/EntityHorseAbstract.java +@@ -210,7 +210,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + + @Override + protected int e(float f, float f1) { +- return MathHelper.f((f * 0.5F - 3.0F) * f1); ++ return MathHelper.f((f * 0.5F - this.safeFallDistance) * f1); // Purpur + } + + protected int getChestSlots() { +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 4fe5a8d0201d662c68dd58eeb8cf1d304787edb4..279662570c0bbd6c00b5732881cd35dfe694b25d 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -132,6 +132,7 @@ public abstract class EntityLiving extends Entity { + // CraftBukkit start + public int expToDrop; + public int maxAirTicks = 300; ++ public float safeFallDistance = 3.0F; // Purpur + boolean forceDrops; + ArrayList drops = new ArrayList(); + public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; +@@ -226,8 +227,8 @@ public abstract class EntityLiving extends Entity { + this.cR(); + } + +- if (!this.world.isClientSide && this.fallDistance > 3.0F && flag) { +- float f = (float) MathHelper.f(this.fallDistance - 3.0F); ++ if (!this.world.isClientSide && this.fallDistance > this.safeFallDistance && flag) { // Purpur ++ float f = (float) MathHelper.f(this.fallDistance - this.safeFallDistance); // Purpur + + if (!iblockdata.isAir()) { + double d1 = Math.min((double) (0.2F + f / 15.0F), 2.5D); +@@ -1685,7 +1686,7 @@ public abstract class EntityLiving extends Entity { + MobEffect mobeffect = this.getEffect(MobEffects.JUMP); + float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1); + +- return MathHelper.f((f - 3.0F - f2) * f1); ++ return MathHelper.f((f - this.safeFallDistance - f2) * f1); // Purpur + } + + protected void playBlockStepSound() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 16e69cfd4994fd6850ee3635ea819379412351c9..0292cae6225ae2ee156f436c8827a018e8ffa723 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -826,4 +826,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + getHandle().setHurtDirection(hurtDirection); + } + // Paper end ++ ++ // Purpur start ++ @Override ++ public float getSafeFallDistance() { ++ return getHandle().safeFallDistance; ++ } ++ ++ @Override ++ public void setSafeFallDistance(float safeFallDistance) { ++ getHandle().safeFallDistance = safeFallDistance; ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0012-Lagging-threshold.patch b/patches/Purpur/patches/server/0012-Lagging-threshold.patch new file mode 100644 index 00000000..1eccab1d --- /dev/null +++ b/patches/Purpur/patches/server/0012-Lagging-threshold.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 23 Jul 2019 10:07:16 -0500 +Subject: [PATCH] Lagging threshold + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 90556fca9315e71c4f3fbd231ac6d765677ca271..e12fecf47798f638b85e7c6d54055a526097811c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -163,6 +163,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Fri, 5 Jul 2019 16:36:55 -0500 +Subject: [PATCH] ItemFactory#getMonsterEgg + + +diff --git a/src/main/java/net/minecraft/server/ItemMonsterEgg.java b/src/main/java/net/minecraft/server/ItemMonsterEgg.java +index a236e0441fc20270b4c44ed5a7d5b94297949226..eeac4aeaf21b43647aa643ec55a1ae842a7ac197 100644 +--- a/src/main/java/net/minecraft/server/ItemMonsterEgg.java ++++ b/src/main/java/net/minecraft/server/ItemMonsterEgg.java +@@ -9,7 +9,7 @@ import javax.annotation.Nullable; + + public class ItemMonsterEgg extends Item { + +- private static final Map, ItemMonsterEgg> a = Maps.newIdentityHashMap(); ++ public static final Map, ItemMonsterEgg> a = Maps.newIdentityHashMap(); // Purpur - private -> public + private final int b; + private final int c; + private final EntityTypes d; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index af84813c703813ec3a6ca89ff437d89e31a9100f..2d38a4782d85dd9c5e6097bea6c13c6185ea05ab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -388,4 +388,18 @@ public final class CraftItemFactory implements ItemFactory { + new net.md_5.bungee.api.chat.TextComponent(customName)); + } + // Paper end ++ ++ // Purpur start ++ @Override ++ public ItemStack getMonsterEgg(org.bukkit.entity.EntityType type) { ++ if (type == null) { ++ return null; ++ } ++ String name = type.getKey().toString(); ++ net.minecraft.server.MinecraftKey key = new net.minecraft.server.MinecraftKey(name); ++ net.minecraft.server.EntityTypes types = net.minecraft.server.EntityTypes.getFromKey(key); ++ net.minecraft.server.ItemMonsterEgg egg = net.minecraft.server.ItemMonsterEgg.a.get(types); ++ return new net.minecraft.server.ItemStack(egg).asBukkitMirror(); ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0014-PlayerSetSpawnerTypeWithEggEvent.patch b/patches/Purpur/patches/server/0014-PlayerSetSpawnerTypeWithEggEvent.patch new file mode 100644 index 00000000..52606f59 --- /dev/null +++ b/patches/Purpur/patches/server/0014-PlayerSetSpawnerTypeWithEggEvent.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 5 Jul 2019 18:21:00 -0500 +Subject: [PATCH] PlayerSetSpawnerTypeWithEggEvent + + +diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java +index bf914dc5ee7f2d4a324b6711ea273f5581ec84ad..1138981aebb7fbb3f3839dcd221e3ee850ab4456 100644 +--- a/src/main/java/net/minecraft/server/EntityTypes.java ++++ b/src/main/java/net/minecraft/server/EntityTypes.java +@@ -143,6 +143,16 @@ public class EntityTypes { + return (EntityTypes) IRegistry.a((IRegistry) IRegistry.ENTITY_TYPE, s, (Object) entitytypes_builder.a(s)); + } + ++ // Purpur start ++ public static EntityTypes getFromBukkitType(org.bukkit.entity.EntityType bukkitType) { ++ return getFromKey(new MinecraftKey(bukkitType.getKey().toString())); ++ } ++ ++ public static EntityTypes getFromKey(MinecraftKey key) { ++ return IRegistry.ENTITY_TYPE.get(key); ++ } ++ // Purpur end ++ + public static MinecraftKey getName(EntityTypes entitytypes) { + return IRegistry.ENTITY_TYPE.getKey(entitytypes); + } +@@ -287,6 +297,16 @@ public class EntityTypes { + return this.bg; + } + ++ // Purpur start ++ public String getName() { ++ return IRegistry.ENTITY_TYPE.getKey(this).getKey(); ++ } ++ ++ public String getTranslatedName() { ++ return getNameComponent().getString(); ++ } ++ // Purpur end ++ + public String getDescriptionId() { return f(); } // Paper - OBFHELPER + public String f() { + if (this.bo == null) { +@@ -296,6 +316,7 @@ public class EntityTypes { + return this.bo; + } + ++ public IChatBaseComponent getNameComponent() { return g(); } // Purpur - OBFHELPER + public IChatBaseComponent g() { + if (this.bp == null) { + this.bp = new ChatMessage(this.f()); +diff --git a/src/main/java/net/minecraft/server/ItemMonsterEgg.java b/src/main/java/net/minecraft/server/ItemMonsterEgg.java +index eeac4aeaf21b43647aa643ec55a1ae842a7ac197..d66acf90d0b1bcd5c6481d2a2ee753c7693995b0 100644 +--- a/src/main/java/net/minecraft/server/ItemMonsterEgg.java ++++ b/src/main/java/net/minecraft/server/ItemMonsterEgg.java +@@ -7,6 +7,13 @@ import java.util.Objects; + import java.util.Optional; + import javax.annotation.Nullable; + ++// Purpur start ++import net.pl3x.purpur.event.PlayerSetSpawnerTypeWithEggEvent; ++import org.bukkit.block.CreatureSpawner; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Player; ++// Purpur end ++ + public class ItemMonsterEgg extends Item { + + public static final Map, ItemMonsterEgg> a = Maps.newIdentityHashMap(); // Purpur - private -> public +@@ -41,6 +48,15 @@ public class ItemMonsterEgg extends Item { + MobSpawnerAbstract mobspawnerabstract = ((TileEntityMobSpawner) tileentity).getSpawner(); + EntityTypes entitytypes = this.a(itemstack.getTag()); + ++ // Purpur start ++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ PlayerSetSpawnerTypeWithEggEvent event = new PlayerSetSpawnerTypeWithEggEvent((Player) itemactioncontext.getEntity().getBukkitEntity(), bukkitBlock, (CreatureSpawner) bukkitBlock.getState(), EntityType.fromName(entitytypes.getName())); ++ if (!event.callEvent()) { ++ return EnumInteractionResult.FAIL; ++ } ++ entitytypes = EntityTypes.getFromBukkitType(event.getEntityType()); ++ // Purpur end ++ + mobspawnerabstract.setMobName(entitytypes); + tileentity.update(); + world.notify(blockposition, iblockdata, iblockdata, 3); diff --git a/patches/Purpur/patches/server/0015-EMC-MonsterEggSpawnEvent.patch b/patches/Purpur/patches/server/0015-EMC-MonsterEggSpawnEvent.patch new file mode 100644 index 00000000..3dfa2461 --- /dev/null +++ b/patches/Purpur/patches/server/0015-EMC-MonsterEggSpawnEvent.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 20 Jul 2013 22:40:56 -0400 +Subject: [PATCH] EMC - MonsterEggSpawnEvent + + +diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java +index 1138981aebb7fbb3f3839dcd221e3ee850ab4456..37b984a5b6c1c6e146e1c4b0947d1e39a051cfbb 100644 +--- a/src/main/java/net/minecraft/server/EntityTypes.java ++++ b/src/main/java/net/minecraft/server/EntityTypes.java +@@ -187,19 +187,46 @@ public class EntityTypes { + + @Nullable + public Entity spawnCreature(WorldServer worldserver, @Nullable ItemStack itemstack, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1) { +- return this.spawnCreature(worldserver, itemstack == null ? null : itemstack.getTag(), itemstack != null && itemstack.hasName() ? itemstack.getName() : null, entityhuman, blockposition, enummobspawn, flag, flag1); ++ return this.spawnCreature(worldserver, itemstack, itemstack == null ? null : itemstack.getTag(), itemstack != null && itemstack.hasName() ? itemstack.getName() : null, entityhuman, blockposition, enummobspawn, flag, flag1); // Purpur + } + + @Nullable + public T spawnCreature(WorldServer worldserver, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1) { ++ // Purpur start ++ return spawnCreature(worldserver, null, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); ++ } ++ ++ @Nullable ++ public T spawnCreature(WorldServer worldserver, @Nullable ItemStack itemstack, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1) { ++ // Purpur end + // CraftBukkit start +- return this.spawnCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); ++ return this.spawnCreature(worldserver, itemstack, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Purpur + } + + @Nullable + public T spawnCreature(WorldServer worldserver, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Purpur start ++ return spawnCreature(worldserver, null, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason); ++ } ++ ++ @Nullable ++ public T spawnCreature(WorldServer worldserver, @Nullable ItemStack itemstack, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Purpur end + T t0 = this.createCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); + ++ // Purpur start ++ if (spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG && itemstack != null && t0 != null) { ++ final net.pl3x.purpur.event.entity.MonsterEggSpawnEvent event = new net.pl3x.purpur.event.entity.MonsterEggSpawnEvent(entityhuman != null ? entityhuman.getBukkitEntity() : null, (org.bukkit.entity.LivingEntity) t0.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ if (!event.callEvent()) { ++ worldserver.removeEntity(t0); ++ return null; ++ } ++ if (event.getEntity().getEntityId() != t0.getId()) { ++ return (T) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getEntity()).getHandle(); ++ } ++ } ++ // Purpur end ++ + if (t0 != null) { + worldserver.addAllEntities(t0, spawnReason); + return !t0.dead ? t0 : null; // Don't return an entity when CreatureSpawnEvent is canceled diff --git a/patches/Purpur/patches/server/0016-EntityMoveEvent.patch b/patches/Purpur/patches/server/0016-EntityMoveEvent.patch new file mode 100644 index 00000000..188733b6 --- /dev/null +++ b/patches/Purpur/patches/server/0016-EntityMoveEvent.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 11 Feb 2020 21:56:48 -0600 +Subject: [PATCH] EntityMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 279662570c0bbd6c00b5732881cd35dfe694b25d..50cb2551598101d1c12007c53421b6c2f59ce2aa 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -2813,6 +2813,20 @@ public abstract class EntityLiving extends Entity { + + this.collideNearby(); + this.world.getMethodProfiler().exit(); ++ // Purpur start ++ if (((WorldServer) world).hasEntityMoveEvent) { ++ if (lastX != locX() || lastY != locY() || lastZ != locZ() || lastYaw != yaw || lastPitch != pitch) { ++ Location from = new Location(world.getWorld(), lastX, lastY, lastZ, lastYaw, lastPitch); ++ Location to = new Location (world.getWorld(), locX(), locY(), locZ(), yaw, pitch); ++ net.pl3x.purpur.event.entity.EntityMoveEvent event = new net.pl3x.purpur.event.entity.EntityMoveEvent(getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ setLocation(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); ++ } ++ } ++ } ++ // Purpur end + if (!this.world.isClientSide && this.dO() && this.aG()) { + this.damageEntity(DamageSource.DROWN, 1.0F); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e12fecf47798f638b85e7c6d54055a526097811c..d79cf993cdadfe7fadd6c7e65b9fc691a298c702 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1398,6 +1398,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper ++ worldserver.hasEntityMoveEvent = net.pl3x.purpur.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur + TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.methodProfiler.a(() -> { +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index efe2a9ad2ece23bd71f4ad63b2c6f54f42345b55..2315c71465e4c1ea00a4c355b43de1e3fb0ca995 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -101,6 +101,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final Convertable.ConversionSession convertable; + public final UUID uuid; + boolean hasPhysicsEvent = true; // Paper ++ boolean hasEntityMoveEvent = false; // Purpur + private static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } diff --git a/patches/Purpur/patches/server/0017-Player-invulnerabilities.patch b/patches/Purpur/patches/server/0017-Player-invulnerabilities.patch new file mode 100644 index 00000000..35cdc7d4 --- /dev/null +++ b/patches/Purpur/patches/server/0017-Player-invulnerabilities.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 2 May 2020 20:55:44 -0500 +Subject: [PATCH] Player invulnerabilities + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 6bab47ab2583735c36d74d849ab0923494a265db..6e292d91d509327fa5b9dea947811d04e78e1e93 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -150,6 +150,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + this.canPickUpLoot = true; + this.maxHealthCache = this.getMaxHealth(); + this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper ++ ++ this.invulnerableTicks = world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur + } + // Paper start + public BlockPosition getPointInFront(double inFront) { +@@ -991,6 +993,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + } + ++ // Purpur start ++ public boolean isSpawnInvulnerable() { ++ return invulnerableTicks > 0 || frozen; ++ } ++ // Purpur end ++ + @Override + public boolean damageEntity(DamageSource damagesource, float f) { + if (this.isInvulnerable(damagesource)) { +@@ -998,7 +1006,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } else { + boolean flag = this.server.j() && this.canPvP() && "fall".equals(damagesource.translationIndex); + +- if (!flag && this.invulnerableTicks > 0 && damagesource != DamageSource.OUT_OF_WORLD) { ++ if (!flag && isSpawnInvulnerable() && damagesource != DamageSource.OUT_OF_WORLD) { // Purpur + return false; + } else { + if (damagesource instanceof EntityDamageSource) { +@@ -1169,6 +1177,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + // CraftBukkit end + } + ++ this.invulnerableTicks = worldserver.purpurConfig.playerSpawnInvulnerableTicks; // Purpur + return this; + } + } +@@ -2322,9 +2331,17 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + @Override + public boolean isFrozen() { // Paper - protected > public +- return super.isFrozen() || (this.playerConnection != null && this.playerConnection.isDisconnected()); // Paper ++ return super.isFrozen() || frozen || (this.playerConnection != null && this.playerConnection.isDisconnected()); // Paper // Purpur + } + ++ // Purpur start ++ private boolean frozen = false; ++ ++ public void setFrozen(boolean frozen) { ++ this.frozen = frozen; ++ } ++ // Purpur end ++ + @Override + public Scoreboard getScoreboard() { + return getBukkitEntity().getScoreboard().getHandle(); +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index caf9ce94a7cb6154981d42953c81b588b19e3814..e833447073585ff97f8ab3a95caea8866fcc24e2 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -1708,6 +1708,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + PlayerConnectionUtils.ensureMainThread(packetplayinresourcepackstatus, this, this.player.getWorldServer()); + // Paper start + PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packetplayinresourcepackstatus.status.ordinal()]; ++ player.setFrozen(packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED); // Purpur + player.getBukkitEntity().setResourcePackStatus(packStatus); + this.server.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), packStatus)); + // Paper end +diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java +index 1eb44877e7384ae0a028a12b832684126b8d50ec..5b0fdcf5190e4ab2af249a5a0952b66d52f08751 100644 +--- a/src/main/java/net/minecraft/server/PlayerList.java ++++ b/src/main/java/net/minecraft/server/PlayerList.java +@@ -914,6 +914,8 @@ public abstract class PlayerList { + } + // Paper end + ++ entityplayer1.invulnerableTicks = entityplayer1.world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur ++ + // CraftBukkit end + return entityplayer1; + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 2578a4677d1ee060f687be531e696b7c7be89e84..c441fcea9b2b5a77b801c8a69541cf42050927dc 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -67,4 +67,11 @@ public class PurpurWorldConfig { + idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping); + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + } ++ ++ public int playerSpawnInvulnerableTicks = 60; ++ public boolean playerInvulnerableWhileAcceptingResourcePack = false; ++ private void playerInvulnerabilities() { ++ playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks); ++ playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index debf252a23d0178f06fdadb9c27c3c66b9bbc2d0..8a28c68c5fc22838c62ceef738b330afb840c4c6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2239,5 +2239,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void resetIdleTimer() { + getHandle().resetIdleTimer(); + } ++ ++ @Override ++ public boolean isSpawnInvulnerable() { ++ return getHandle().isSpawnInvulnerable(); ++ } ++ ++ @Override ++ public int getSpawnInvulnerableTicks() { ++ return getHandle().invulnerableTicks; ++ } ++ ++ @Override ++ public void setSpawnInvulnerableTicks(int invulnerableTicks) { ++ getHandle().invulnerableTicks = invulnerableTicks; ++ } + // Purpur end + } diff --git a/patches/Purpur/patches/server/0018-Anvil-API.patch b/patches/Purpur/patches/server/0018-Anvil-API.patch new file mode 100644 index 00000000..e7794f5e --- /dev/null +++ b/patches/Purpur/patches/server/0018-Anvil-API.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 19 Apr 2020 00:17:56 -0500 +Subject: [PATCH] Anvil API + + +diff --git a/src/main/java/net/minecraft/server/ContainerAnvil.java b/src/main/java/net/minecraft/server/ContainerAnvil.java +index fc2038df89f25c07f6f853f6df41fe9b203c3585..4aa6b035a6a8ea39401c6566cd286de39f60e942 100644 +--- a/src/main/java/net/minecraft/server/ContainerAnvil.java ++++ b/src/main/java/net/minecraft/server/ContainerAnvil.java +@@ -20,6 +20,8 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + public int maximumRepairCost = 40; + private CraftInventoryView bukkitEntity; + // CraftBukkit end ++ public boolean bypassCost = false; // Purpur ++ public boolean canDoUnsafeEnchants = false; // Purpur + + public ContainerAnvil(int i, PlayerInventory playerinventory) { + this(i, playerinventory, ContainerAccess.a); +@@ -38,12 +40,14 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + + @Override + protected boolean b(EntityHuman entityhuman, boolean flag) { +- return (entityhuman.abilities.canInstantlyBuild || entityhuman.expLevel >= this.levelCost.get()) && this.levelCost.get() > 0; ++ return (entityhuman.abilities.canInstantlyBuild || entityhuman.expLevel >= this.levelCost.get()) && (bypassCost || this.levelCost.get() > 0); // Purpur + } + + @Override + protected ItemStack a(EntityHuman entityhuman, ItemStack itemstack) { ++ if (net.pl3x.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new net.pl3x.purpur.event.inventory.AnvilTakeResultEvent(entityhuman.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); // Purpur + if (!entityhuman.abilities.canInstantlyBuild) { ++ if (bypassCost) ((EntityPlayer) entityhuman).lastSentExp = -1; else // Purpur + entityhuman.levelDown(-this.levelCost.get()); + } + +@@ -94,6 +98,12 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + + @Override + public void e() { ++ // Purpur start ++ bypassCost = false; ++ canDoUnsafeEnchants = false; ++ if (net.pl3x.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new net.pl3x.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); ++ // Purpur end ++ + ItemStack itemstack = this.repairInventory.getItem(0); + + this.levelCost.set(1); +@@ -170,7 +180,7 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + int i2 = (Integer) map1.get(enchantment); + + i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); +- boolean flag3 = enchantment.canEnchant(itemstack); ++ boolean flag3 = canDoUnsafeEnchants || enchantment.canEnchant(itemstack); // Purpur + + if (this.player.abilities.canInstantlyBuild || itemstack.getItem() == Items.ENCHANTED_BOOK) { + flag3 = true; +@@ -182,7 +192,7 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + Enchantment enchantment1 = (Enchantment) iterator1.next(); + + if (enchantment1 != enchantment && !enchantment.isCompatible(enchantment1)) { +- flag3 = false; ++ flag3 = canDoUnsafeEnchants; // Purpur + ++i; + } + } +@@ -253,6 +263,13 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + this.levelCost.set(maximumRepairCost - 1); // CraftBukkit + } + ++ // Purpur start ++ if (bypassCost && levelCost.get() >= maximumRepairCost) { ++ itemstack.getOrCreateTagAndSet("Purpur.realCost", NBTTagInt.a(levelCost.get())); ++ levelCost.set(maximumRepairCost - 1); ++ } ++ // Purpur end ++ + if (this.levelCost.get() >= maximumRepairCost && !this.player.abilities.canInstantlyBuild) { // CraftBukkit + itemstack1 = ItemStack.b; + } +@@ -274,6 +291,12 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + + org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), itemstack1); // CraftBukkit + this.c(); ++ // Purpur start ++ if (canDoUnsafeEnchants && itemstack1 != ItemStack.NULL_ITEM) { ++ ((EntityPlayer) player).playerConnection.sendPacket(new PacketPlayOutSetSlot(windowId, 2, itemstack1)); ++ ((EntityPlayer) player).playerConnection.sendPacket(new PacketPlayOutWindowData(windowId, 0, levelCost.get())); ++ } ++ // Purpur end + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +index 9374c27061280e51bcbae24573eb5d36736c3e4f..d45f356be39cbe271cd2a257b9dac66b88a346fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +@@ -9,7 +9,7 @@ import org.bukkit.inventory.AnvilInventory; + public class CraftInventoryAnvil extends CraftResultInventory implements AnvilInventory { + + private final Location location; +- private final ContainerAnvil container; ++ public final ContainerAnvil container; // Purpur - private -> public + + public CraftInventoryAnvil(Location location, IInventory inventory, IInventory resultInventory, ContainerAnvil container) { + super(inventory, resultInventory); +@@ -47,4 +47,26 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn + Preconditions.checkArgument(levels >= 0, "Maximum repair cost must be positive (or 0)"); + container.maximumRepairCost = levels; + } ++ ++ // Purpur start ++ @Override ++ public boolean canBypassCost() { ++ return container.bypassCost; ++ } ++ ++ @Override ++ public void setBypassCost(boolean bypassCost) { ++ container.bypassCost = bypassCost; ++ } ++ ++ @Override ++ public boolean canDoUnsafeEnchants() { ++ return container.canDoUnsafeEnchants; ++ } ++ ++ @Override ++ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { ++ container.canDoUnsafeEnchants = canDoUnsafeEnchants; ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0019-Configurable-villager-brain-ticks.patch b/patches/Purpur/patches/server/0019-Configurable-villager-brain-ticks.patch new file mode 100644 index 00000000..d663244d --- /dev/null +++ b/patches/Purpur/patches/server/0019-Configurable-villager-brain-ticks.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 23 Jul 2019 08:28:21 -0500 +Subject: [PATCH] Configurable villager brain ticks + + +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index 21d0570a59240e955ff148bac0226b220a7dec36..c034869310ca3dadbfe5425c45aaa80dac59ac88 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -58,6 +58,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + }, MemoryModuleType.MEETING_POINT, (entityvillager, villageplacetype) -> { + return villageplacetype == VillagePlaceType.s; + }); ++ private final int brainTickOffset; // Purpur + + public EntityVillager(EntityTypes entitytypes, World world) { + this(entitytypes, world, VillagerType.PLAINS); +@@ -70,6 +71,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + this.getNavigation().d(true); + this.setCanPickupLoot(true); + this.setVillagerData(this.getVillagerData().withType(villagertype).withProfession(VillagerProfession.NONE)); ++ this.brainTickOffset = getRandom().nextInt(100); // Purpur + } + + @Override +@@ -166,7 +168,11 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + protected void mobTick() { mobTick(false); } + protected void mobTick(boolean inactive) { + this.world.getMethodProfiler().enter("villagerBrain"); +- if (!inactive) this.getBehaviorController().Wa((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper ++ // Purpur start ++ boolean tick = (world.getTime() + brainTickOffset) % world.purpurConfig.villagerBrainTicks == 0; ++ if (((WorldServer) world).getMinecraftServer().lagging ? tick : world.purpurConfig.villagerUseBrainTicksOnlyWhenLagging || tick) ++ // Purpur end ++ if (!inactive) this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper + this.world.getMethodProfiler().exit(); + if (this.bF) { + this.bF = false; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index c441fcea9b2b5a77b801c8a69541cf42050927dc..c7fb5a737cab0083c39732247acb8f4e87562894 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -74,4 +74,11 @@ public class PurpurWorldConfig { + playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks); + playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); + } ++ ++ public int villagerBrainTicks = 1; ++ public boolean villagerUseBrainTicksOnlyWhenLagging = true; ++ private void villagerSettings() { ++ villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); ++ villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); ++ } + } diff --git a/patches/Purpur/patches/server/0020-Alternative-Keepalive-Handling.patch b/patches/Purpur/patches/server/0020-Alternative-Keepalive-Handling.patch new file mode 100644 index 00000000..5cb0efbd --- /dev/null +++ b/patches/Purpur/patches/server/0020-Alternative-Keepalive-Handling.patch @@ -0,0 +1,85 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/PacketPlayInKeepAlive.java b/src/main/java/net/minecraft/server/PacketPlayInKeepAlive.java +index 8e93f1540ba5f995489c1fbcec70d10b011cd9c3..470f92c4fb0919d052b19acff8dff533724fbcc1 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayInKeepAlive.java ++++ b/src/main/java/net/minecraft/server/PacketPlayInKeepAlive.java +@@ -22,6 +22,7 @@ public class PacketPlayInKeepAlive implements Packet { + packetdataserializer.writeLong(this.a); + } + ++ public long getId() { return b(); } // Purpur - OBFHELPER + public long b() { + return this.a; + } +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index e833447073585ff97f8ab3a95caea8866fcc24e2..fdd517290a0b306dff9c0fffadc424b59b8504f3 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -84,6 +84,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + private long lastKeepAlive = SystemUtils.getMonotonicMillis(); private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER + private boolean awaitingKeepAlive; private void setPendingPing(boolean isPending) { this.awaitingKeepAlive = isPending;}; private boolean isPendingPing() { return this.awaitingKeepAlive;}; // Paper - OBFHELPER + private long h; private void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; private long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER ++ private java.util.List keepAlives = new java.util.ArrayList<>(); // Purpur + // CraftBukkit start - multithreaded fields + private volatile int chatThrottle; + private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle"); +@@ -218,6 +219,21 @@ public class PlayerConnection implements PacketListenerPlayIn { + long currentTime = SystemUtils.getMonotonicMillis(); + long elapsedTime = currentTime - this.getLastPing(); + ++ // Purpur start ++ if (net.pl3x.purpur.PurpurConfig.useAlternateKeepAlive) { ++ if (elapsedTime >= 1000L) { // 1 second ++ if (!processedDisconnect && keepAlives.size() > KEEPALIVE_LIMIT) { ++ PlayerConnection.LOGGER.warn("{} was kicked due to keepalive timeout!", player.getName()); ++ disconnect(new ChatMessage("disconnect.timeout")); ++ } else { ++ setLastPing(currentTime); // hijack this field for 1 second intervals ++ keepAlives.add(currentTime); // currentTime is ID ++ sendPacket(new PacketPlayOutKeepAlive(currentTime)); ++ } ++ } ++ } else ++ // Purpur end ++ + if (this.isPendingPing()) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + PlayerConnection.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getName()); // more info +@@ -2882,6 +2898,16 @@ public class PlayerConnection implements PacketListenerPlayIn { + + @Override + public void a(PacketPlayInKeepAlive packetplayinkeepalive) { ++ // Purpur start ++ if (net.pl3x.purpur.PurpurConfig.useAlternateKeepAlive) { ++ long id = packetplayinkeepalive.getId(); ++ if (keepAlives.size() > 0 && keepAlives.contains(id)) { ++ int ping = (int) (SystemUtils.getMonotonicMillis() - id); ++ player.ping = (player.ping * 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.awaitingKeepAlive && packetplayinkeepalive.b() == this.h) { + int i = (int) (SystemUtils.getMonotonicMillis() - this.lastKeepAlive); +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index a34ed978596f1a466b0b48e7db92ac4f1345a996..9593ac057ef5b79fb54501d7cce1e69e49102918 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -155,6 +155,11 @@ public class PurpurConfig { + laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); + } + ++ public static boolean useAlternateKeepAlive = false; ++ private static void useAlternateKeepAlive() { ++ useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive); ++ } ++ + public static boolean barrelSixRows = false; + public static boolean enderChestSixRows = false; + public static boolean enderChestPermissionRows = false; diff --git a/patches/Purpur/patches/server/0021-Silk-touch-spawners.patch b/patches/Purpur/patches/server/0021-Silk-touch-spawners.patch new file mode 100644 index 00000000..0282a262 --- /dev/null +++ b/patches/Purpur/patches/server/0021-Silk-touch-spawners.patch @@ -0,0 +1,182 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 9 May 2019 14:27:37 -0500 +Subject: [PATCH] Silk touch spawners + + +diff --git a/src/main/java/net/minecraft/server/BlockMobSpawner.java b/src/main/java/net/minecraft/server/BlockMobSpawner.java +index 81e145ff0759322f74888c81df8d2133fece7144..91b92d95a961ba00ddd7026bb83d021bb1ac51ed 100644 +--- a/src/main/java/net/minecraft/server/BlockMobSpawner.java ++++ b/src/main/java/net/minecraft/server/BlockMobSpawner.java +@@ -1,5 +1,14 @@ + package net.minecraft.server; + ++// Purpur start ++import net.md_5.bungee.api.ChatColor; ++import net.md_5.bungee.api.chat.BaseComponent; ++import net.md_5.bungee.api.chat.TextComponent; ++import net.md_5.bungee.chat.ComponentSerializer; ++ ++import java.util.List; ++// Purpur end ++ + public class BlockMobSpawner extends BlockTileEntity { + + protected BlockMobSpawner(BlockBase.Info blockbase_info) { +@@ -11,6 +20,59 @@ public class BlockMobSpawner extends BlockTileEntity { + return new TileEntityMobSpawner(); + } + ++ // Purpur start ++ @Override ++ public void a(World world, EntityHuman entityhuman, BlockPosition blockposition, IBlockData iblockdata, TileEntity tileentity, ItemStack itemstack) { ++ if (world.purpurConfig.silkTouchEnabled && entityhuman.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(world, itemstack)) { ++ MinecraftKey type = ((TileEntityMobSpawner) tileentity).getSpawner().getMobName(); ++ if (type != null) { ++ String mobName = EntityTypes.getFromKey(type).getTranslatedName(); ++ ++ NBTTagCompound display = new NBTTagCompound(); ++ boolean customDisplay = false; ++ ++ String name = world.purpurConfig.silkTouchSpawnerName; ++ if (name != null && !name.isEmpty() && !name.equals("Spawner")) { ++ name = ChatColor.translateAlternateColorCodes('&', name ++ .replace("{mob}", mobName)); ++ BaseComponent[] comp = TextComponent.fromLegacyText(name); ++ display.set("Name", NBTTagString.create(ComponentSerializer.toString(comp))); ++ customDisplay = true; ++ } ++ ++ List lore = world.purpurConfig.silkTouchSpawnerLore; ++ if (lore != null && !lore.isEmpty()) { ++ NBTTagList list = new NBTTagList(); ++ for (String line : lore) { ++ line = ChatColor.translateAlternateColorCodes('&', line ++ .replace("{mob}", mobName)); ++ BaseComponent[] comp = TextComponent.fromLegacyText(line); ++ list.add(NBTTagString.create(ComponentSerializer.toString(comp))); ++ } ++ display.set("Lore", list); ++ customDisplay = true; ++ } ++ ++ NBTTagCompound tag = new NBTTagCompound(); ++ if (customDisplay) { ++ tag.set("display", display); ++ } ++ tag.setString("Purpur.mob_type", type.toString()); ++ ++ ItemStack item = new ItemStack(Blocks.SPAWNER.getItem()); ++ item.setTag(tag); ++ ++ dropItem(world, blockposition, item); ++ } ++ } ++ super.a(world, entityhuman, blockposition, iblockdata, tileentity, itemstack); ++ } ++ ++ private boolean isSilkTouch(World world, ItemStack itemstack) { ++ return itemstack != null && world.purpurConfig.silkTouchTools.contains(itemstack.getItem()) && EnchantmentManager.getEnchantmentLevel(Enchantments.SILK_TOUCH, itemstack) > 0; ++ } ++ // Purpur end ++ + @Override + public void dropNaturally(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, ItemStack itemstack) { + super.dropNaturally(iblockdata, worldserver, blockposition, itemstack); +@@ -23,6 +85,7 @@ public class BlockMobSpawner extends BlockTileEntity { + + @Override + public int getExpDrop(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, ItemStack itemstack) { ++ if (isSilkTouch(worldserver, itemstack)) return 0; // Purpur + int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15); + + return i; +diff --git a/src/main/java/net/minecraft/server/ItemSpawner.java b/src/main/java/net/minecraft/server/ItemSpawner.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5c7f73918542bbdbc8d8f97671f24091fc6cb83b +--- /dev/null ++++ b/src/main/java/net/minecraft/server/ItemSpawner.java +@@ -0,0 +1,23 @@ ++package net.minecraft.server; ++ ++public class ItemSpawner extends ItemBlock { ++ public ItemSpawner(Block block, Info info) { ++ super(block, info); ++ } ++ ++ @Override ++ protected boolean a(BlockPosition blockposition, World world, EntityHuman entityhuman, ItemStack itemstack, IBlockData iblockdata) { ++ boolean handled = super.a(blockposition, world, entityhuman, itemstack, iblockdata); ++ if (world.purpurConfig.silkTouchEnabled && entityhuman.getBukkitEntity().hasPermission("purpur.place.spawners")) { ++ TileEntity spawner = world.getTileEntity(blockposition); ++ if (spawner instanceof TileEntityMobSpawner && itemstack.hasTag()) { ++ NBTTagCompound tag = itemstack.getTag(); ++ if (tag.hasKey("Purpur.mob_type")) { ++ EntityTypes.getByName(tag.getString("Purpur.mob_type")).ifPresent(type -> ++ ((TileEntityMobSpawner) spawner).getSpawner().setMobName(type)); ++ } ++ } ++ } ++ return handled; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Items.java b/src/main/java/net/minecraft/server/Items.java +index 1c861bccc2652057cb154667a5bf7269092b2db4..67ebcbe4daa1ef3cef6ca43ec92befbe4156842e 100644 +--- a/src/main/java/net/minecraft/server/Items.java ++++ b/src/main/java/net/minecraft/server/Items.java +@@ -180,7 +180,7 @@ public class Items { + public static final Item ct = a(Blocks.PURPUR_BLOCK, CreativeModeTab.b); + public static final Item cu = a(Blocks.PURPUR_PILLAR, CreativeModeTab.b); + public static final Item cv = a(Blocks.PURPUR_STAIRS, CreativeModeTab.b); +- public static final Item cw = a(Blocks.SPAWNER); ++ public static final Item cw = a(Blocks.SPAWNER, new ItemSpawner(Blocks.SPAWNER, new Item.Info().a(EnumItemRarity.EPIC))); // Purpur + public static final Item cx = a(Blocks.OAK_STAIRS, CreativeModeTab.b); + public static final Item cy = a(Blocks.CHEST, CreativeModeTab.c); + public static final Item cz = a(Blocks.DIAMOND_ORE, CreativeModeTab.b); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index c7fb5a737cab0083c39732247acb8f4e87562894..ba89efb3159ab04a6239865967f93055cb6be7a9 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1,6 +1,12 @@ + package net.pl3x.purpur; + ++import net.minecraft.server.IRegistry; ++import net.minecraft.server.Item; ++import net.minecraft.server.Items; ++import net.minecraft.server.MinecraftKey; + import org.bukkit.configuration.ConfigurationSection; ++ ++import java.util.ArrayList; + import java.util.List; + import static net.pl3x.purpur.PurpurConfig.log; + +@@ -75,6 +81,29 @@ public class PurpurWorldConfig { + playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); + } + ++ public boolean silkTouchEnabled = false; ++ public String silkTouchSpawnerName = "Spawner"; ++ public List silkTouchSpawnerLore = new ArrayList<>(); ++ public List silkTouchTools = new ArrayList<>(); ++ private void silkTouchSettings() { ++ silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled); ++ silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); ++ silkTouchSpawnerLore.clear(); ++ getList("gameplay-mechanics.silk-touch.spawner-lore", new ArrayList(){{ ++ add("Spawns a {mob}"); ++ }}).forEach(line -> silkTouchSpawnerLore.add(line.toString())); ++ silkTouchTools.clear(); ++ getList("gameplay-mechanics.silk-touch.tools", new ArrayList(){{ ++ add("minecraft:iron_pickaxe"); ++ add("minecraft:golden_pickaxe"); ++ add("minecraft:diamond_pickaxe"); ++ add("minecraft:netherite_pickaxe"); ++ }}).forEach(key -> { ++ Item item = IRegistry.ITEM.get(new MinecraftKey(key.toString())); ++ if (item != Items.AIR) silkTouchTools.add(item); ++ }); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + private void villagerSettings() { diff --git a/patches/Purpur/patches/server/0022-MC-168772-Fix-Add-turtle-egg-block-options.patch b/patches/Purpur/patches/server/0022-MC-168772-Fix-Add-turtle-egg-block-options.patch new file mode 100644 index 00000000..1e9731b4 --- /dev/null +++ b/patches/Purpur/patches/server/0022-MC-168772-Fix-Add-turtle-egg-block-options.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 6 Jun 2019 22:15:46 -0500 +Subject: [PATCH] MC-168772 Fix - Add turtle egg block options + + +diff --git a/src/main/java/net/minecraft/server/BlockTurtleEgg.java b/src/main/java/net/minecraft/server/BlockTurtleEgg.java +index 553c8affab6228cb187549deb5b34f79ba8f912c..92cca6c44f12a9283988b84681aac760f1c38d7e 100644 +--- a/src/main/java/net/minecraft/server/BlockTurtleEgg.java ++++ b/src/main/java/net/minecraft/server/BlockTurtleEgg.java +@@ -163,6 +163,23 @@ public class BlockTurtleEgg extends Block { + } + + private boolean a(World world, Entity entity) { +- return !(entity instanceof EntityTurtle) && !(entity instanceof EntityBat) ? (!(entity instanceof EntityLiving) ? false : entity instanceof EntityHuman || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) : false; ++ // Purpur start - fix MC-168772 ++ if (entity instanceof EntityTurtle) { ++ return false; ++ } ++ if (!world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof EntityExperienceOrb) { ++ return false; ++ } ++ if (!world.purpurConfig.turtleEggsBreakFromItems && entity instanceof EntityItem) { ++ return false; ++ } ++ if (!world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof EntityMinecartAbstract) { ++ return false; ++ } ++ if (entity instanceof EntityLiving && !(entity instanceof EntityHuman)) { ++ return world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); ++ } ++ return true; ++ // Purpur end + } + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index ba89efb3159ab04a6239865967f93055cb6be7a9..f403f9dc6cd148b9f9c901f27ce6a77d4225a53f 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -104,6 +104,15 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean turtleEggsBreakFromExpOrbs = true; ++ public boolean turtleEggsBreakFromItems = true; ++ public boolean turtleEggsBreakFromMinecarts = true; ++ private void turtleEggSettings() { ++ turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); ++ turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); ++ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + private void villagerSettings() { diff --git a/patches/Purpur/patches/server/0023-Fix-vanilla-command-permission-handler.patch b/patches/Purpur/patches/server/0023-Fix-vanilla-command-permission-handler.patch new file mode 100644 index 00000000..6e928c66 --- /dev/null +++ b/patches/Purpur/patches/server/0023-Fix-vanilla-command-permission-handler.patch @@ -0,0 +1,30 @@ +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 + + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index d9c47f3fc18266df3be1f564c01dfc3e26941380..cf35c9d545582173f81347a656faf1940892716f 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -35,6 +35,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) { + 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 f34461460049a80c5ff57805927053a36a4db426..8d89e671eb9081f9198f2b2b2ae0b7bd186c1522 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -87,6 +87,7 @@ public final class VanillaCommandWrapper extends BukkitCommand { + } + + public static String getPermission(CommandNode vanillaCommand) { ++ if (vanillaCommand.getPermission() != null) return vanillaCommand.getPermission(); // Purpur + return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName()); + } + diff --git a/patches/Purpur/patches/server/0024-Logger-settings-suppressing-pointless-logs.patch b/patches/Purpur/patches/server/0024-Logger-settings-suppressing-pointless-logs.patch new file mode 100644 index 00000000..467241db --- /dev/null +++ b/patches/Purpur/patches/server/0024-Logger-settings-suppressing-pointless-logs.patch @@ -0,0 +1,46 @@ +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) + + +diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +index c680319e4040be2b60795b22a5e65d6444cc67ed..eaa1063ff2bc5621e93043c4de41ca62f1323fde 100644 +--- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java ++++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +@@ -170,6 +170,7 @@ public class AdvancementDataPlayer { + if (advancement == null) { + // CraftBukkit start + if (entry.getKey().getNamespace().equals("minecraft")) { ++ if (!net.pl3x.purpur.PurpurConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur + AdvancementDataPlayer.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", entry.getKey(), this.f); + } + // CraftBukkit end +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 9593ac057ef5b79fb54501d7cce1e69e49102918..ec8efc913f8f0bf6ff559a3a5b439a3d24f3a9b6 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -178,4 +178,11 @@ public class PurpurConfig { + InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); + } ++ ++ public static boolean loggerSuppressInitLegacyMaterialError = false; ++ public static boolean loggerSuppressIgnoredAdvancementWarnings = false; ++ private static void loggerSettings() { ++ loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError); ++ loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +index b14333ce9e6921024bc93c2847b84f1fb40a5508..d25204f7d4af1ddd37005ccbcbe6c03d8eefcf47 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +@@ -254,6 +254,7 @@ public final class CraftLegacy { + } + + static { ++ if (!net.pl3x.purpur.PurpurConfig.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/patches/Purpur/patches/server/0025-Disable-outdated-build-check.patch b/patches/Purpur/patches/server/0025-Disable-outdated-build-check.patch new file mode 100644 index 00000000..87d21d81 --- /dev/null +++ b/patches/Purpur/patches/server/0025-Disable-outdated-build-check.patch @@ -0,0 +1,19 @@ +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 + + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index a92721dff5c2a9a2a167b36c23d1ef22d2bbd3e1..b10873022efc8f01ef172e86cad07831d7bf0d5e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -259,7 +259,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 SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(Main.class.getPackage().getImplementationVendor()); // Paper + + Calendar deadline = Calendar.getInstance(); diff --git a/patches/Purpur/patches/server/0026-Giants-AI-settings.patch b/patches/Purpur/patches/server/0026-Giants-AI-settings.patch new file mode 100644 index 00000000..52696674 --- /dev/null +++ b/patches/Purpur/patches/server/0026-Giants-AI-settings.patch @@ -0,0 +1,182 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 12 May 2019 00:43:12 -0500 +Subject: [PATCH] Giants AI settings + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 013c44f80f74376e8bbb37afb5de07aa5d8fb1bc..39036105b51dddbce8e4986e3be226f31fb13051 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -136,7 +136,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + public double D; + public double E; + public double F; +- public float G; public final float getStepHeight() { return this.G; } // Tuinity - OBFHELPER ++ public float G; public final float getStepHeight() { return this.G; } public void setStepHeight(float stepHeight) { this.G = stepHeight; } // Tuinity - OBFHELPER // Purpur - OBFHELPER + public boolean noclip; + public float I; + protected final Random random; +diff --git a/src/main/java/net/minecraft/server/EntityGiantZombie.java b/src/main/java/net/minecraft/server/EntityGiantZombie.java +index 9f4f56c47ecd4b35ebf33ca0bf9a040074ababf2..565c938d879940d8e12fe320ea8524d2cf679c1f 100644 +--- a/src/main/java/net/minecraft/server/EntityGiantZombie.java ++++ b/src/main/java/net/minecraft/server/EntityGiantZombie.java +@@ -4,9 +4,66 @@ public class EntityGiantZombie extends EntityMonster { + + public EntityGiantZombie(EntityTypes entitytypes, World world) { + super(entitytypes, world); +- this.safeFallDistance = 10.0F; // Purpur ++ // Purpur start ++ this.safeFallDistance = 10.0F; ++ setStepHeight(world.purpurConfig.giantStepHeight); ++ // Purpur end + } + ++ // Purpur start ++ @Override ++ protected void initPathfinder() { ++ if (world.purpurConfig.giantHaveAI) { ++ this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D)); ++ this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 16.0F)); ++ this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D)); ++ if (world.purpurConfig.giantHaveHostileAI) { ++ this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false)); ++ this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this).a(EntityPigZombie.class)); ++ this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); ++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillager.class, false)); ++ this.targetSelector.a(4, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); ++ this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, true)); ++ } ++ } ++ } ++ ++ @Override ++ protected void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.giantMaxHealth); ++ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.giantMovementSpeed); ++ this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(this.world.purpurConfig.giantAttackDamage); ++ } ++ ++ @Override ++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @javax.annotation.Nullable GroupDataEntity groupdataentity, @javax.annotation.Nullable NBTTagCompound nbttagcompound) { ++ GroupDataEntity groupData = super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); ++ if (groupData == null) { ++ setEquipmentBasedOnDifficulty(difficultydamagescaler); ++ setEnchantmentBasedOnDifficulty(difficultydamagescaler); ++ } ++ return groupData; ++ } ++ ++ @Override ++ protected void setEquipmentBasedOnDifficulty(DifficultyDamageScaler difficulty) { ++ super.setEquipmentBasedOnDifficulty(difficulty); ++ // TODO make configurable ++ if (random.nextFloat() < (world.getDifficulty() == EnumDifficulty.HARD ? 0.1F : 0.05F)) { ++ setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); ++ } ++ } ++ ++ @Override ++ public float getJumpHeight() { ++ // make giants jump as high as everything else relative to their size ++ // 1.0 makes bottom of feet about as high as their waist when they jump ++ return world.purpurConfig.giantJumpHeight; ++ } ++ // Purpur end ++ + @Override + protected float b(EntityPose entitypose, EntitySize entitysize) { + return 10.440001F; +@@ -18,6 +75,6 @@ public class EntityGiantZombie extends EntityMonster { + + @Override + public float a(BlockPosition blockposition, IWorldReader iworldreader) { +- return iworldreader.y(blockposition) - 0.5F; ++ return super.a(blockposition, iworldreader); // Purpur - fix light requirements for natural spawns + } + } +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index 7582a3a0955db2bc79daeced8e9c869f4276815a..7d9027d881e6e3eb0d1f8478ac7a1501dc5dec1d 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -949,6 +949,7 @@ public abstract class EntityInsentient extends EntityLiving { + return f; + } + ++ protected void setEquipmentBasedOnDifficulty(DifficultyDamageScaler difficultydamagescaler) { a(difficultydamagescaler); } // Purpur - OBFHELPER + protected void a(DifficultyDamageScaler difficultydamagescaler) { + if (this.random.nextFloat() < 0.15F * difficultydamagescaler.d()) { + int i = this.random.nextInt(2); +@@ -1056,6 +1057,7 @@ public abstract class EntityInsentient extends EntityLiving { + } + } + ++ protected void setEnchantmentBasedOnDifficulty(DifficultyDamageScaler difficultydamagescaler) { b(difficultydamagescaler); } // Purpur - OBFHELPER + protected void b(DifficultyDamageScaler difficultydamagescaler) { + float f = difficultydamagescaler.d(); + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 50cb2551598101d1c12007c53421b6c2f59ce2aa..02a49d9ddc58f44471f92cd40b8b968fc7984e73 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -165,6 +165,7 @@ public abstract class EntityLiving extends Entity { + this.activeItem = ItemStack.b; + this.by = Optional.empty(); + this.attributeMap = new AttributeMapBase(AttributeDefaults.a(entitytypes)); ++ this.initAttributes(); // Purpur + this.craftAttributes = new CraftAttributeMap(attributeMap); // CraftBukkit + // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor + this.datawatcher.set(EntityLiving.HEALTH, (float) this.getAttributeInstance(GenericAttributes.MAX_HEALTH).getValue()); +@@ -180,6 +181,8 @@ public abstract class EntityLiving extends Entity { + this.bg = this.a(new Dynamic(dynamicopsnbt, dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), dynamicopsnbt.emptyMap())))); + } + ++ protected void initAttributes() {} // Purpur ++ + public BehaviorController getBehaviorController() { + return this.bg; + } +@@ -2171,7 +2174,7 @@ public abstract class EntityLiving extends Entity { + this.enderTeleportTo(vec3d.x, vec3d.y, vec3d.z); + } + +- protected float dJ() { ++ protected float dJ() { return getJumpHeight(); } public float getJumpHeight() { // Purpur - OBFHELPER + return 0.42F * this.getBlockJumpFactor(); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f403f9dc6cd148b9f9c901f27ce6a77d4225a53f..3207b8b6166bd28796cb9c199c3ab466f7c0a02e 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -113,6 +113,28 @@ public class PurpurWorldConfig { + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + } + ++ public float giantStepHeight = 2.0F; ++ public float giantJumpHeight = 1.0F; ++ public double giantMovementSpeed = 0.5D; ++ public double giantAttackDamage = 50.0D; ++ public boolean giantHaveAI = false; ++ public boolean giantHaveHostileAI = false; ++ public double giantMaxHealth = 100.0D; ++ private void giantSettings() { ++ giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight); ++ giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight); ++ giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed); ++ giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage); ++ giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI); ++ giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI); ++ if (PurpurConfig.version < 8) { ++ double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth); ++ set("mobs.giant.attributes.max-health", oldValue); ++ set("mobs.giant.max-health", null); ++ } ++ giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + private void villagerSettings() { diff --git a/patches/Purpur/patches/server/0027-Illusioners-AI-settings.patch b/patches/Purpur/patches/server/0027-Illusioners-AI-settings.patch new file mode 100644 index 00000000..de8ef59b --- /dev/null +++ b/patches/Purpur/patches/server/0027-Illusioners-AI-settings.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 5 Jul 2019 11:09:25 -0500 +Subject: [PATCH] Illusioners AI settings + + +diff --git a/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java b/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java +index 50442b3a498d1bab4270e69952a79f5182153ece..c57bf5091430709778dc21d70c8a32819c9d6639 100644 +--- a/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java ++++ b/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java +@@ -19,6 +19,15 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan + + } + ++ // Purpur start ++ @Override ++ protected void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.illusionerMovementSpeed); ++ this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(this.world.purpurConfig.illusionerFollowRange); ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.illusionerMaxHealth); ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + super.initPathfinder(); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 3207b8b6166bd28796cb9c199c3ab466f7c0a02e..5c716a6b4f9ff33912ebb274b36eef70679ea87b 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -135,6 +135,20 @@ public class PurpurWorldConfig { + giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); + } + ++ public double illusionerMovementSpeed = 0.5D; ++ public double illusionerFollowRange = 18.0D; ++ public double illusionerMaxHealth = 32.0D; ++ private void illusionerSettings() { ++ illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed); ++ illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); ++ if (PurpurConfig.version < 8) { ++ double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth); ++ set("mobs.illusioner.attributes.max-health", oldValue); ++ set("mobs.illusioner.max-health", null); ++ } ++ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + private void villagerSettings() { diff --git a/patches/Purpur/patches/server/0028-Zombie-horse-naturally-spawn.patch b/patches/Purpur/patches/server/0028-Zombie-horse-naturally-spawn.patch new file mode 100644 index 00000000..2c71b2c8 --- /dev/null +++ b/patches/Purpur/patches/server/0028-Zombie-horse-naturally-spawn.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 7 Jul 2019 19:52:16 -0500 +Subject: [PATCH] Zombie horse naturally spawn + + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 2315c71465e4c1ea00a4c355b43de1e3fb0ca995..2f41a537fb1d8348b5f65a3e85da841761311744 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -996,12 +996,18 @@ public class WorldServer extends World implements GeneratorAccessSeed { + boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper + + if (flag1) { +- EntityHorseSkeleton entityhorseskeleton = (EntityHorseSkeleton) EntityTypes.SKELETON_HORSE.a((World) this); +- +- entityhorseskeleton.t(true); +- entityhorseskeleton.setAgeRaw(0); +- entityhorseskeleton.setPosition((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); +- this.addEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit ++ // Purpur start ++ EntityHorseAbstract horse; ++ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) { ++ horse = EntityTypes.ZOMBIE_HORSE.create(this); ++ } else { ++ horse = EntityTypes.SKELETON_HORSE.create(this); ++ ((EntityHorseSkeleton) horse).setTrap(true); ++ } ++ horse.setAgeRaw(0); ++ horse.setPosition(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ addEntity(horse, CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit ++ // Purpur end + } + + EntityLightning entitylightning = (EntityLightning) EntityTypes.LIGHTNING_BOLT.a((World) this); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 5c716a6b4f9ff33912ebb274b36eef70679ea87b..04dfb820053925c2835bdcec702d5a467a9fe48e 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -155,4 +155,9 @@ public class PurpurWorldConfig { + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); + } ++ ++ public double zombieHorseSpawnChance = 0.0D; ++ private void zombieHorseSettings() { ++ zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); ++ } + } diff --git a/patches/Purpur/patches/server/0029-Charged-creeper-naturally-spawn.patch b/patches/Purpur/patches/server/0029-Charged-creeper-naturally-spawn.patch new file mode 100644 index 00000000..c8ba04ef --- /dev/null +++ b/patches/Purpur/patches/server/0029-Charged-creeper-naturally-spawn.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 29 Nov 2019 22:37:44 -0600 +Subject: [PATCH] Charged creeper naturally spawn + + +diff --git a/src/main/java/net/minecraft/server/EntityCreeper.java b/src/main/java/net/minecraft/server/EntityCreeper.java +index 79ef955070b2982be79cc58e40093624bd088ff0..eb4a0ef0e7a8bc8e0cb648bc369815ce0efb6223 100644 +--- a/src/main/java/net/minecraft/server/EntityCreeper.java ++++ b/src/main/java/net/minecraft/server/EntityCreeper.java +@@ -23,6 +23,17 @@ public class EntityCreeper extends EntityMonster { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @javax.annotation.Nullable GroupDataEntity groupdataentity, @javax.annotation.Nullable NBTTagCompound nbttagcompound) { ++ double chance = worldaccess.getMinecraftWorld().purpurConfig.creeperChargedChance; ++ if (chance > 0D && random.nextDouble() <= chance) { ++ setPowered(true); ++ } ++ return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(1, new PathfinderGoalFloat(this)); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 04dfb820053925c2835bdcec702d5a467a9fe48e..5b8961a061c4a877cfa856f6d9f6d77060060829 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -113,6 +113,11 @@ public class PurpurWorldConfig { + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + } + ++ public double creeperChargedChance = 0.0D; ++ private void creeperSettings() { ++ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); ++ } ++ + public float giantStepHeight = 2.0F; + public float giantJumpHeight = 1.0F; + public double giantMovementSpeed = 0.5D; diff --git a/patches/Purpur/patches/server/0030-Rabbit-naturally-spawn-toast-and-killer.patch b/patches/Purpur/patches/server/0030-Rabbit-naturally-spawn-toast-and-killer.patch new file mode 100644 index 00000000..11dbd070 --- /dev/null +++ b/patches/Purpur/patches/server/0030-Rabbit-naturally-spawn-toast-and-killer.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 31 Aug 2019 17:47:11 -0500 +Subject: [PATCH] Rabbit naturally spawn toast and killer + + +diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java +index 80a4a71753a1fd9f62e75f0b4ea9307ed7072c4f..f17608730fca96af4f9779863a8c25723a3bd5cf 100644 +--- a/src/main/java/net/minecraft/server/EntityRabbit.java ++++ b/src/main/java/net/minecraft/server/EntityRabbit.java +@@ -297,6 +297,10 @@ public class EntityRabbit extends EntityAnimal { + if (!this.hasCustomName()) { + this.setCustomName(new ChatMessage(SystemUtils.a("entity", EntityRabbit.bp))); + } ++ // Purpur start ++ } else if (i == 98) { ++ setCustomName(new ChatMessage("Toast")); ++ // Purpur end + } + + this.datawatcher.set(EntityRabbit.bo, i); +@@ -318,6 +322,16 @@ public class EntityRabbit extends EntityAnimal { + } + + private int a(GeneratorAccess generatoraccess) { ++ // Purpur start ++ World world = generatoraccess.getMinecraftWorld(); ++ if (world.purpurConfig.rabbitNaturalKiller > 0D && random.nextDouble() <= world.purpurConfig.rabbitNaturalKiller) { ++ return 99; ++ } ++ if (world.purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.purpurConfig.rabbitNaturalToast) { ++ return 98; ++ } ++ // Purpur end ++ + BiomeBase biomebase = generatoraccess.getBiome(this.getChunkCoordinates()); + int i = this.random.nextInt(100); + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 5b8961a061c4a877cfa856f6d9f6d77060060829..f51f60110dd6117979b9d9b230065ad21135824c 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -154,6 +154,13 @@ public class PurpurWorldConfig { + illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); + } + ++ public double rabbitNaturalToast = 0.0D; ++ public double rabbitNaturalKiller = 0.0D; ++ private void rabbitSettings() { ++ rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); ++ rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + private void villagerSettings() { diff --git a/patches/Purpur/patches/server/0031-Fix-outdated-server-showing-in-ping-before-server-fu.patch b/patches/Purpur/patches/server/0031-Fix-outdated-server-showing-in-ping-before-server-fu.patch new file mode 100644 index 00000000..b1287fe4 --- /dev/null +++ b/patches/Purpur/patches/server/0031-Fix-outdated-server-showing-in-ping-before-server-fu.patch @@ -0,0 +1,19 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/PacketStatusListener.java b/src/main/java/net/minecraft/server/PacketStatusListener.java +index 9beb6fad228ccb3081b661c845836f4f29404d86..b98d094422f09ddffe09bac7cf1a9c7e92ae5aa6 100644 +--- a/src/main/java/net/minecraft/server/PacketStatusListener.java ++++ b/src/main/java/net/minecraft/server/PacketStatusListener.java +@@ -133,6 +133,7 @@ public class PacketStatusListener implements PacketStatusInListener { + + this.networkManager.sendPacket(new PacketStatusOutServerInfo(ping)); + */ ++ if (this.minecraftServer.getServerPing().getServerData() == null) return; // Purpur - do not respond to pings before we know the protocol version + com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.minecraftServer, this.networkManager); + // Paper end + } diff --git a/patches/Purpur/patches/server/0032-Make-Iron-Golems-Swim.patch b/patches/Purpur/patches/server/0032-Make-Iron-Golems-Swim.patch new file mode 100644 index 00000000..04f7ed16 --- /dev/null +++ b/patches/Purpur/patches/server/0032-Make-Iron-Golems-Swim.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 15 Jun 2019 03:12:15 -0500 +Subject: [PATCH] Make Iron Golems Swim + + +diff --git a/src/main/java/net/minecraft/server/EntityIronGolem.java b/src/main/java/net/minecraft/server/EntityIronGolem.java +index 0a5c27e732540e03c87e6da0fbdc6e3c2e382810..bdff2368836dca230a6622a205d5772834afc6ee 100644 +--- a/src/main/java/net/minecraft/server/EntityIronGolem.java ++++ b/src/main/java/net/minecraft/server/EntityIronGolem.java +@@ -24,6 +24,7 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable { + + @Override + protected void initPathfinder() { ++ if (world.purpurConfig.ironGolemCanSwim) this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, true)); + this.goalSelector.a(2, new PathfinderGoalMoveTowardsTarget(this, 0.9D, 32.0F)); + this.goalSelector.a(2, new PathfinderGoalStrollVillage(this, 0.6D, false)); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f51f60110dd6117979b9d9b230065ad21135824c..3ec33a96396e49d1ddb9f7eea804e4ae8080efbd 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -154,6 +154,11 @@ public class PurpurWorldConfig { + illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); + } + ++ public boolean ironGolemCanSwim = false; ++ private void ironGolemSettings() { ++ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); ++ } ++ + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + private void rabbitSettings() { diff --git a/patches/Purpur/patches/server/0033-Dont-send-useless-entity-packets.patch b/patches/Purpur/patches/server/0033-Dont-send-useless-entity-packets.patch new file mode 100644 index 00000000..858e7ec0 --- /dev/null +++ b/patches/Purpur/patches/server/0033-Dont-send-useless-entity-packets.patch @@ -0,0 +1,78 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java +index f322dccd834ff56b99f8796309709b5b6ac01456..228236bce14bfdf930570b453862dcfaae9e4823 100644 +--- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java +@@ -172,6 +172,7 @@ public class EntityTrackerEntry { + this.o = 0; + packet1 = new PacketPlayOutEntityTeleport(this.tracker); + } ++ if (net.pl3x.purpur.PurpurConfig.dontSendUselessEntityPackets && isUselessPacket(packet1)) packet1 = null; // Purpur + } + + if ((this.e || this.tracker.impulse || this.tracker instanceof EntityLiving && ((EntityLiving) this.tracker).isGliding()) && this.tickCounter > 0) { +@@ -258,6 +259,22 @@ public class EntityTrackerEntry { + + } + ++ // Purpur start ++ private boolean isUselessPacket(Packet possibleUselessPacket) { ++ if (possibleUselessPacket instanceof PacketPlayOutEntity) { ++ PacketPlayOutEntity packet = (PacketPlayOutEntity) possibleUselessPacket; ++ if (possibleUselessPacket instanceof PacketPlayOutEntity.PacketPlayOutRelEntityMove) { ++ return packet.getX() == 0 && packet.getY() == 0 && packet.getZ() == 0; ++ } else if (possibleUselessPacket instanceof PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook) { ++ return packet.getX() == 0 && packet.getY() == 0 && packet.getZ() == 0 && packet.getYaw() == 0 && packet.getPitch() == 0; ++ } else if (possibleUselessPacket instanceof PacketPlayOutEntity.PacketPlayOutEntityLook) { ++ return packet.getYaw() == 0 && packet.getPitch() == 0; ++ } ++ } ++ return false; ++ } ++ // Purpur end ++ + public void a(EntityPlayer entityplayer) { + this.tracker.c(entityplayer); + entityplayer.c(this.tracker); +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutEntity.java b/src/main/java/net/minecraft/server/PacketPlayOutEntity.java +index 8e48407fd405ac4c3eece7762b8155c5d0f00fa0..91c4e658230bb8bbce9d0f56db0768a7c09f0095 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutEntity.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutEntity.java +@@ -5,11 +5,11 @@ import java.io.IOException; + public class PacketPlayOutEntity implements Packet { + + protected int a; +- protected short b; +- protected short c; +- protected short d; +- protected byte e; +- protected byte f; ++ protected short b; public short getX() { return b; } // Purpur - OBFHELPER ++ protected short c; public short getY() { return c; } // Purpur - OBFHELPER ++ protected short d; public short getZ() { return d; } // Purpur - OBFHELPER ++ protected byte e; public byte getYaw() { return e; } // Purpur - OBFHELPER ++ protected byte f; public byte getPitch() { return f; } // Purpur - OBFHELPER + protected boolean g; + protected boolean h; + protected boolean i; +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index ec8efc913f8f0bf6ff559a3a5b439a3d24f3a9b6..a2f3c5c81d93814f89a778c8c58d36b416ea1efc 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -179,6 +179,11 @@ public class PurpurConfig { + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); + } + ++ public static boolean dontSendUselessEntityPackets = false; ++ private static void dontSendUselessEntityPackets() { ++ dontSendUselessEntityPackets = getBoolean("settings.dont-send-useless-entity-packets", dontSendUselessEntityPackets); ++ } ++ + public static boolean loggerSuppressInitLegacyMaterialError = false; + public static boolean loggerSuppressIgnoredAdvancementWarnings = false; + private static void loggerSettings() { diff --git a/patches/Purpur/patches/server/0034-Tulips-change-fox-type.patch b/patches/Purpur/patches/server/0034-Tulips-change-fox-type.patch new file mode 100644 index 00000000..1be43418 --- /dev/null +++ b/patches/Purpur/patches/server/0034-Tulips-change-fox-type.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 13 Jul 2019 15:56:22 -0500 +Subject: [PATCH] Tulips change fox type + + +diff --git a/src/main/java/net/minecraft/server/EntityFox.java b/src/main/java/net/minecraft/server/EntityFox.java +index 56c119e8d11c5ffb1f90ac4249bce434b3e78884..8845afd83d47902d5192ec1a9146b6f20c5667d3 100644 +--- a/src/main/java/net/minecraft/server/EntityFox.java ++++ b/src/main/java/net/minecraft/server/EntityFox.java +@@ -37,9 +37,9 @@ public class EntityFox extends EntityAnimal { + private static final Predicate bv = (entity) -> { + return !entity.bx() && IEntitySelector.e.test(entity); + }; +- private PathfinderGoal bw; +- private PathfinderGoal bx; +- private PathfinderGoal by; ++ private PathfinderGoal bw; private PathfinderGoal attackAnimalGoal() { return bw; } // Purpur - OBFHELPER ++ private PathfinderGoal bx; private PathfinderGoal attackTurtleGoal() { return bx; } // Purpur - OBFHELPER ++ private PathfinderGoal by; private PathfinderGoal attackFishGoal() { return by; } // Purpur - OBFHELPER + private float bz; + private float bA; + private float bB; +@@ -227,6 +227,11 @@ public class EntityFox extends EntityAnimal { + } + + private void initializePathFinderGoals() { ++ // Purpur start - do not add duplicate goals ++ this.targetSelector.a(attackAnimalGoal()); ++ this.targetSelector.a(attackTurtleGoal()); ++ this.targetSelector.a(attackFishGoal()); ++ // Purpur end + if (this.getFoxType() == EntityFox.Type.RED) { + this.targetSelector.a(4, this.bw); + this.targetSelector.a(4, this.bx); +@@ -259,6 +264,7 @@ public class EntityFox extends EntityAnimal { + + public void setFoxType(EntityFox.Type entityfox_type) { + this.datawatcher.set(EntityFox.bo, entityfox_type.b()); ++ initializePathFinderGoals(); // Purpur - fix API bug not updating pathfinders on type change + } + + private List fa() { +@@ -576,6 +582,27 @@ public class EntityFox extends EntityAnimal { + return this.fa().contains(uuid); + } + ++ @Override ++ public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) { ++ if (world.purpurConfig.foxTypeChangesWithTulips) { ++ ItemStack itemstack = entityhuman.b(enumhand); ++ if (getFoxType() == Type.RED && itemstack.getItem() == Items.whiteTulip()) { ++ setFoxType(Type.SNOW); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ return EnumInteractionResult.SUCCESS; ++ } else if (getFoxType() == Type.SNOW && itemstack.getItem() == Items.orangeTulip()) { ++ setFoxType(Type.RED); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ return EnumInteractionResult.SUCCESS; ++ } ++ } ++ return super.b(entityhuman, enumhand); ++ } ++ + @Override + protected org.bukkit.event.entity.EntityDeathEvent d(DamageSource damagesource) { // Paper + ItemStack itemstack = this.getEquipment(EnumItemSlot.MAINHAND).cloneItemStack(); // Paper +diff --git a/src/main/java/net/minecraft/server/Items.java b/src/main/java/net/minecraft/server/Items.java +index 67ebcbe4daa1ef3cef6ca43ec92befbe4156842e..4c379916d8d7797038d2980761c49f44c010dea8 100644 +--- a/src/main/java/net/minecraft/server/Items.java ++++ b/src/main/java/net/minecraft/server/Items.java +@@ -119,8 +119,8 @@ public class Items { + public static final Item bk = a(Blocks.ALLIUM, CreativeModeTab.c); + public static final Item bl = a(Blocks.AZURE_BLUET, CreativeModeTab.c); + public static final Item bm = a(Blocks.RED_TULIP, CreativeModeTab.c); +- public static final Item bn = a(Blocks.ORANGE_TULIP, CreativeModeTab.c); +- public static final Item bo = a(Blocks.WHITE_TULIP, CreativeModeTab.c); ++ public static final Item bn = a(Blocks.ORANGE_TULIP, CreativeModeTab.c); public static Item orangeTulip() { return bn; } // Purpur - OBFHELPER ++ public static final Item bo = a(Blocks.WHITE_TULIP, CreativeModeTab.c); public static Item whiteTulip() { return bo; } // Purpur - OBFHELPER + public static final Item bp = a(Blocks.PINK_TULIP, CreativeModeTab.c); + public static final Item bq = a(Blocks.OXEYE_DAISY, CreativeModeTab.c); + public static final Item br = a(Blocks.CORNFLOWER, CreativeModeTab.c); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 3ec33a96396e49d1ddb9f7eea804e4ae8080efbd..459ff8ffe7c0bf09fe93d89b3433bc930bb52f06 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -118,6 +118,11 @@ public class PurpurWorldConfig { + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + } + ++ public boolean foxTypeChangesWithTulips = false; ++ private void foxSettings() { ++ foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); ++ } ++ + public float giantStepHeight = 2.0F; + public float giantJumpHeight = 1.0F; + public double giantMovementSpeed = 0.5D; diff --git a/patches/Purpur/patches/server/0035-Breedable-Polar-Bears.patch b/patches/Purpur/patches/server/0035-Breedable-Polar-Bears.patch new file mode 100644 index 00000000..f7eff416 --- /dev/null +++ b/patches/Purpur/patches/server/0035-Breedable-Polar-Bears.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Mar 2020 19:46:44 -0500 +Subject: [PATCH] Breedable Polar Bears + + +diff --git a/src/main/java/net/minecraft/server/EntityPolarBear.java b/src/main/java/net/minecraft/server/EntityPolarBear.java +index 914c6e48d60ac8feadc08e52e050899fe1396a8d..99f0bd8f82520778d469ec51745034e6ebd3238a 100644 +--- a/src/main/java/net/minecraft/server/EntityPolarBear.java ++++ b/src/main/java/net/minecraft/server/EntityPolarBear.java +@@ -23,6 +23,30 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean mate(EntityAnimal entityanimal) { ++ if (entityanimal == this) { ++ return false; ++ } else if (this.isStanding()) { ++ return false; ++ } else if (this.getGoalTarget() != null) { ++ return false; ++ } else if (!(entityanimal instanceof EntityPolarBear)) { ++ return false; ++ } else { ++ EntityPolarBear polarbear = (EntityPolarBear) entityanimal; ++ if (polarbear.isStanding()) { ++ return false; ++ } ++ if (polarbear.getGoalTarget() != null) { ++ return false; ++ } ++ return this.isInLove() && polarbear.isInLove(); ++ } ++ } ++ // Purpur end ++ + @Override + public EntityAgeable createChild(WorldServer worldserver, EntityAgeable entityageable) { + return (EntityAgeable) EntityTypes.POLAR_BEAR.a((World) worldserver); +@@ -30,7 +54,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + + @Override + public boolean k(ItemStack itemstack) { +- return false; ++ return world.purpurConfig.polarBearBreedableItem != null && itemstack.getItem() == world.purpurConfig.polarBearBreedableItem; // Purpur; + } + + @Override +@@ -39,6 +63,12 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); + this.goalSelector.a(1, new EntityPolarBear.c()); + this.goalSelector.a(1, new EntityPolarBear.d()); ++ // Purpur start ++ if (world.purpurConfig.polarBearBreedableItem != null) { ++ this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); ++ this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, RecipeItemStack.a(world.purpurConfig.polarBearBreedableItem), false)); ++ } ++ // Purpur end + this.goalSelector.a(4, new PathfinderGoalFollowParent(this, 1.25D)); + this.goalSelector.a(5, new PathfinderGoalRandomStroll(this, 1.0D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); +@@ -180,10 +210,12 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + return flag; + } + ++ public boolean isStanding() { return eM(); } // Purpur - OBFHELPER + public boolean eM() { + return (Boolean) this.datawatcher.get(EntityPolarBear.bo); + } + ++ public void setStanding(boolean standing) { t(standing); } // Purpur - OBFHELPER + public void t(boolean flag) { + this.datawatcher.set(EntityPolarBear.bo, flag); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 459ff8ffe7c0bf09fe93d89b3433bc930bb52f06..09b66b9ca3185528d0bc5f0a1d811c5a42039339 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -164,6 +164,14 @@ public class PurpurWorldConfig { + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + } + ++ public String polarBearBreedableItemString = ""; ++ public Item polarBearBreedableItem = null; ++ private void polarBearSettings() { ++ polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); ++ Item item = IRegistry.ITEM.get(new MinecraftKey(polarBearBreedableItemString)); ++ if (item != Items.AIR) polarBearBreedableItem = item; ++ } ++ + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + private void rabbitSettings() { diff --git a/patches/Purpur/patches/server/0036-Chickens-can-retaliate.patch b/patches/Purpur/patches/server/0036-Chickens-can-retaliate.patch new file mode 100644 index 00000000..853a0507 --- /dev/null +++ b/patches/Purpur/patches/server/0036-Chickens-can-retaliate.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 12 Apr 2020 13:19:34 -0500 +Subject: [PATCH] Chickens can retaliate + + +diff --git a/src/main/java/net/minecraft/server/EntityChicken.java b/src/main/java/net/minecraft/server/EntityChicken.java +index 182469fd39cb23633a3225cf1a64ab6b291e4cdf..8fb5d5c75e79a81ab46af3fbb96ebc41804113c4 100644 +--- a/src/main/java/net/minecraft/server/EntityChicken.java ++++ b/src/main/java/net/minecraft/server/EntityChicken.java +@@ -17,16 +17,33 @@ public class EntityChicken extends EntityAnimal { + this.a(PathType.WATER, 0.0F); + } + ++ // Purpur start ++ @Override ++ protected void initAttributes() { ++ if (world.purpurConfig.chickenRetaliate) { ++ this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(2.0D); ++ } ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); +- this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); ++ //this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); // Purpur - moved down + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); + this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, EntityChicken.bv)); + this.goalSelector.a(4, new PathfinderGoalFollowParent(this, 1.1D)); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this)); ++ // Purpur start ++ if (world.purpurConfig.chickenRetaliate) { ++ this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, false)); ++ this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this)); ++ } else { ++ this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); ++ } ++ // Purpur end + } + + @Override +@@ -35,7 +52,7 @@ public class EntityChicken extends EntityAnimal { + } + + public static AttributeProvider.Builder eK() { +- return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 4.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.25D); ++ return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 4.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.25D).a(GenericAttributes.ATTACK_DAMAGE, 0.0D); // Purpur + } + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 09b66b9ca3185528d0bc5f0a1d811c5a42039339..c2a13de7f0cafc8c93c5255e0f65f5e8ad439c7a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -113,6 +113,11 @@ public class PurpurWorldConfig { + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + } + ++ public boolean chickenRetaliate = false; ++ private void chickenSettings() { ++ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); ++ } ++ + public double creeperChargedChance = 0.0D; + private void creeperSettings() { + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); diff --git a/patches/Purpur/patches/server/0037-Add-option-to-set-armorstand-step-height.patch b/patches/Purpur/patches/server/0037-Add-option-to-set-armorstand-step-height.patch new file mode 100644 index 00000000..069dba0b --- /dev/null +++ b/patches/Purpur/patches/server/0037-Add-option-to-set-armorstand-step-height.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 6 Oct 2019 12:46:35 -0500 +Subject: [PATCH] Add option to set armorstand step height + + +diff --git a/src/main/java/net/minecraft/server/EntityArmorStand.java b/src/main/java/net/minecraft/server/EntityArmorStand.java +index bf932bf0eea4e52603b3b4ec41013df7c930773d..41a36ce6d446b78bdd7a4739ad372a2ee19da116 100644 +--- a/src/main/java/net/minecraft/server/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/server/EntityArmorStand.java +@@ -625,6 +625,7 @@ public class EntityArmorStand extends EntityLiving { + + @Override + public void tick() { ++ setStepHeight(world.purpurConfig.armorstandStepHeight); // Purpur + // Paper start + if (!this.canTick) { + if (this.noTickPoseDirty) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index c2a13de7f0cafc8c93c5255e0f65f5e8ad439c7a..0fa01267fa35f1afdc81c2636f9efa8506bcebb6 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -63,6 +63,11 @@ public class PurpurWorldConfig { + return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); + } + ++ public float armorstandStepHeight = 0.0F; ++ private void armorstandSettings() { ++ armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); ++ } ++ + public boolean idleTimeoutKick = true; + public boolean idleTimeoutTickNearbyEntities = true; + public boolean idleTimeoutCountAsSleeping = false; diff --git a/patches/Purpur/patches/server/0038-Cat-spawning-options.patch b/patches/Purpur/patches/server/0038-Cat-spawning-options.patch new file mode 100644 index 00000000..87be9d47 --- /dev/null +++ b/patches/Purpur/patches/server/0038-Cat-spawning-options.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Dec 2019 18:52:55 -0600 +Subject: [PATCH] Cat spawning options + + +diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java +index 0c98a436021cbdedba5352073b1f8bf9852298eb..08778cc0de9b1ffefc52d12d31403c33c50ddb2f 100644 +--- a/src/main/java/net/minecraft/server/IEntityAccess.java ++++ b/src/main/java/net/minecraft/server/IEntityAccess.java +@@ -47,6 +47,7 @@ public interface IEntityAccess { + } + } + ++ default List getEntitiesInAABB(Class oclass, AxisAlignedBB axisalignedbb) { return a(oclass, axisalignedbb); } // Purpur - OBFHELPER + default List a(Class oclass, AxisAlignedBB axisalignedbb) { + return this.a(oclass, axisalignedbb, IEntitySelector.g); + } +diff --git a/src/main/java/net/minecraft/server/MobSpawnerCat.java b/src/main/java/net/minecraft/server/MobSpawnerCat.java +index 5e17868a76ea8e3f105c11d496d6da12afa0da41..5a0f8779672a9e34f6970045361630ab5af3990b 100644 +--- a/src/main/java/net/minecraft/server/MobSpawnerCat.java ++++ b/src/main/java/net/minecraft/server/MobSpawnerCat.java +@@ -16,7 +16,7 @@ public class MobSpawnerCat implements MobSpawner { + if (this.a > 0) { + return 0; + } else { +- this.a = 1200; ++ this.a = worldserver.purpurConfig.catSpawnDelay; // Purpur; + EntityPlayer entityplayer = worldserver.q_(); + + if (entityplayer == null) { +@@ -50,10 +50,12 @@ public class MobSpawnerCat implements MobSpawner { + } + + private int a(WorldServer worldserver, BlockPosition blockposition) { +- boolean flag = true; +- +- if (worldserver.y().a(VillagePlaceType.r.c(), blockposition, 48, VillagePlace.Occupancy.IS_OCCUPIED) > 4L) { +- List list = worldserver.a(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(48.0D, 8.0D, 48.0D)); ++ // Purpur start ++ int range = worldserver.purpurConfig.catSpawnVillageScanRange; ++ if (range <= 0) return 0; ++ if (worldserver.getPoiStorage().count(VillagePlaceType.home().predicate(), blockposition, range, VillagePlace.Occupancy.IS_OCCUPIED) > 4L) { ++ List list = worldserver.getEntitiesInAABB(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(range, 8.0D, range)); ++ // Purpur end + + if (list.size() < 5) { + return this.a(blockposition, worldserver); +@@ -64,9 +66,11 @@ public class MobSpawnerCat implements MobSpawner { + } + + private int b(WorldServer worldserver, BlockPosition blockposition) { +- boolean flag = true; +- List list = worldserver.a(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(16.0D, 8.0D, 16.0D)); +- ++ // Purpur start ++ int range = worldserver.purpurConfig.catSpawnSwampHutScanRange; ++ if (range <= 0) return 0; ++ List list = worldserver.getEntitiesInAABB(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(range, 8.0D, range)); ++ // Purpur end + return list.size() < 1 ? this.a(blockposition, worldserver) : 0; + } + +diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java +index fce9967912628c232fe41ccc17fe2296f001ec61..44063d599e39e087b3eccfe204ef2fd79c3364e5 100644 +--- a/src/main/java/net/minecraft/server/VillagePlace.java ++++ b/src/main/java/net/minecraft/server/VillagePlace.java +@@ -178,6 +178,7 @@ public class VillagePlace extends RegionFileSection { + ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition); + } + ++ public long count(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { return a(predicate, blockposition, i, villageplace_occupancy); } // Purpur - OBFHELPER + public long a(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { + return this.c(predicate, blockposition, i, villageplace_occupancy).count(); + } +diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java +index a5718af9b614ae505067131f04ebb490617d6aa4..b6b4c8c491d692f93d2c38d602ff99b0611b72aa 100644 +--- a/src/main/java/net/minecraft/server/VillagePlaceType.java ++++ b/src/main/java/net/minecraft/server/VillagePlaceType.java +@@ -44,7 +44,7 @@ public class VillagePlaceType { + public static final VillagePlaceType o = a("shepherd", a(Blocks.LOOM), 1, 1); + public static final VillagePlaceType p = a("toolsmith", a(Blocks.SMITHING_TABLE), 1, 1); + public static final VillagePlaceType q = a("weaponsmith", a(Blocks.GRINDSTONE), 1, 1); +- public static final VillagePlaceType r = a("home", VillagePlaceType.z, 1, 1); ++ public static final VillagePlaceType r = a("home", VillagePlaceType.z, 1, 1); public static VillagePlaceType home() { return r; } // Purpur - OBFHELPER + public static final VillagePlaceType s = a("meeting", a(Blocks.BELL), 32, 6); + public static final VillagePlaceType t = a("beehive", a(Blocks.BEEHIVE), 0, 1); + public static final VillagePlaceType u = a("bee_nest", a(Blocks.BEE_NEST), 0, 1); +@@ -83,6 +83,7 @@ public class VillagePlaceType { + return this.D; + } + ++ public Predicate predicate() { return c(); } // Purpur - OBFHELPER + public Predicate c() { + return this.E; + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 0fa01267fa35f1afdc81c2636f9efa8506bcebb6..9c5de125a6ed207f238dff1e17e38a4083c4c251 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -118,6 +118,15 @@ public class PurpurWorldConfig { + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + } + ++ public int catSpawnDelay = 1200; ++ public int catSpawnSwampHutScanRange = 16; ++ public int catSpawnVillageScanRange = 48; ++ private void catSettings() { ++ catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); ++ catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); ++ catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); ++ } ++ + public boolean chickenRetaliate = false; + private void chickenSettings() { + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); diff --git a/patches/Purpur/patches/server/0039-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch b/patches/Purpur/patches/server/0039-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch new file mode 100644 index 00000000..3355e6be --- /dev/null +++ b/patches/Purpur/patches/server/0039-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch @@ -0,0 +1,21 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/MobSpawnerCat.java b/src/main/java/net/minecraft/server/MobSpawnerCat.java +index 5a0f8779672a9e34f6970045361630ab5af3990b..25bb494350cd771166a78a7b14726cee8f01b02e 100644 +--- a/src/main/java/net/minecraft/server/MobSpawnerCat.java ++++ b/src/main/java/net/minecraft/server/MobSpawnerCat.java +@@ -80,8 +80,9 @@ public class MobSpawnerCat implements MobSpawner { + if (entitycat == null) { + return 0; + } else { ++ entitycat.setPositionRotation(blockposition, 0.0F, 0.0F); // Purpur + entitycat.prepare(worldserver, worldserver.getDamageScaler(blockposition), EnumMobSpawn.NATURAL, (GroupDataEntity) null, (NBTTagCompound) null); +- entitycat.setPositionRotation(blockposition, 0.0F, 0.0F); ++ //entitycat.setPositionRotation(blockposition, 0.0F, 0.0F); // Purpur - move up - fixes non black cat types spawning inside swamp huts + worldserver.addAllEntities(entitycat); + return 1; + } diff --git a/patches/Purpur/patches/server/0040-Cows-eat-mushrooms.patch b/patches/Purpur/patches/server/0040-Cows-eat-mushrooms.patch new file mode 100644 index 00000000..d77f9afd --- /dev/null +++ b/patches/Purpur/patches/server/0040-Cows-eat-mushrooms.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 4 May 2019 01:10:30 -0500 +Subject: [PATCH] Cows eat mushrooms + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 39036105b51dddbce8e4986e3be226f31fb13051..4ed4f31c8e34279f9aa9fd7bbddbb36239ea36ef 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -2778,6 +2778,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.invulnerable = flag; + } + ++ public void copyPositionRotation(Entity entity) { this.u(entity); } // Purpur - OBFHELPER + public void u(Entity entity) { + this.setPositionRotation(entity.locX(), entity.locY(), entity.locZ(), entity.yaw, entity.pitch); + } +diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java +index 42e6761c8b18b79ffd3f4d5e853ea87a2c153c23..cfb009c811bd2908d38da1b0007cb7aaed4e42c3 100644 +--- a/src/main/java/net/minecraft/server/EntityCow.java ++++ b/src/main/java/net/minecraft/server/EntityCow.java +@@ -16,6 +16,7 @@ public class EntityCow extends EntityAnimal { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); + this.goalSelector.a(1, new PathfinderGoalPanic(this, 2.0D)); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); ++ if (world.purpurConfig.cowFeedMushrooms > 0) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.25D, RecipeItemStack.a(Items.WHEAT, Blocks.RED_MUSHROOM.getItem(), Blocks.BROWN_MUSHROOM.getItem()), false)); else // Purpur + this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.25D, RecipeItemStack.a(Items.WHEAT), false)); + this.goalSelector.a(4, new PathfinderGoalFollowParent(this, 1.25D)); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D)); +@@ -70,11 +71,80 @@ public class EntityCow extends EntityAnimal { + + entityhuman.a(enumhand, itemstack1); + return EnumInteractionResult.a(this.world.isClientSide); ++ // Purpur start - feed mushroom to change to mooshroom ++ } else if (world.purpurConfig.cowFeedMushrooms > 0 && getEntityType() != EntityTypes.MOOSHROOM && isMushroom(itemstack)) { ++ return feedMushroom(entityhuman, itemstack); ++ // Purpur end + } else { + return super.b(entityhuman, enumhand); + } + } + ++ // Purpur start - feed mushroom to change to mooshroom ++ private int redMushroomsFed = 0; ++ private int brownMushroomsFed = 0; ++ ++ private boolean isMushroom(ItemStack itemstack) { ++ return itemstack.getItem() == Blocks.RED_MUSHROOM.getItem() || itemstack.getItem() == Blocks.BROWN_MUSHROOM.getItem(); ++ } ++ ++ private int incrementFeedCount(ItemStack itemstack) { ++ if (itemstack.getItem() == Blocks.RED_MUSHROOM.getItem()) { ++ return ++redMushroomsFed; ++ } else { ++ return ++brownMushroomsFed; ++ } ++ } ++ ++ private EnumInteractionResult feedMushroom(EntityHuman entityhuman, ItemStack itemstack) { ++ world.broadcastEntityEffect(this, (byte) 18); // hearts ++ playSound(SoundEffects.ENTITY_COW_MILK, 1.0F, 1.0F); ++ if (incrementFeedCount(itemstack) < world.purpurConfig.cowFeedMushrooms) { ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ return EnumInteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping) ++ } ++ EntityMushroomCow mooshroom = EntityTypes.MOOSHROOM.create(world); ++ if (mooshroom == null) { ++ return EnumInteractionResult.PASS; ++ } ++ if (itemstack.getItem() == Blocks.BROWN_MUSHROOM.getItem()) { ++ mooshroom.setVariant(EntityMushroomCow.Type.BROWN); ++ } else { ++ mooshroom.setVariant(EntityMushroomCow.Type.RED); ++ } ++ mooshroom.setPositionRotation(this.locX(), this.locY(), this.locZ(), this.yaw, this.pitch); ++ mooshroom.setHealth(this.getHealth()); ++ mooshroom.setAge(getAge()); ++ mooshroom.copyPositionRotation(this); ++ mooshroom.setRenderYawOffset(this.getRenderYawOffset()); ++ mooshroom.setHeadRotation(this.getHeadRotation()); ++ mooshroom.lastYaw = this.lastYaw; ++ mooshroom.lastPitch = this.lastPitch; ++ if (this.hasCustomName()) { ++ mooshroom.setCustomName(this.getCustomName()); ++ } ++ if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { ++ return EnumInteractionResult.PASS; ++ } ++ if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) { ++ return EnumInteractionResult.PASS; ++ } ++ this.world.addEntity(mooshroom); ++ this.die(); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ for (int i = 0; i < 15; ++i) { ++ ((WorldServer) world).sendParticles(((WorldServer) world).players, null, Particles.HAPPY_VILLAGER, ++ locX() + random.nextFloat(), locY() + (random.nextFloat() * 2), locZ() + random.nextFloat(), 1, ++ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); ++ } ++ return EnumInteractionResult.SUCCESS; ++ } ++ // Purpur end ++ + @Override + public EntityCow createChild(WorldServer worldserver, EntityAgeable entityageable) { + return (EntityCow) EntityTypes.COW.a((World) worldserver); +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 02a49d9ddc58f44471f92cd40b8b968fc7984e73..65a54b6bf113ca6e88929e23c5d5cbfc6cfc7bad 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -80,7 +80,7 @@ public abstract class EntityLiving extends Entity { + public int maxNoDamageTicks; + public final float ay; + public final float az; +- public float aA; ++ public float aA; public float getRenderYawOffset() { return this.aA; } public void setRenderYawOffset(float f) { this.aA = f; } // Purpur - OBFHELPER + public float aB; + public float aC; + public float aD; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 9c5de125a6ed207f238dff1e17e38a4083c4c251..0c841b824a93d5e43bad171d1ca828eca3e891df 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -132,6 +132,11 @@ public class PurpurWorldConfig { + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); + } + ++ public int cowFeedMushrooms = 0; ++ private void cowSettings() { ++ cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); ++ } ++ + public double creeperChargedChance = 0.0D; + private void creeperSettings() { + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); diff --git a/patches/Purpur/patches/server/0041-Fix-cow-rotation-when-shearing-mooshroom.patch b/patches/Purpur/patches/server/0041-Fix-cow-rotation-when-shearing-mooshroom.patch new file mode 100644 index 00000000..d0e14361 --- /dev/null +++ b/patches/Purpur/patches/server/0041-Fix-cow-rotation-when-shearing-mooshroom.patch @@ -0,0 +1,25 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/EntityMushroomCow.java b/src/main/java/net/minecraft/server/EntityMushroomCow.java +index 1aacbae04ce89be4da82d65116c494c493e71530..38df17bd206c908582ece2c4105235feaf0f2227 100644 +--- a/src/main/java/net/minecraft/server/EntityMushroomCow.java ++++ b/src/main/java/net/minecraft/server/EntityMushroomCow.java +@@ -135,7 +135,13 @@ public class EntityMushroomCow extends EntityCow implements IShearable { + + entitycow.setPositionRotation(this.locX(), this.locY(), this.locZ(), this.yaw, this.pitch); + entitycow.setHealth(this.getHealth()); +- entitycow.aA = this.aA; ++ // Purpur start - correctly copy rotation ++ entitycow.copyPositionRotation(this); ++ entitycow.setRenderYawOffset(this.getRenderYawOffset()); ++ entitycow.setHeadRotation(this.getHeadRotation()); ++ entitycow.lastYaw = this.lastYaw; ++ entitycow.lastPitch = this.lastPitch; ++ // Purpur end + if (this.hasCustomName()) { + entitycow.setCustomName(this.getCustomName()); + entitycow.setCustomNameVisible(this.getCustomNameVisible()); diff --git a/patches/Purpur/patches/server/0042-Pigs-give-saddle-back.patch b/patches/Purpur/patches/server/0042-Pigs-give-saddle-back.patch new file mode 100644 index 00000000..06028b58 --- /dev/null +++ b/patches/Purpur/patches/server/0042-Pigs-give-saddle-back.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 12 May 2019 01:14:46 -0500 +Subject: [PATCH] Pigs give saddle back + + +diff --git a/src/main/java/net/minecraft/server/EntityPig.java b/src/main/java/net/minecraft/server/EntityPig.java +index ee94c2827cfc53f7a37e61d8c1c0c30a52374cf8..7f52c39234e69b612b89993ce4503c20690064ed 100644 +--- a/src/main/java/net/minecraft/server/EntityPig.java ++++ b/src/main/java/net/minecraft/server/EntityPig.java +@@ -109,6 +109,18 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable { + + if (!flag && this.hasSaddle() && !this.isVehicle() && !entityhuman.eq()) { + if (!this.world.isClientSide) { ++ // Purpur start ++ if (world.purpurConfig.pigGiveSaddleBack && entityhuman.isSneaking()) { ++ this.saddleStorage.setSaddle(false); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ ItemStack saddle = new ItemStack(Items.SADDLE); ++ if (!entityhuman.inventory.pickup(saddle)) { ++ entityhuman.drop(saddle, false); ++ } ++ } ++ return EnumInteractionResult.SUCCESS; ++ } ++ // Purpur end + entityhuman.startRiding(this); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 0c841b824a93d5e43bad171d1ca828eca3e891df..24ef91788ff69004eed1b136a0ceb6176db00e76 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -188,6 +188,11 @@ public class PurpurWorldConfig { + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + } + ++ public boolean pigGiveSaddleBack = false; ++ private void pigSettings() { ++ pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); ++ } ++ + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; + private void polarBearSettings() { diff --git a/patches/Purpur/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch b/patches/Purpur/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch new file mode 100644 index 00000000..7180b193 --- /dev/null +++ b/patches/Purpur/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 May 2019 23:58:44 -0500 +Subject: [PATCH] Snowman drop and put back pumpkin + + +diff --git a/src/main/java/net/minecraft/server/EntitySnowman.java b/src/main/java/net/minecraft/server/EntitySnowman.java +index 48997367a67ffd1dbf29cdb048720610528f35cb..95ee716fc9b79b5fcb8508118b3876c51f3f6987 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowman.java ++++ b/src/main/java/net/minecraft/server/EntitySnowman.java +@@ -7,6 +7,7 @@ import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class EntitySnowman extends EntityGolem implements IShearable, IRangedEntity { + ++ private static final RecipeItemStack PUMPKIN = RecipeItemStack.a(Blocks.PUMPKIN.getItem(), Blocks.CARVED_PUMPKIN.getItem(), Blocks.JACK_O_LANTERN.getItem()); // Purpur + private static final DataWatcherObject b = DataWatcher.a(EntitySnowman.class, DataWatcherRegistry.a); + + public EntitySnowman(EntityTypes entitytypes, World world) { +@@ -120,9 +121,25 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + itemstack.damage(1, entityhuman, (entityhuman1) -> { + entityhuman1.broadcastItemBreak(enumhand); + }); ++ // Purpur start ++ if (world.purpurConfig.snowGolemDropsPumpkin) { ++ EntityItem pumpkin = new EntityItem(world, locX(), locY(), locZ(), new ItemStack(Blocks.CARVED_PUMPKIN.getItem())); ++ pumpkin.pickupDelay = 10; ++ world.addEntity(pumpkin); ++ return EnumInteractionResult.SUCCESS; ++ } ++ // Purpur end + } + + return EnumInteractionResult.a(this.world.isClientSide); ++ // Purpur start ++ } else if (world.purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && PUMPKIN.test(itemstack)) { ++ setHasPumpkin(true); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ itemstack.subtract(1); ++ } ++ return EnumInteractionResult.SUCCESS; ++ // Purpur end + } else { + return EnumInteractionResult.PASS; + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 24ef91788ff69004eed1b136a0ceb6176db00e76..6d93ff4b08582f0f3ea1bb4b582aaa881651d34d 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -208,6 +208,13 @@ public class PurpurWorldConfig { + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + } + ++ public boolean snowGolemDropsPumpkin = false; ++ public boolean snowGolemPutPumpkinBack = false; ++ private void snowGolemSettings() { ++ snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); ++ snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + private void villagerSettings() { diff --git a/patches/Purpur/patches/server/0044-Ender-dragon-always-drop-egg.patch b/patches/Purpur/patches/server/0044-Ender-dragon-always-drop-egg.patch new file mode 100644 index 00000000..7a0bbbf8 --- /dev/null +++ b/patches/Purpur/patches/server/0044-Ender-dragon-always-drop-egg.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 7 Feb 2020 04:42:57 -0600 +Subject: [PATCH] Ender dragon always drop egg + + +diff --git a/src/main/java/net/minecraft/server/EnderDragonBattle.java b/src/main/java/net/minecraft/server/EnderDragonBattle.java +index 38dc6086d18951e065d4048d1d8eee288c5c5fd1..f853f6c424da77c40ee3d5b5dc2279ba8918977c 100644 +--- a/src/main/java/net/minecraft/server/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/server/EnderDragonBattle.java +@@ -358,7 +358,7 @@ public class EnderDragonBattle { + this.bossBattle.setVisible(false); + this.generateExitPortal(true); + this.n(); +- if (!this.previouslyKilled) { ++ if (this.world.purpurConfig.enderDragonAlwaysDropsEggBlock || !this.previouslyKilled) { // Purpur - always place dragon egg + this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 6d93ff4b08582f0f3ea1bb4b582aaa881651d34d..bafead429f2ca19033a63241ff24b16f597f7678 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -142,6 +142,11 @@ public class PurpurWorldConfig { + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + } + ++ public boolean enderDragonAlwaysDropsEggBlock = false; ++ private void enderDragonSettings() { ++ enderDragonAlwaysDropsEggBlock = getBoolean("mobs.ender_dragon.always-drop-egg-block", enderDragonAlwaysDropsEggBlock); ++ } ++ + public boolean foxTypeChangesWithTulips = false; + private void foxSettings() { + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); diff --git a/patches/Purpur/patches/server/0045-Ender-dragon-always-drop-full-exp.patch b/patches/Purpur/patches/server/0045-Ender-dragon-always-drop-full-exp.patch new file mode 100644 index 00000000..d619217b --- /dev/null +++ b/patches/Purpur/patches/server/0045-Ender-dragon-always-drop-full-exp.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 24 Aug 2019 14:42:54 -0500 +Subject: [PATCH] Ender dragon always drop full exp + + +diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java +index de53af8c01f8fa9636386737b6bc071b32f87cd1..a5a2182455542bb8fd62941bd0da2f38ba698f35 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java +@@ -564,7 +564,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + boolean flag = this.world.getGameRules().getBoolean(GameRules.DO_MOB_LOOT); + short short0 = 500; + +- if (this.bF != null && !this.bF.isPreviouslyKilled()) { ++ if (getEnderDragonBattle() != null && (world.purpurConfig.enderDragonAlwaysDropsFullExp || !getEnderDragonBattle().isPreviouslyKilled())) { // Purpur + short0 = 12000; + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index bafead429f2ca19033a63241ff24b16f597f7678..1623e65adf402269748cc06e8f2a42dcf411babb 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -143,8 +143,10 @@ public class PurpurWorldConfig { + } + + public boolean enderDragonAlwaysDropsEggBlock = false; ++ public boolean enderDragonAlwaysDropsFullExp = false; + private void enderDragonSettings() { + enderDragonAlwaysDropsEggBlock = getBoolean("mobs.ender_dragon.always-drop-egg-block", enderDragonAlwaysDropsEggBlock); ++ enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); + } + + public boolean foxTypeChangesWithTulips = false; diff --git a/patches/Purpur/patches/server/0046-Signs-editable-on-right-click.patch b/patches/Purpur/patches/server/0046-Signs-editable-on-right-click.patch new file mode 100644 index 00000000..ec87ae51 --- /dev/null +++ b/patches/Purpur/patches/server/0046-Signs-editable-on-right-click.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 24 May 2019 02:39:25 -0500 +Subject: [PATCH] Signs editable on right click + + +diff --git a/src/main/java/net/minecraft/server/BlockSign.java b/src/main/java/net/minecraft/server/BlockSign.java +index 3585fd82c14338a82302ca403e91e6cfe65e1c19..770ad5edfb44acb69e7da33226ca6bf7c01a77b7 100644 +--- a/src/main/java/net/minecraft/server/BlockSign.java ++++ b/src/main/java/net/minecraft/server/BlockSign.java +@@ -56,6 +56,17 @@ public abstract class BlockSign extends BlockTileEntity implements IBlockWaterlo + } + } + ++ // Purpur start - right click to open sign editor ++ if (world.purpurConfig.signRightClickEdit && itemstack.getItem() instanceof ItemSign && ++ !entityhuman.isSneaking() && entityhuman.abilities.mayBuild && ++ entityhuman.getBukkitEntity().hasPermission("purpur.sign.edit")) { ++ tileentitysign.isEditable = true; ++ tileentitysign.a(entityhuman); ++ entityhuman.openSign(tileentitysign); ++ return EnumInteractionResult.SUCCESS; ++ } ++ // Purpur end ++ + return tileentitysign.b(entityhuman) ? EnumInteractionResult.SUCCESS : EnumInteractionResult.PASS; + } else { + return EnumInteractionResult.PASS; +diff --git a/src/main/java/net/minecraft/server/TileEntitySign.java b/src/main/java/net/minecraft/server/TileEntitySign.java +index 2b9d5724c1b63f5e55010f9e3450004821c098a4..316766970243e8ac00e4c82f6c710de5edc3fa51 100644 +--- a/src/main/java/net/minecraft/server/TileEntitySign.java ++++ b/src/main/java/net/minecraft/server/TileEntitySign.java +@@ -113,6 +113,7 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + return this.isEditable; + } + ++ public void setEditor(EntityHuman entityhuman) { a(entityhuman); } // Purpur - OBFHELPER + public void a(EntityHuman entityhuman) { + // Paper start + //this.c = entityhuman; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 1623e65adf402269748cc06e8f2a42dcf411babb..02587d8609b8aa8d2a48c38372f5c245878fe15e 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -109,6 +109,11 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean signRightClickEdit = false; ++ private void signSettings() { ++ signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); ++ } ++ + public boolean turtleEggsBreakFromExpOrbs = true; + public boolean turtleEggsBreakFromItems = true; + public boolean turtleEggsBreakFromMinecarts = true; diff --git a/patches/Purpur/patches/server/0047-Signs-allow-color-codes.patch b/patches/Purpur/patches/server/0047-Signs-allow-color-codes.patch new file mode 100644 index 00000000..4f23bb7d --- /dev/null +++ b/patches/Purpur/patches/server/0047-Signs-allow-color-codes.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 6 Jun 2019 17:40:30 -0500 +Subject: [PATCH] Signs allow color codes + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 6e292d91d509327fa5b9dea947811d04e78e1e93..46e06a5cf6e7fda62fe84f53db35f143e8f5d676 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1429,6 +1429,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + @Override + public void openSign(TileEntitySign tileentitysign) { ++ if (world.purpurConfig.signAllowColors) this.playerConnection.sendPacket(tileentitysign.getTranslatedUpdatePacket()); // Purpur + tileentitysign.a((EntityHuman) this); + this.playerConnection.sendPacket(new PacketPlayOutOpenSignEditor(tileentitysign.getPosition())); + } +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index fdd517290a0b306dff9c0fffadc424b59b8504f3..3fca11948fad36b6adec55883d365e58a17925fa 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -2879,6 +2879,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } + // Paper end ++ // Purpur start ++ if (worldserver.purpurConfig.signAllowColors) { ++ lines[i] = currentLine; ++ if (player.hasPermission("purpur.sign.color")) lines[i] = lines[i].replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); ++ if (player.hasPermission("purpur.sign.style")) lines[i] = lines[i].replaceAll("(?i)&([l-or])", "\u00a7$1"); ++ if (player.hasPermission("purpur.sign.magic")) lines[i] = lines[i].replaceAll("(?i)&([kr])", "\u00a7$1"); ++ } else ++ // Purpur end + lines[i] = SharedConstants.filterAllowedChatCharacters(currentLine); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created. + } + SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(x, y, z), this.server.getPlayer(this.player), lines); +diff --git a/src/main/java/net/minecraft/server/TileEntitySign.java b/src/main/java/net/minecraft/server/TileEntitySign.java +index 316766970243e8ac00e4c82f6c710de5edc3fa51..852bb5db84ddd735f5e0d7f922918cc90b897e91 100644 +--- a/src/main/java/net/minecraft/server/TileEntitySign.java ++++ b/src/main/java/net/minecraft/server/TileEntitySign.java +@@ -93,6 +93,18 @@ public class TileEntitySign extends TileEntity implements ICommandListener { // + this.g[i] = null; + } + ++ // Purpur start ++ public PacketPlayOutTileEntityData getTranslatedUpdatePacket() { ++ NBTTagCompound nbt = save(new NBTTagCompound()); ++ for (int i = 0; i < 4; ++i) { ++ String line = net.pl3x.purpur.ComponentUtil.fromComponent(lines[i]).replace("\u00a7", "&"); ++ nbt.setString("Text" + (i + 1), IChatBaseComponent.ChatSerializer.componentToJson(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(line)[0])); ++ } ++ nbt.setString("PurpurEditor", "true"); ++ return new PacketPlayOutTileEntityData(position, 9, nbt); ++ } ++ // Purpur end ++ + @Nullable + @Override + public PacketPlayOutTileEntityData getUpdatePacket() { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 02587d8609b8aa8d2a48c38372f5c245878fe15e..77ab4cbbb08618fa72b264ebfb5f7211eec9af42 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -109,8 +109,10 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean signAllowColors = false; + public boolean signRightClickEdit = false; + private void signSettings() { ++ signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors); + signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); + } + diff --git a/patches/Purpur/patches/server/0048-Allow-soil-to-moisten-from-water-directly-under-it.patch b/patches/Purpur/patches/server/0048-Allow-soil-to-moisten-from-water-directly-under-it.patch new file mode 100644 index 00000000..60860dbb --- /dev/null +++ b/patches/Purpur/patches/server/0048-Allow-soil-to-moisten-from-water-directly-under-it.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 21 Jun 2019 14:37:10 -0500 +Subject: [PATCH] Allow soil to moisten from water directly under it + + +diff --git a/src/main/java/net/minecraft/server/BlockSoil.java b/src/main/java/net/minecraft/server/BlockSoil.java +index a315e2628c35ee713b68741c6e52c4b140c05f27..8dd48669c29dd51ed4d535dad0b0319f4bb2250c 100644 +--- a/src/main/java/net/minecraft/server/BlockSoil.java ++++ b/src/main/java/net/minecraft/server/BlockSoil.java +@@ -116,19 +116,14 @@ public class BlockSoil extends Block { + } + + private static boolean a(IWorldReader iworldreader, BlockPosition blockposition) { +- Iterator iterator = BlockPosition.a(blockposition.b(-4, 0, -4), blockposition.b(4, 1, 4)).iterator(); +- +- BlockPosition blockposition1; +- +- do { +- if (!iterator.hasNext()) { +- return false; ++ // Purpur start ++ for (BlockPosition position : BlockPosition.a(blockposition.b(-4, 0, -4), blockposition.b(4, 1, 4))) { ++ if (iworldreader.getFluid(position).a(TagsFluid.WATER)) { ++ return true; + } +- +- blockposition1 = (BlockPosition) iterator.next(); +- } while (!iworldreader.getFluid(blockposition1).a((Tag) TagsFluid.WATER)); +- +- return true; ++ } ++ return ((WorldServer) iworldreader).purpurConfig.farmlandGetsMoistFromBelow && iworldreader.getFluid(blockposition.shift(EnumDirection.DOWN)).a(TagsFluid.WATER); ++ // Purpur end + } + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 77ab4cbbb08618fa72b264ebfb5f7211eec9af42..ce7b3775d81135a785599d1866a8fe88f897eff7 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -109,6 +109,11 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean farmlandGetsMoistFromBelow = false; ++ private void farmlandSettings() { ++ farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); ++ } ++ + public boolean signAllowColors = false; + public boolean signRightClickEdit = false; + private void signSettings() { diff --git a/patches/Purpur/patches/server/0049-Controllable-Minecarts.patch b/patches/Purpur/patches/server/0049-Controllable-Minecarts.patch new file mode 100644 index 00000000..69ae58d1 --- /dev/null +++ b/patches/Purpur/patches/server/0049-Controllable-Minecarts.patch @@ -0,0 +1,202 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 29 Jun 2019 02:32:40 -0500 +Subject: [PATCH] Controllable Minecarts + + +diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java +index 2291135eaef64c403183724cb6e413cd7e472672..bc61aaff65a7dc1e7534452b285953b83adb7000 100644 +--- a/src/main/java/net/minecraft/server/BlockPosition.java ++++ b/src/main/java/net/minecraft/server/BlockPosition.java +@@ -36,6 +36,12 @@ public class BlockPosition extends BaseBlockPosition { + private static final int m = 38; + // Paper end + ++ // Purpur start ++ public BlockPosition(Entity entity) { ++ super(entity.locX(), entity.locY(), entity.locZ()); ++ } ++ // Purpur end ++ + public BlockPosition(int i, int j, int k) { + super(i, j, k); + } +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 65a54b6bf113ca6e88929e23c5d5cbfc6cfc7bad..4bd1322892e1c46addd795254d9ae6d3e7dc1e5b 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -98,9 +98,9 @@ public abstract class EntityLiving extends Entity { + protected int aO;protected int getKillCount() { return this.aO; } // Paper - OBFHELPER + public float lastDamage; + public boolean jumping; // Paper protected -> public +- public float aR; +- public float aS; +- public float aT; ++ public float aR; public float getStrafe() { return aR; } public void setStrafe(float strafe) { aR = strafe; } // Purpur - OBFHELPER ++ public float aS; public float getVertical() { return aS; } public void setVertical(float vertical) { aS = vertical; } // Purpur - OBFHELPER ++ public float aT; public float getForward() { return aT; } public void setForward(float forward) { aT = forward; } // Purpur - OBFHELPER + protected int aU; + protected double aV; + protected double aW; +diff --git a/src/main/java/net/minecraft/server/EntityMinecartAbstract.java b/src/main/java/net/minecraft/server/EntityMinecartAbstract.java +index 022dfdc5b6af4b243e7e4da8660e8e41d04e1a30..298af30b1a7f12d42216fc1b7ee801fd7be93d3c 100644 +--- a/src/main/java/net/minecraft/server/EntityMinecartAbstract.java ++++ b/src/main/java/net/minecraft/server/EntityMinecartAbstract.java +@@ -445,16 +445,62 @@ public abstract class EntityMinecartAbstract extends Entity { + + public void a(int i, int j, int k, boolean flag) {} + ++ // Purpur start ++ private Double lastSpeed; ++ ++ public double getControllableSpeed() { ++ BlockPosition position = new BlockPosition(this); ++ Block block = world.getType(position).getBlock(); ++ if (!block.material.isSolid()) { ++ block = world.getType(position.shift(EnumDirection.DOWN)).getBlock(); ++ } ++ Double speed = world.purpurConfig.controllableMinecartsBlockSpeeds.get(block); ++ if (!block.material.isSolid()) { ++ speed = lastSpeed; ++ } else if (speed == null) { ++ speed = world.purpurConfig.controllableMinecartsBaseSpeed; ++ } ++ return lastSpeed = speed; ++ } ++ // Purpur end ++ + protected void h() { + double d0 = this.getMaxSpeed(); + Vec3D vec3d = this.getMot(); + + this.setMot(MathHelper.a(vec3d.x, -d0, d0), vec3d.y, MathHelper.a(vec3d.z, -d0, d0)); ++ ++ // Purpur start ++ if (world.purpurConfig.controllableMinecarts && !isInWater() && !isInLava() && !passengers.isEmpty()) { ++ Entity passenger = passengers.get(0); ++ if (passenger instanceof EntityHuman) { ++ EntityHuman entityhuman = (EntityHuman) passenger; ++ if (entityhuman.jumping && this.onGround) { ++ setMot(new Vec3D(getMot().x, world.purpurConfig.controllableMinecartsHopBoost, getMot().z)); ++ } ++ if (entityhuman.getForward() != 0.0F) { ++ Vector velocity = entityhuman.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed()); ++ if (entityhuman.getForward() < 0.0) { ++ velocity.multiply(-0.5); ++ } ++ setMot(new Vec3D(velocity.getX(), getMot().y, velocity.getZ())); ++ } ++ this.yaw = passenger.yaw - 90; ++ setStepHeight(world.purpurConfig.controllableMinecartsStepHeight); ++ } else { ++ setStepHeight(0.0F); ++ } ++ } else { ++ setStepHeight(0.0F); ++ } ++ // Purpur end ++ + if (this.onGround) { + // CraftBukkit start - replace magic numbers with our variables + this.setMot(new Vec3D(this.getMot().x * this.derailedX, this.getMot().y * this.derailedY, this.getMot().z * this.derailedZ)); + // CraftBukkit end + } ++ else if (world.purpurConfig.controllableMinecarts) setMot(new Vec3D(getMot().x * derailedX, getMot().y, getMot().z * derailedZ)); // Purpur + + this.move(EnumMoveType.SELF, this.getMot()); + if (!this.onGround) { +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 46e06a5cf6e7fda62fe84f53db35f143e8f5d676..e0a2057b3e9915092f0d973d512821448aa30d18 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1004,6 +1004,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + if (this.isInvulnerable(damagesource)) { + return false; + } else { ++ if (damagesource == DamageSource.FALL && getRootVehicle() instanceof EntityMinecartAbstract && world.purpurConfig.controllableMinecarts && !world.purpurConfig.controllableMinecartsFallDamage) return false; // Purpur + boolean flag = this.server.j() && this.canPvP() && "fall".equals(damagesource.translationIndex); + + if (!flag && isSpawnInvulnerable() && damagesource != DamageSource.OUT_OF_WORLD) { // Purpur +diff --git a/src/main/java/net/minecraft/server/ItemMinecart.java b/src/main/java/net/minecraft/server/ItemMinecart.java +index ceef7aaf923026ff1044d6feba4297279eb44157..002651aaf3b8a9b489fe323756cd1ad13f9874e8 100644 +--- a/src/main/java/net/minecraft/server/ItemMinecart.java ++++ b/src/main/java/net/minecraft/server/ItemMinecart.java +@@ -103,8 +103,10 @@ public class ItemMinecart extends Item { + IBlockData iblockdata = world.getType(blockposition); + + if (!iblockdata.a((Tag) TagsBlock.RAILS)) { +- return EnumInteractionResult.FAIL; +- } else { ++ // Purpur start - place minecarts anywhere ++ if (!world.purpurConfig.controllableMinecartsPlaceAnywhere) return EnumInteractionResult.FAIL; ++ if (iblockdata.getMaterial().isSolid()) blockposition = blockposition.shift(itemactioncontext.getClickedFace()); ++ } //else { // Purpur end - place minecarts anywhere + ItemStack itemstack = itemactioncontext.getItemStack(); + + if (!world.isClientSide) { +@@ -131,6 +133,6 @@ public class ItemMinecart extends Item { + + itemstack.subtract(1); + return EnumInteractionResult.a(world.isClientSide); +- } ++ //} // Purpur - place minecarts anywhere + } + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index ce7b3775d81135a785599d1866a8fe88f897eff7..7bb80ab8fde39f39a0834e4ed2d3002e73885737 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1,5 +1,7 @@ + package net.pl3x.purpur; + ++import net.minecraft.server.Block; ++import net.minecraft.server.Blocks; + import net.minecraft.server.IRegistry; + import net.minecraft.server.Item; + import net.minecraft.server.Items; +@@ -7,7 +9,10 @@ import net.minecraft.server.MinecraftKey; + import org.bukkit.configuration.ConfigurationSection; + + import java.util.ArrayList; ++import java.util.HashMap; + import java.util.List; ++import java.util.Map; ++ + import static net.pl3x.purpur.PurpurConfig.log; + + public class PurpurWorldConfig { +@@ -68,6 +73,34 @@ public class PurpurWorldConfig { + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); + } + ++ public boolean controllableMinecarts = false; ++ public boolean controllableMinecartsPlaceAnywhere = false; ++ public float controllableMinecartsStepHeight = 1.0F; ++ public double controllableMinecartsHopBoost = 0.5D; ++ public boolean controllableMinecartsFallDamage = true; ++ public double controllableMinecartsBaseSpeed = 0.1D; ++ public Map controllableMinecartsBlockSpeeds = new HashMap<>(); ++ private void controllableMinecartsSettings() { ++ controllableMinecarts = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", controllableMinecarts); ++ controllableMinecartsPlaceAnywhere = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", controllableMinecartsPlaceAnywhere); ++ controllableMinecartsStepHeight = (float) getDouble("gameplay-mechanics.controllable-minecarts.step-height", controllableMinecartsStepHeight); ++ controllableMinecartsHopBoost = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", controllableMinecartsHopBoost); ++ controllableMinecartsFallDamage = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", controllableMinecartsFallDamage); ++ controllableMinecartsBaseSpeed = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", controllableMinecartsBaseSpeed); ++ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed"); ++ if (section != null) { ++ for (String key : section.getKeys(false)) { ++ Block block = IRegistry.BLOCK.get(new MinecraftKey(key)); ++ if (block != Blocks.AIR) { ++ controllableMinecartsBlockSpeeds.put(block, section.getDouble(key, controllableMinecartsBaseSpeed)); ++ } ++ } ++ } else { ++ set("gameplay-mechanics.controllable-minecarts.block-speed.grass-block", 0.3D); ++ set("gameplay-mechanics.controllable-minecarts.block-speed.stone", 0.5D); ++ } ++ } ++ + public boolean idleTimeoutKick = true; + public boolean idleTimeoutTickNearbyEntities = true; + public boolean idleTimeoutCountAsSleeping = false; diff --git a/patches/Purpur/patches/server/0050-Disable-loot-drops-on-death-by-cramming.patch b/patches/Purpur/patches/server/0050-Disable-loot-drops-on-death-by-cramming.patch new file mode 100644 index 00000000..52250e03 --- /dev/null +++ b/patches/Purpur/patches/server/0050-Disable-loot-drops-on-death-by-cramming.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 3 Jul 2019 23:58:31 -0500 +Subject: [PATCH] Disable loot drops on death by cramming + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 4bd1322892e1c46addd795254d9ae6d3e7dc1e5b..bef80c83c551abe2cc23cf73e6c737313ad399c9 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -1493,8 +1493,10 @@ public abstract class EntityLiving extends Entity { + + this.dropInventory(); // CraftBukkit - from below + if (this.cW() && this.world.getGameRules().getBoolean(GameRules.DO_MOB_LOOT)) { ++ if (!(damagesource == DamageSource.CRAMMING && world.purpurConfig.disableDropsOnCrammingDeath)) { // Purpur + this.a(damagesource, flag); + this.dropDeathLoot(damagesource, i, flag); ++ } // Purpur + } + // CraftBukkit start - Call death event + org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 7bb80ab8fde39f39a0834e4ed2d3002e73885737..dde6877f175d6a80d86f78ce25aa4626cdd6bbe5 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -112,6 +112,11 @@ public class PurpurWorldConfig { + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + } + ++ public boolean disableDropsOnCrammingDeath = false; ++ private void miscGameplayMechanicsSettings() { ++ disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); ++ } ++ + public int playerSpawnInvulnerableTicks = 60; + public boolean playerInvulnerableWhileAcceptingResourcePack = false; + private void playerInvulnerabilities() { diff --git a/patches/Purpur/patches/server/0051-Players-should-not-cram-to-death.patch b/patches/Purpur/patches/server/0051-Players-should-not-cram-to-death.patch new file mode 100644 index 00000000..689abc1f --- /dev/null +++ b/patches/Purpur/patches/server/0051-Players-should-not-cram-to-death.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 21 Jul 2019 18:01:46 -0500 +Subject: [PATCH] Players should not cram to death + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index e0a2057b3e9915092f0d973d512821448aa30d18..5c81b2ad016325b1b04b7da489c413ee2e5128d2 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1406,7 +1406,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + @Override + public boolean isInvulnerable(DamageSource damagesource) { +- return super.isInvulnerable(damagesource) || this.H() || this.abilities.isInvulnerable && damagesource == DamageSource.WITHER; ++ return super.isInvulnerable(damagesource) || this.H() || damagesource == DamageSource.CRAMMING || this.abilities.isInvulnerable && damagesource == DamageSource.WITHER; // Purpur + } + + @Override diff --git a/patches/Purpur/patches/server/0052-Option-to-toggle-milk-curing-bad-omen.patch b/patches/Purpur/patches/server/0052-Option-to-toggle-milk-curing-bad-omen.patch new file mode 100644 index 00000000..f6fb8f4a --- /dev/null +++ b/patches/Purpur/patches/server/0052-Option-to-toggle-milk-curing-bad-omen.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 10 Jul 2019 20:43:05 -0500 +Subject: [PATCH] Option to toggle milk curing bad omen + + +diff --git a/src/main/java/net/minecraft/server/ItemMilkBucket.java b/src/main/java/net/minecraft/server/ItemMilkBucket.java +index fcf254bbaeb3c0ffdb6834a8d5ad2c3cf4235e5b..79554e18fc39ecd9db87618a59d2e6709049820e 100644 +--- a/src/main/java/net/minecraft/server/ItemMilkBucket.java ++++ b/src/main/java/net/minecraft/server/ItemMilkBucket.java +@@ -20,7 +20,9 @@ public class ItemMilkBucket extends Item { + } + + if (!world.isClientSide) { ++ MobEffect badOmen = entityliving.getEffect(MobEffects.BAD_OMEN); // Purpur + entityliving.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit ++ if (!world.purpurConfig.milkCuresBadOmen && badOmen != null) entityliving.addEffect(badOmen); // Purpur + } + + return itemstack.isEmpty() ? new ItemStack(Items.BUCKET) : itemstack; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index dde6877f175d6a80d86f78ce25aa4626cdd6bbe5..c60a3d7d9057e665023e464f62a3c2ea9ccd8b41 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -113,8 +113,10 @@ public class PurpurWorldConfig { + } + + public boolean disableDropsOnCrammingDeath = false; ++ public boolean milkCuresBadOmen = true; + private void miscGameplayMechanicsSettings() { + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); ++ milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + } + + public int playerSpawnInvulnerableTicks = 60; diff --git a/patches/Purpur/patches/server/0053-End-gateway-should-check-if-entity-can-use-portal.patch b/patches/Purpur/patches/server/0053-End-gateway-should-check-if-entity-can-use-portal.patch new file mode 100644 index 00000000..aeb737ba --- /dev/null +++ b/patches/Purpur/patches/server/0053-End-gateway-should-check-if-entity-can-use-portal.patch @@ -0,0 +1,18 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/TileEntityEndGateway.java b/src/main/java/net/minecraft/server/TileEntityEndGateway.java +index e0118a971e1ea3c52a1380f519146b8f46a425ea..ed8e91bf6c8b9d410d439bdddd5067d346a20a7e 100644 +--- a/src/main/java/net/minecraft/server/TileEntityEndGateway.java ++++ b/src/main/java/net/minecraft/server/TileEntityEndGateway.java +@@ -127,6 +127,7 @@ public class TileEntityEndGateway extends TileEntityEnderPortal implements ITick + + public void b(Entity entity) { + if (this.world instanceof WorldServer && !this.f()) { ++ if (!entity.canPortal()) return; // Purpur + this.c = 100; + if (this.exitPortal == null && this.world.getTypeKey() == DimensionManager.THE_END) { // CraftBukkit - work in alternate worlds + this.a((WorldServer) this.world); diff --git a/patches/Purpur/patches/server/0054-Fix-the-dead-lagging-the-server.patch b/patches/Purpur/patches/server/0054-Fix-the-dead-lagging-the-server.patch new file mode 100644 index 00000000..66709837 --- /dev/null +++ b/patches/Purpur/patches/server/0054-Fix-the-dead-lagging-the-server.patch @@ -0,0 +1,31 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 4ed4f31c8e34279f9aa9fd7bbddbb36239ea36ef..f18941c7c740959181b728ab9da06c7e9d97aa79 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -1523,6 +1523,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.pitch = MathHelper.a(f1, -90.0F, 90.0F) % 360.0F; + this.lastYaw = this.yaw; + this.lastPitch = this.pitch; ++ if (valid && !dead) world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit // Paper // Purpur + } + + public void f(double d0, double d1, double d2) { +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index bef80c83c551abe2cc23cf73e6c737313ad399c9..9b1ecf6101e146843b4d2eeabc3e0e3c4fde54b5 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -2492,7 +2492,7 @@ public abstract class EntityLiving extends Entity { + } + } + +- this.movementTick(); ++ if (!dead) this.movementTick(); // Purpur + double d0 = this.locX() - this.lastX; + double d1 = this.locZ() - this.lastZ; + float f = (float) (d0 * d0 + d1 * d1); diff --git a/patches/Purpur/patches/server/0055-Skip-events-if-there-s-no-listeners.patch b/patches/Purpur/patches/server/0055-Skip-events-if-there-s-no-listeners.patch new file mode 100644 index 00000000..0ac08c76 --- /dev/null +++ b/patches/Purpur/patches/server/0055-Skip-events-if-there-s-no-listeners.patch @@ -0,0 +1,26 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java +index e56ff365118c50486f36cb15a4ca062c5a481674..17753c8a997aa286460be5d8eb6508e2eaed18ce 100644 +--- a/src/main/java/net/minecraft/server/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/server/CommandDispatcher.java +@@ -278,6 +278,7 @@ public class CommandDispatcher { + } + + private void runSync(EntityPlayer entityplayer, 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(entityplayer.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + PlayerCommandSendEvent event = new PlayerCommandSendEvent(entityplayer.getBukkitEntity(), new LinkedHashSet<>(bukkit)); +@@ -290,6 +291,7 @@ public class CommandDispatcher { + } + } + // CraftBukkit end ++ } // Purpur - skip event + entityplayer.playerConnection.sendPacket(new PacketPlayOutCommands(rootcommandnode)); + } + diff --git a/patches/Purpur/patches/server/0056-Add-permission-for-F3-N-debug.patch b/patches/Purpur/patches/server/0056-Add-permission-for-F3-N-debug.patch new file mode 100644 index 00000000..89cb0bf3 --- /dev/null +++ b/patches/Purpur/patches/server/0056-Add-permission-for-F3-N-debug.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 28 Dec 2019 04:21:54 -0600 +Subject: [PATCH] Add permission for F3+N debug + + +diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java +index 5b0fdcf5190e4ab2af249a5a0952b66d52f08751..3c19e931ad7d5330f1c77ef65aaa5858a001e4df 100644 +--- a/src/main/java/net/minecraft/server/PlayerList.java ++++ b/src/main/java/net/minecraft/server/PlayerList.java +@@ -1073,6 +1073,7 @@ public abstract class PlayerList { + } else { + b0 = (byte) (24 + i); + } ++ if (b0 < 28 && entityplayer.getBukkitEntity().hasPermission("purpur.debug.f3n")) b0 = 28; // Purpur + + entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityStatus(entityplayer, b0)); + } diff --git a/patches/Purpur/patches/server/0057-Add-wither-skeleton-takes-wither-damage-option.patch b/patches/Purpur/patches/server/0057-Add-wither-skeleton-takes-wither-damage-option.patch new file mode 100644 index 00000000..1786f3d7 --- /dev/null +++ b/patches/Purpur/patches/server/0057-Add-wither-skeleton-takes-wither-damage-option.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 14 Jan 2020 19:43:40 -0600 +Subject: [PATCH] Add wither skeleton takes wither damage option + + +diff --git a/src/main/java/net/minecraft/server/EntitySkeletonWither.java b/src/main/java/net/minecraft/server/EntitySkeletonWither.java +index 1ae9910fefece1a3fe6410b27642da6edd8f296d..c872be77a6cd767520d5412b38ec4ed4fa87ac2f 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeletonWither.java ++++ b/src/main/java/net/minecraft/server/EntitySkeletonWither.java +@@ -97,6 +97,6 @@ public class EntitySkeletonWither extends EntitySkeletonAbstract { + + @Override + public boolean d(MobEffect mobeffect) { +- return mobeffect.getMobEffect() == MobEffects.WITHER ? false : super.d(mobeffect); ++ return (world.purpurConfig.witherSkeletonTakesWitherDamage || mobeffect.getMobEffect() != MobEffects.WITHER) && super.d(mobeffect); // Purpur + } + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index c60a3d7d9057e665023e464f62a3c2ea9ccd8b41..0db9da068391a381b0a594dc15ae2182dc0f12d9 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -281,6 +281,11 @@ public class PurpurWorldConfig { + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); + } + ++ public boolean witherSkeletonTakesWitherDamage = false; ++ private void witherSkeletonSettings() { ++ witherSkeletonTakesWitherDamage = getBoolean("mobs.wither_skeleton.takes-wither-damage", witherSkeletonTakesWitherDamage); ++ } ++ + public double zombieHorseSpawnChance = 0.0D; + private void zombieHorseSettings() { + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); diff --git a/patches/Purpur/patches/server/0058-Configurable-TPS-Catchup.patch b/patches/Purpur/patches/server/0058-Configurable-TPS-Catchup.patch new file mode 100644 index 00000000..4594fac6 --- /dev/null +++ b/patches/Purpur/patches/server/0058-Configurable-TPS-Catchup.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Mar 2020 19:06:22 -0500 +Subject: [PATCH] Configurable TPS Catchup + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d79cf993cdadfe7fadd6c7e65b9fc691a298c702..942f556eca32ce13c6b3490ef0d6f9e960a36d06 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1002,7 +1002,13 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Thu, 19 Mar 2020 19:39:34 -0500 +Subject: [PATCH] Add option to allow loyalty on tridents to work in the void + + +diff --git a/src/main/java/net/minecraft/server/EntityThrownTrident.java b/src/main/java/net/minecraft/server/EntityThrownTrident.java +index 8026a55d462a646d6b39db21d52ba0871dfbc4d3..2acbe7437df71fc27580deddee8eb7655a297b70 100644 +--- a/src/main/java/net/minecraft/server/EntityThrownTrident.java ++++ b/src/main/java/net/minecraft/server/EntityThrownTrident.java +@@ -38,7 +38,7 @@ public class EntityThrownTrident extends EntityArrow { + + Entity entity = this.getShooter(); + +- if ((this.ai || this.t()) && entity != null) { ++ if ((this.ai || this.t() || (world.purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && locY() < world.purpurConfig.tridentLoyaltyVoidReturnHeight)) && entity != null) { // Purpur + byte b0 = (Byte) this.datawatcher.get(EntityThrownTrident.g); + + if (b0 > 0 && !this.z()) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 0db9da068391a381b0a594dc15ae2182dc0f12d9..fd111dae73f07a787edf38c2de41063c305d7a7d 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -114,9 +114,11 @@ public class PurpurWorldConfig { + + public boolean disableDropsOnCrammingDeath = false; + public boolean milkCuresBadOmen = true; ++ public double tridentLoyaltyVoidReturnHeight = 0.0D; + private void miscGameplayMechanicsSettings() { + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); ++ tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + } + + public int playerSpawnInvulnerableTicks = 60; diff --git a/patches/Purpur/patches/server/0060-Add-enderman-and-creeper-griefing-controls.patch b/patches/Purpur/patches/server/0060-Add-enderman-and-creeper-griefing-controls.patch new file mode 100644 index 00000000..a55cc21f --- /dev/null +++ b/patches/Purpur/patches/server/0060-Add-enderman-and-creeper-griefing-controls.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 26 Apr 2020 16:28:38 -0500 +Subject: [PATCH] Add enderman and creeper griefing controls + + +diff --git a/src/main/java/net/minecraft/server/EntityCreeper.java b/src/main/java/net/minecraft/server/EntityCreeper.java +index eb4a0ef0e7a8bc8e0cb648bc369815ce0efb6223..2256b81624b12b6f6cb54250b24fa12ad6da621d 100644 +--- a/src/main/java/net/minecraft/server/EntityCreeper.java ++++ b/src/main/java/net/minecraft/server/EntityCreeper.java +@@ -215,7 +215,7 @@ public class EntityCreeper extends EntityMonster { + + public void explode() { + if (!this.world.isClientSide) { +- Explosion.Effect explosion_effect = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? Explosion.Effect.DESTROY : Explosion.Effect.NONE; ++ Explosion.Effect explosion_effect = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) && world.purpurConfig.creeperAllowGriefing ? Explosion.Effect.DESTROY : Explosion.Effect.NONE; // Purpur + float f = this.isPowered() ? 2.0F : 1.0F; + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java +index f53525eb32a6096ae24fd23756b2169d5d39e9d4..7b175240e44b0c7eb5044d7bcaf54dac22f50f2a 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderman.java ++++ b/src/main/java/net/minecraft/server/EntityEnderman.java +@@ -369,6 +369,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + @Override + public boolean a() { ++ if (!enderman.world.purpurConfig.endermanAllowGriefing) return false; // Purpur + return this.enderman.getCarried() != null ? false : (!this.enderman.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.enderman.getRandom().nextInt(20) == 0); + } + +@@ -402,7 +403,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + static class PathfinderGoalEndermanPlaceBlock extends PathfinderGoal { + +- private final EntityEnderman a; ++ private final EntityEnderman a; public EntityEnderman getEnderman() { return a; } // Purpur - OBFHELPER + + public PathfinderGoalEndermanPlaceBlock(EntityEnderman entityenderman) { + this.a = entityenderman; +@@ -410,6 +411,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + @Override + public boolean a() { ++ if (!getEnderman().world.purpurConfig.endermanAllowGriefing) return false; // Purpur + return this.a.getCarried() == null ? false : (!this.a.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.a.getRandom().nextInt(2000) == 0); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index fd111dae73f07a787edf38c2de41063c305d7a7d..d0bb71e450026f830e5f52dce7ecf1d4c1011fb8 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -191,8 +191,10 @@ public class PurpurWorldConfig { + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); + } + ++ public boolean creeperAllowGriefing = true; + public double creeperChargedChance = 0.0D; + private void creeperSettings() { ++ creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + } + +@@ -203,6 +205,11 @@ public class PurpurWorldConfig { + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); + } + ++ public boolean endermanAllowGriefing = true; ++ private void endermanSettings() { ++ endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); ++ } ++ + public boolean foxTypeChangesWithTulips = false; + private void foxSettings() { + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); diff --git a/patches/Purpur/patches/server/0061-Entities-pick-up-loot-bypass-mob-griefing-gamerule.patch b/patches/Purpur/patches/server/0061-Entities-pick-up-loot-bypass-mob-griefing-gamerule.patch new file mode 100644 index 00000000..154a967e --- /dev/null +++ b/patches/Purpur/patches/server/0061-Entities-pick-up-loot-bypass-mob-griefing-gamerule.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 24 Apr 2020 09:33:11 -0500 +Subject: [PATCH] Entities pick up loot bypass mob-griefing gamerule + + +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index 7d9027d881e6e3eb0d1f8478ac7a1501dc5dec1d..72d07fd593dd9459b3ce1c238a02f3839d2e097b 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -545,7 +545,7 @@ public abstract class EntityInsentient extends EntityLiving { + public void movementTick() { + super.movementTick(); + this.world.getMethodProfiler().enter("looting"); +- if (!this.world.isClientSide && this.canPickupLoot() && this.isAlive() && !this.killed && this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (!this.world.isClientSide && this.canPickupLoot() && this.isAlive() && !this.killed && (this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) || world.purpurConfig.entitiesPickUpLootBypassMobGriefing)) { // Purpur + List list = this.world.a(EntityItem.class, this.getBoundingBox().grow(1.0D, 0.0D, 1.0D)); + Iterator iterator = list.iterator(); + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d0bb71e450026f830e5f52dce7ecf1d4c1011fb8..8677230d4bf721f7a24ffa56d6306a1adfb65e60 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -113,10 +113,12 @@ public class PurpurWorldConfig { + } + + public boolean disableDropsOnCrammingDeath = false; ++ public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean milkCuresBadOmen = true; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + private void miscGameplayMechanicsSettings() { + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); ++ entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + } diff --git a/patches/Purpur/patches/server/0062-Villagers-farming-can-bypass-mob-griefing-gamerule.patch b/patches/Purpur/patches/server/0062-Villagers-farming-can-bypass-mob-griefing-gamerule.patch new file mode 100644 index 00000000..a7d6767f --- /dev/null +++ b/patches/Purpur/patches/server/0062-Villagers-farming-can-bypass-mob-griefing-gamerule.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 24 Apr 2020 09:37:29 -0500 +Subject: [PATCH] Villagers farming can bypass mob-griefing gamerule + + +diff --git a/src/main/java/net/minecraft/server/BehaviorFarm.java b/src/main/java/net/minecraft/server/BehaviorFarm.java +index 54a555509e3d83e9749609dc35897ad151bca681..0ff202c0d77681f7e0d55d57c69dd0e455336eba 100644 +--- a/src/main/java/net/minecraft/server/BehaviorFarm.java ++++ b/src/main/java/net/minecraft/server/BehaviorFarm.java +@@ -18,7 +18,7 @@ public class BehaviorFarm extends Behavior { + } + + protected boolean a(WorldServer worldserver, EntityVillager entityvillager) { +- if (!worldserver.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (!worldserver.getGameRules().getBoolean(GameRules.MOB_GRIEFING) && !worldserver.purpurConfig.villagerFarmingBypassMobGriefing) { // Purpur + return false; + } else if (entityvillager.getVillagerData().getProfession() != VillagerProfession.FARMER) { + return false; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 8677230d4bf721f7a24ffa56d6306a1adfb65e60..79109fd94ba90ae43ce39d858b5d06a5f769a333 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -287,9 +287,11 @@ public class PurpurWorldConfig { + + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; ++ public boolean villagerFarmingBypassMobGriefing = false; + private void villagerSettings() { + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); ++ villagerFarmingBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerFarmingBypassMobGriefing); + } + + public boolean witherSkeletonTakesWitherDamage = false; diff --git a/patches/Purpur/patches/server/0063-Villagers-follow-emerald-blocks.patch b/patches/Purpur/patches/server/0063-Villagers-follow-emerald-blocks.patch new file mode 100644 index 00000000..bc50d271 --- /dev/null +++ b/patches/Purpur/patches/server/0063-Villagers-follow-emerald-blocks.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 29 Nov 2019 22:10:12 -0600 +Subject: [PATCH] Villagers follow emerald blocks + + +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index c034869310ca3dadbfe5425c45aaa80dac59ac88..1cde71b812c7721298e7addb74de01e4ea297499 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -74,6 +74,13 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + this.brainTickOffset = getRandom().nextInt(100); // Purpur + } + ++ // Purpur start ++ @Override ++ protected void initPathfinder() { ++ if (world.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS)); ++ } ++ // Purpur end ++ + @Override + public BehaviorController getBehaviorController() { + return (BehaviorController) super.getBehaviorController(); // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/server/EntityVillagerAbstract.java b/src/main/java/net/minecraft/server/EntityVillagerAbstract.java +index b2a76db173ae12bff2e8a7de411cb489fdb2e9c7..2a5eda6a7e67d0b8682a96552248ea4e117c1196 100644 +--- a/src/main/java/net/minecraft/server/EntityVillagerAbstract.java ++++ b/src/main/java/net/minecraft/server/EntityVillagerAbstract.java +@@ -17,6 +17,8 @@ import io.papermc.paper.event.player.PlayerTradeEvent; + + public abstract class EntityVillagerAbstract extends EntityAgeable implements NPC, IMerchant { + ++ static final RecipeItemStack TEMPT_ITEMS = RecipeItemStack.a(Blocks.EMERALD_BLOCK.getItem()); // Purpur ++ + // CraftBukkit start + private CraftMerchant craftMerchant; + +diff --git a/src/main/java/net/minecraft/server/EntityVillagerTrader.java b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +index f1a509063c09e603140c74255a3fb901693d2cc5..74c2d89af516ffc252032d5cbd12b489ea46813e 100644 +--- a/src/main/java/net/minecraft/server/EntityVillagerTrader.java ++++ b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +@@ -40,6 +40,7 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + this.goalSelector.a(1, new PathfinderGoalPanic(this, 0.5D)); + this.goalSelector.a(1, new PathfinderGoalLookAtTradingPlayer(this)); + this.goalSelector.a(2, new EntityVillagerTrader.a(this, 2.0D, 0.35D)); ++ if (world.purpurConfig.villagerTraderFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS)); // Purpur + this.goalSelector.a(4, new PathfinderGoalMoveTowardsRestriction(this, 0.35D)); + this.goalSelector.a(8, new PathfinderGoalRandomStrollLand(this, 0.35D)); + this.goalSelector.a(9, new PathfinderGoalInteract(this, EntityHuman.class, 3.0F, 1.0F)); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 79109fd94ba90ae43ce39d858b5d06a5f769a333..7b3df3fa454164bd13bbadd80d2900edeb4fc974 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -288,10 +288,17 @@ public class PurpurWorldConfig { + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + public boolean villagerFarmingBypassMobGriefing = false; ++ public boolean villagerFollowEmeraldBlock = false; + private void villagerSettings() { + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); + villagerFarmingBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerFarmingBypassMobGriefing); ++ villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); ++ } ++ ++ public boolean villagerTraderFollowEmeraldBlock = false; ++ private void villagerTraderSettings() { ++ villagerTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", villagerTraderFollowEmeraldBlock); + } + + public boolean witherSkeletonTakesWitherDamage = false; diff --git a/patches/Purpur/patches/server/0064-Allow-leashing-villagers.patch b/patches/Purpur/patches/server/0064-Allow-leashing-villagers.patch new file mode 100644 index 00000000..62cafa09 --- /dev/null +++ b/patches/Purpur/patches/server/0064-Allow-leashing-villagers.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 3 Oct 2019 18:08:03 -0500 +Subject: [PATCH] Allow leashing villagers + + +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index 72d07fd593dd9459b3ce1c238a02f3839d2e097b..de891e39c02ac6fbb87044d31b3eeabac1e78865 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -1146,6 +1146,7 @@ public abstract class EntityInsentient extends EntityLiving { + if (!this.isAlive()) { + return EnumInteractionResult.PASS; + } else if (this.getLeashHolder() == entityhuman) { ++ if (enumhand == EnumHand.OFF_HAND && (world.purpurConfig.villagerCanBeLeashed || world.purpurConfig.villagerTraderCanBeLeashed) && this instanceof EntityVillagerAbstract) return EnumInteractionResult.CONSUME; // Purpur + // CraftBukkit start - fire PlayerUnleashEntityEvent + if (CraftEventFactory.callPlayerUnleashEntityEvent(this, entityhuman).isCancelled()) { + ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutAttachEntity(this, this.getLeashHolder())); +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index 1cde71b812c7721298e7addb74de01e4ea297499..e4aedb3df5d0a47b5bb9175627aa794fc1779639 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -79,6 +79,11 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + protected void initPathfinder() { + if (world.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS)); + } ++ ++ @Override ++ public boolean a(EntityHuman entityhuman) { ++ return world.purpurConfig.villagerCanBeLeashed && !this.isLeashed(); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityVillagerTrader.java b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +index 74c2d89af516ffc252032d5cbd12b489ea46813e..96dda6a14fd17509e9bcb72cc7e9c8532c6a036b 100644 +--- a/src/main/java/net/minecraft/server/EntityVillagerTrader.java ++++ b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +@@ -47,6 +47,13 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); + } + ++ // Purpur - start ++ @Override ++ public boolean a(EntityHuman entityhuman) { ++ return world.purpurConfig.villagerTraderCanBeLeashed && !this.isLeashed(); ++ } ++ // Purpur - end ++ + @Nullable + @Override + public EntityAgeable createChild(WorldServer worldserver, EntityAgeable entityageable) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 7b3df3fa454164bd13bbadd80d2900edeb4fc974..585e4aef1a85924655165670aa408ca368eef864 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -287,17 +287,21 @@ public class PurpurWorldConfig { + + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; ++ public boolean villagerCanBeLeashed = false; + public boolean villagerFarmingBypassMobGriefing = false; + public boolean villagerFollowEmeraldBlock = false; + private void villagerSettings() { + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); ++ villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); + villagerFarmingBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerFarmingBypassMobGriefing); + villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); + } + ++ public boolean villagerTraderCanBeLeashed = false; + public boolean villagerTraderFollowEmeraldBlock = false; + private void villagerTraderSettings() { ++ villagerTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", villagerTraderCanBeLeashed); + villagerTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", villagerTraderFollowEmeraldBlock); + } + diff --git a/patches/Purpur/patches/server/0065-Implement-configurable-search-radius-for-villagers-t.patch b/patches/Purpur/patches/server/0065-Implement-configurable-search-radius-for-villagers-t.patch new file mode 100644 index 00000000..7c05b49f --- /dev/null +++ b/patches/Purpur/patches/server/0065-Implement-configurable-search-radius-for-villagers-t.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 22 Jul 2019 17:32:17 -0500 +Subject: [PATCH] Implement configurable search radius for villagers to spawn + iron golems + + +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index e4aedb3df5d0a47b5bb9175627aa794fc1779639..c06425434c97645b914c07940528901a2979ce1b 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -894,6 +894,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + + @Nullable + private EntityIronGolem d(WorldServer worldserver) { ++ if (world.purpurConfig.villagerSpawnIronGolemRadius > 0 && world.a(EntityIronGolem.class, getBoundingBox().grow(world.purpurConfig.villagerSpawnIronGolemRadius)).size() > world.purpurConfig.villagerSpawnIronGolemLimit) return null; // Purpur + BlockPosition blockposition = this.getChunkCoordinates(); + + for (int i = 0; i < 10; ++i) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 585e4aef1a85924655165670aa408ca368eef864..db6fb004be99341dba16c184244ed1a04c386fd4 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -290,12 +290,16 @@ public class PurpurWorldConfig { + public boolean villagerCanBeLeashed = false; + public boolean villagerFarmingBypassMobGriefing = false; + public boolean villagerFollowEmeraldBlock = false; ++ public int villagerSpawnIronGolemRadius = 0; ++ public int villagerSpawnIronGolemLimit = 0; + private void villagerSettings() { + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); + villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); + villagerFarmingBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerFarmingBypassMobGriefing); + villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); ++ villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); ++ villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); + } + + public boolean villagerTraderCanBeLeashed = false; diff --git a/patches/Purpur/patches/server/0066-Implement-infinite-lava.patch b/patches/Purpur/patches/server/0066-Implement-infinite-lava.patch new file mode 100644 index 00000000..9129614e --- /dev/null +++ b/patches/Purpur/patches/server/0066-Implement-infinite-lava.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 23 Nov 2019 17:55:42 -0600 +Subject: [PATCH] Implement infinite lava + + +diff --git a/src/main/java/net/minecraft/server/FluidTypeFlowing.java b/src/main/java/net/minecraft/server/FluidTypeFlowing.java +index d72a88e9275eb00eed35b6467538a46e5cee32d5..35d55bc15b179a8200082ca23fad04bbe550068d 100644 +--- a/src/main/java/net/minecraft/server/FluidTypeFlowing.java ++++ b/src/main/java/net/minecraft/server/FluidTypeFlowing.java +@@ -195,7 +195,7 @@ public abstract class FluidTypeFlowing extends FluidType { + } + } + +- if (this.f() && j >= 2) { ++ if (infinite(iworldreader) && j >= getRequiredSources(iworldreader)) { // Purpur + IBlockData iblockdata2 = iworldreader.getType(blockposition.down()); + Fluid fluid1 = iblockdata2.getFluid(); + +@@ -266,6 +266,17 @@ public abstract class FluidTypeFlowing extends FluidType { + return (Fluid) this.e().h().set(FluidTypeFlowing.FALLING, flag); + } + ++ // Purpur start ++ protected boolean infinite(IWorldReader iworldreader) { ++ return infinite(); ++ } ++ ++ protected int getRequiredSources(IWorldReader iworldreader) { ++ return 2; ++ } ++ // Purpur end ++ ++ protected boolean infinite() { return f(); } // Purpur - OBFHELPER + protected abstract boolean f(); + + protected void a(GeneratorAccess generatoraccess, BlockPosition blockposition, IBlockData iblockdata, EnumDirection enumdirection, Fluid fluid) { +diff --git a/src/main/java/net/minecraft/server/FluidTypeLava.java b/src/main/java/net/minecraft/server/FluidTypeLava.java +index 29930e801cdcb97bec2fb113ec478fe9c4a63b63..ffab2391925f577420ee52f3aa05041afa61464e 100644 +--- a/src/main/java/net/minecraft/server/FluidTypeLava.java ++++ b/src/main/java/net/minecraft/server/FluidTypeLava.java +@@ -147,6 +147,18 @@ public abstract class FluidTypeLava extends FluidTypeFlowing { + generatoraccess.triggerEffect(1501, blockposition, 0); + } + ++ // Purpur start ++ @Override ++ protected boolean infinite(IWorldReader iworldreader) { ++ return iworldreader.getWorldBorder().world.purpurConfig.lavaInfinite; ++ } ++ ++ @Override ++ protected int getRequiredSources(IWorldReader iworldreader) { ++ return iworldreader.getWorldBorder().world.purpurConfig.lavaInfiniteRequiredSources; ++ } ++ // Purpur end ++ + @Override + protected boolean f() { + return false; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index db6fb004be99341dba16c184244ed1a04c386fd4..d1f86ed2bbdec50e17b04814ea103bc0fb9ac0b1 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -158,6 +158,13 @@ public class PurpurWorldConfig { + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); + } + ++ public boolean lavaInfinite = false; ++ public int lavaInfiniteRequiredSources = 2; ++ private void lavaSettings() { ++ lavaInfinite = getBoolean("blocks.lava.infinite-source", lavaInfinite); ++ lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources); ++ } ++ + public boolean signAllowColors = false; + public boolean signRightClickEdit = false; + private void signSettings() { diff --git a/patches/Purpur/patches/server/0067-Make-lava-flow-speed-configurable.patch b/patches/Purpur/patches/server/0067-Make-lava-flow-speed-configurable.patch new file mode 100644 index 00000000..5cc8ce4f --- /dev/null +++ b/patches/Purpur/patches/server/0067-Make-lava-flow-speed-configurable.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jan 2020 11:31:36 -0600 +Subject: [PATCH] Make lava flow speed configurable + + +diff --git a/src/main/java/net/minecraft/server/FluidTypeLava.java b/src/main/java/net/minecraft/server/FluidTypeLava.java +index ffab2391925f577420ee52f3aa05041afa61464e..d981aa5a5f001333675eabd50497bbfa2dcf1df3 100644 +--- a/src/main/java/net/minecraft/server/FluidTypeLava.java ++++ b/src/main/java/net/minecraft/server/FluidTypeLava.java +@@ -129,7 +129,7 @@ public abstract class FluidTypeLava extends FluidTypeFlowing { + + @Override + public int a(IWorldReader iworldreader) { +- return iworldreader.getDimensionManager().isNether() ? 10 : 30; ++ return iworldreader.getDimensionManager().isNether() ? iworldreader.getWorldBorder().world.purpurConfig.lavaSpeedNether : iworldreader.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur + } + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d1f86ed2bbdec50e17b04814ea103bc0fb9ac0b1..4fa008bdbd40effe092c7d36ac2157918fb6d3f6 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -160,9 +160,13 @@ public class PurpurWorldConfig { + + public boolean lavaInfinite = false; + public int lavaInfiniteRequiredSources = 2; ++ public int lavaSpeedNether = 10; ++ public int lavaSpeedNotNether = 30; + private void lavaSettings() { + lavaInfinite = getBoolean("blocks.lava.infinite-source", lavaInfinite); + lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources); ++ lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether); ++ lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); + } + + public boolean signAllowColors = false; diff --git a/patches/Purpur/patches/server/0068-Add-player-death-exp-control-options.patch b/patches/Purpur/patches/server/0068-Add-player-death-exp-control-options.patch new file mode 100644 index 00000000..07c27276 --- /dev/null +++ b/patches/Purpur/patches/server/0068-Add-player-death-exp-control-options.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Dec 2019 22:08:37 -0600 +Subject: [PATCH] Add player death exp control options + + +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index f15ec5c45d95c6828ed628451917ac3426a76f1f..0ef020972494181a87c86f95aec7a4302e243f16 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -85,6 +85,8 @@ public abstract class EntityHuman extends EntityLiving { + // CraftBukkit end + + // Purpur start ++ private javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); ++ + public void setAfk(boolean setAfk){ + } + +@@ -1716,9 +1718,18 @@ public abstract class EntityHuman extends EntityLiving { + @Override + protected int getExpValue(EntityHuman entityhuman) { + if (!this.world.getGameRules().getBoolean(GameRules.KEEP_INVENTORY) && !this.isSpectator()) { +- int i = this.expLevel * 7; +- +- return i > 100 ? 100 : i; ++ // Purpur start ++ int toDrop; ++ try { ++ scriptEngine.eval("expLevel = " + expLevel); ++ scriptEngine.eval("expTotal = " + expTotal); ++ scriptEngine.eval("exp = " + exp); ++ toDrop = (int) Math.round((Double) scriptEngine.eval(world.purpurConfig.playerDeathExpDropEquation)); ++ } catch (Exception ignore) { ++ toDrop = expLevel * 7; ++ } ++ return Math.min(toDrop, world.purpurConfig.playerDeathExpDropMax); ++ // Purpur end + } else { + return 0; + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 4fa008bdbd40effe092c7d36ac2157918fb6d3f6..d54f699aba5c73704f2c739f8fb3f8becf8288ad 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -123,6 +123,13 @@ public class PurpurWorldConfig { + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + } + ++ public String playerDeathExpDropEquation = "expLevel * 7"; ++ public int playerDeathExpDropMax = 100; ++ private void playerDeathExpSettings() { ++ playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); ++ playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); ++ } ++ + public int playerSpawnInvulnerableTicks = 60; + public boolean playerInvulnerableWhileAcceptingResourcePack = false; + private void playerInvulnerabilities() { diff --git a/patches/Purpur/patches/server/0069-Add-canSaveToDisk-to-Entity.patch b/patches/Purpur/patches/server/0069-Add-canSaveToDisk-to-Entity.patch new file mode 100644 index 00000000..1dd9f44d --- /dev/null +++ b/patches/Purpur/patches/server/0069-Add-canSaveToDisk-to-Entity.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 18 Feb 2020 20:07:08 -0600 +Subject: [PATCH] Add canSaveToDisk to Entity + + +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index 076d6c1e1cc049dd312ecb30518e7b25fc2d7371..5f04591193d58ba7897194142da5efcbec3763dd 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -536,6 +536,7 @@ public class ChunkRegionLoader { + + while (iterator1.hasNext()) { + Entity entity = (Entity) iterator1.next(); ++ if (!entity.canSaveToDisk()) continue; // Purpur + final EntityTypes entityType = entity.getEntityType(); + final int saveLimit = worldserver.paperConfig.entityPerChunkSaveLimits.getOrDefault(entityType, -1); + if (saveLimit > -1) { +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index f18941c7c740959181b728ab9da06c7e9d97aa79..dfbb5a9538780cbd2a5766486138f35b189da235 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -308,6 +308,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.headHeight = this.getHeadHeight(EntityPose.STANDING, this.size); + } + ++ // Purpur start ++ public boolean canSaveToDisk() { ++ return true; ++ } ++ // Purpur end ++ + public boolean isSpectator() { + return false; + } diff --git a/patches/Purpur/patches/server/0070-Configurable-void-damage-height.patch b/patches/Purpur/patches/server/0070-Configurable-void-damage-height.patch new file mode 100644 index 00000000..dd1e1615 --- /dev/null +++ b/patches/Purpur/patches/server/0070-Configurable-void-damage-height.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 27 Feb 2020 21:42:19 -0600 +Subject: [PATCH] Configurable void damage height + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index dfbb5a9538780cbd2a5766486138f35b189da235..9eb2e73c084b142bf04f798b572e6ca87f4f8416 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -611,7 +611,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + + // Paper start + protected void performVoidDamage() { +- if (this.locY() < -64.0D || (this.world.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER ++ if (this.locY() < world.purpurConfig.voidDamageHeight || (this.world.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Purpur + && world.paperConfig.doNetherTopVoidDamage() + && this.locY() >= world.paperConfig.netherVoidTopDamageHeight)) { + this.doVoidDamage(); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d54f699aba5c73704f2c739f8fb3f8becf8288ad..046c6e688c64c6f2b2d11c8d356ce0b738e7fc6e 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -116,11 +116,13 @@ public class PurpurWorldConfig { + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean milkCuresBadOmen = true; + public double tridentLoyaltyVoidReturnHeight = 0.0D; ++ public double voidDamageHeight = -64.0D; + private void miscGameplayMechanicsSettings() { + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); ++ voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + } + + public String playerDeathExpDropEquation = "expLevel * 7"; diff --git a/patches/Purpur/patches/server/0071-Dispenser-curse-of-binding-protection.patch b/patches/Purpur/patches/server/0071-Dispenser-curse-of-binding-protection.patch new file mode 100644 index 00000000..1a1f7376 --- /dev/null +++ b/patches/Purpur/patches/server/0071-Dispenser-curse-of-binding-protection.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 25 Aug 2019 00:09:52 -0500 +Subject: [PATCH] Dispenser curse of binding protection + + +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index de891e39c02ac6fbb87044d31b3eeabac1e78865..c9791be9385c83c8ab626ff3661b0c6cb45822ad 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -995,6 +995,13 @@ public abstract class EntityInsentient extends EntityLiving { + + } + ++ // Purpur start ++ public static EnumItemSlot getSlotForDispenser(ItemStack itemstack) { ++ return EnchantmentManager.getEnchantmentLevel(Enchantments.BINDING_CURSE, itemstack) > 0 ? EnumItemSlot.MAINHAND : getSlotForItemStack(itemstack); ++ } ++ // Purpur end ++ ++ public static EnumItemSlot getSlotForItemStack(ItemStack itemstack) { return j(itemstack); } // Purpur - OBFHELPER + public static EnumItemSlot j(ItemStack itemstack) { + Item item = itemstack.getItem(); + +diff --git a/src/main/java/net/minecraft/server/ItemArmor.java b/src/main/java/net/minecraft/server/ItemArmor.java +index 669a5041184846ca8430a7f0d3197025fe6d437e..7cd2e871b5a429a86dbc3c4208d247a4246ea1a8 100644 +--- a/src/main/java/net/minecraft/server/ItemArmor.java ++++ b/src/main/java/net/minecraft/server/ItemArmor.java +@@ -35,7 +35,7 @@ public class ItemArmor extends Item implements ItemWearable { + return false; + } else { + EntityLiving entityliving = (EntityLiving) list.get(0); +- EnumItemSlot enumitemslot = EntityInsentient.j(itemstack); ++ EnumItemSlot enumitemslot = isourceblock.getWorld().purpurConfig.dispenserApplyCursedArmor ? EntityInsentient.getSlotForItemStack(itemstack) : EntityInsentient.getSlotForDispenser(itemstack); // Purpur + ItemStack itemstack1 = itemstack.cloneAndSubtract(1); + // CraftBukkit start + World world = isourceblock.getWorld(); +@@ -94,6 +94,7 @@ public class ItemArmor extends Item implements ItemWearable { + this.m = builder.build(); + } + ++ public EnumItemSlot getEquipmentSlot() { return b(); } // Purpur - OBFHELPER + public EnumItemSlot b() { + return this.b; + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 046c6e688c64c6f2b2d11c8d356ce0b738e7fc6e..9b927dad1b054a21498205bf13f3952ea7ada48a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -162,6 +162,11 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean dispenserApplyCursedArmor = true; ++ private void dispenserSettings() { ++ dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor); ++ } ++ + public boolean farmlandGetsMoistFromBelow = false; + private void farmlandSettings() { + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); diff --git a/patches/Purpur/patches/server/0072-Add-option-for-boats-to-eject-players-on-land.patch b/patches/Purpur/patches/server/0072-Add-option-for-boats-to-eject-players-on-land.patch new file mode 100644 index 00000000..cf929dfc --- /dev/null +++ b/patches/Purpur/patches/server/0072-Add-option-for-boats-to-eject-players-on-land.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 7 Sep 2019 22:47:59 -0500 +Subject: [PATCH] Add option for boats to eject players on land + + +diff --git a/src/main/java/net/minecraft/server/EntityBoat.java b/src/main/java/net/minecraft/server/EntityBoat.java +index baa4a61114e7460c74027e1519332f0dd9582647..603910a6f9ecc34be9eb2d4fb28e5c2e20aca90a 100644 +--- a/src/main/java/net/minecraft/server/EntityBoat.java ++++ b/src/main/java/net/minecraft/server/EntityBoat.java +@@ -442,6 +442,7 @@ public class EntityBoat extends Entity { + + if (f > 0.0F) { + this.aw = f; ++ if (world.purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur + return EntityBoat.EnumStatus.ON_LAND; + } else { + return EntityBoat.EnumStatus.IN_AIR; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 9b927dad1b054a21498205bf13f3952ea7ada48a..97175fa5dc4d697118bb6391effef373bce110be 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -112,12 +112,14 @@ public class PurpurWorldConfig { + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + } + ++ public boolean boatEjectPlayersOnLand = false; + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean milkCuresBadOmen = true; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + private void miscGameplayMechanicsSettings() { ++ boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); diff --git a/patches/Purpur/patches/server/0073-Add-obfhelpers-for-plugin-use.patch b/patches/Purpur/patches/server/0073-Add-obfhelpers-for-plugin-use.patch new file mode 100644 index 00000000..217ab639 --- /dev/null +++ b/patches/Purpur/patches/server/0073-Add-obfhelpers-for-plugin-use.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 1 Jan 2020 20:12:39 -0600 +Subject: [PATCH] Add obfhelpers for plugin use + + +diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java +index afa1dc693bc2e2e68294a1d3dec1c078ea95b286..7f3df9ba37076c0a982803148d21b0985f62f12c 100644 +--- a/src/main/java/net/minecraft/server/ItemStack.java ++++ b/src/main/java/net/minecraft/server/ItemStack.java +@@ -639,6 +639,7 @@ public final class ItemStack { + return this.tag; + } + ++ public NBTTagCompound getOrCreateSubTag(String s) { return a(s); } // Purpur - OBFHELPER + public NBTTagCompound a(String s) { + if (this.tag != null && this.tag.hasKeyOfType(s, 10)) { + return this.tag.getCompound(s); diff --git a/patches/Purpur/patches/server/0074-Mending-mends-most-damages-equipment-first.patch b/patches/Purpur/patches/server/0074-Mending-mends-most-damages-equipment-first.patch new file mode 100644 index 00000000..e80aec9e --- /dev/null +++ b/patches/Purpur/patches/server/0074-Mending-mends-most-damages-equipment-first.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 14 Jul 2019 19:52:47 -0500 +Subject: [PATCH] Mending mends most damages equipment first + + +diff --git a/src/main/java/net/minecraft/server/EnchantmentManager.java b/src/main/java/net/minecraft/server/EnchantmentManager.java +index 7b263594304a9b745f583fe7178ac16936cbd93b..33e2dc3a1003fe95fa71999fc24df84131238a6a 100644 +--- a/src/main/java/net/minecraft/server/EnchantmentManager.java ++++ b/src/main/java/net/minecraft/server/EnchantmentManager.java +@@ -251,7 +251,30 @@ public class EnchantmentManager { + return getEnchantmentLevel(Enchantments.CHANNELING, itemstack) > 0; + } + +- public static @javax.annotation.Nonnull ItemStack getRandomEquippedItemWithEnchant(Enchantment enchantment, EntityLiving entityliving) { Entry entry = b(enchantment, entityliving); return entry != null ? entry.getValue() : ItemStack.NULL_ITEM; } // Paper - OBFHELPER ++ // Purpur start ++ @Nullable ++ public static Entry getMostDamagedEquipment(Enchantment enchantment, EntityLiving entityliving) { ++ Map map = enchantment.a(entityliving); ++ if (map.isEmpty()) { ++ return null; ++ } ++ Entry item = null; ++ float maxPercent = 0F; ++ for (Entry entry : map.entrySet()) { ++ ItemStack itemstack = entry.getValue(); ++ if (!itemstack.isEmpty() && itemstack.isDamaged() && getEnchantmentLevel(enchantment, itemstack) > 0) { ++ float percent = itemstack.getDamagePercent(); ++ if (item == null || percent > maxPercent) { ++ item = entry; ++ maxPercent = percent; ++ } ++ } ++ } ++ return item; ++ } ++ // Purpur end ++ ++ public static @javax.annotation.Nonnull ItemStack getRandomEquippedItemWithEnchant(Enchantment enchantment, EntityLiving entityliving) { Entry entry = enchantment == Enchantments.MENDING && entityliving.world.purpurConfig.useBetterMending ? getMostDamagedEquipment(enchantment, entityliving) : b(enchantment, entityliving); return entry != null ? entry.getValue() : ItemStack.NULL_ITEM; } // Paper - OBFHELPER + @Nullable public static Entry b(Enchantment enchantment, EntityLiving entityliving) { + return a(enchantment, entityliving, (itemstack) -> { + return true; +diff --git a/src/main/java/net/minecraft/server/EntityExperienceOrb.java b/src/main/java/net/minecraft/server/EntityExperienceOrb.java +index c2be0c2bc315876f120cff207e5516dda2bd55d7..358d6d660581686a2e8d49e13e8c2ade9ec2fec2 100644 +--- a/src/main/java/net/minecraft/server/EntityExperienceOrb.java ++++ b/src/main/java/net/minecraft/server/EntityExperienceOrb.java +@@ -220,7 +220,7 @@ public class EntityExperienceOrb extends Entity { + if (this.d == 0 && entityhuman.bu == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(((EntityPlayer) entityhuman).getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper + entityhuman.bu = 2; + entityhuman.receive(this, 1); +- Entry entry = EnchantmentManager.a(Enchantments.MENDING, (EntityLiving) entityhuman, ItemStack::f); ++ Entry entry = world.purpurConfig.useBetterMending ? EnchantmentManager.getMostDamagedEquipment(Enchantments.MENDING, entityhuman) : EnchantmentManager.a(Enchantments.MENDING, entityhuman, ItemStack::isDamaged); // Purpur + + if (entry != null) { + ItemStack itemstack = (ItemStack) entry.getValue(); +diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java +index 7f3df9ba37076c0a982803148d21b0985f62f12c..e747ee83f1a69a4a4bad87e720abc9b085fb6149 100644 +--- a/src/main/java/net/minecraft/server/ItemStack.java ++++ b/src/main/java/net/minecraft/server/ItemStack.java +@@ -413,10 +413,19 @@ public final class ItemStack { + } + } + ++ public boolean isDamaged() { return f(); } // Purpur - OBFHELPER + public boolean f() { + return this.e() && this.getDamage() > 0; + } + ++ public float getDamagePercent() { ++ if (isDamaged()) { ++ return (float) getDamage() / (float) getItem().getMaxDurability(); ++ } else { ++ return 0F; ++ } ++ } ++ + public int getDamage() { + return this.tag == null ? 0 : this.tag.getInt("Damage"); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 97175fa5dc4d697118bb6391effef373bce110be..d61a96f0d9fc92780da0c698ffb4b903878a4198 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -112,6 +112,7 @@ public class PurpurWorldConfig { + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + } + ++ public boolean useBetterMending = false; + public boolean boatEjectPlayersOnLand = false; + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesPickUpLootBypassMobGriefing = false; +@@ -119,6 +120,7 @@ public class PurpurWorldConfig { + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + private void miscGameplayMechanicsSettings() { ++ useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); diff --git a/patches/Purpur/patches/server/0075-Add-5-second-tps-average-in-tps.patch b/patches/Purpur/patches/server/0075-Add-5-second-tps-average-in-tps.patch new file mode 100644 index 00000000..360709e8 --- /dev/null +++ b/patches/Purpur/patches/server/0075-Add-5-second-tps-average-in-tps.patch @@ -0,0 +1,73 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 942f556eca32ce13c6b3490ef0d6f9e960a36d06..e16f120544ad46995dcf695849d3634c575d65a5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -162,7 +162,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 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) { diff --git a/patches/Purpur/patches/server/0076-Implement-elytra-settings.patch b/patches/Purpur/patches/server/0076-Implement-elytra-settings.patch new file mode 100644 index 00000000..9cc6d5cd --- /dev/null +++ b/patches/Purpur/patches/server/0076-Implement-elytra-settings.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 25 Jul 2019 18:07:37 -0500 +Subject: [PATCH] Implement elytra settings + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 9b1ecf6101e146843b4d2eeabc3e0e3c4fde54b5..82ef4c9c534f4a0840a61d33c7727bd4770c7511 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -2851,7 +2851,16 @@ public abstract class EntityLiving extends Entity { + if (itemstack.getItem() == Items.ELYTRA && ItemElytra.d(itemstack)) { + flag = true; + if (!this.world.isClientSide && (this.be + 1) % 20 == 0) { +- itemstack.damage(1, this, (entityliving) -> { ++ // Purpur start ++ int damage = world.purpurConfig.elytraDamagePerSecond; ++ if (world.purpurConfig.elytraDamageMultiplyBySpeed > 0) { ++ double speed = getMot().magnitudeSquared(); ++ if (speed > world.purpurConfig.elytraDamageMultiplyBySpeed) { ++ damage *= (int) speed; ++ } ++ } ++ itemstack.damage(damage, this, (entityliving) -> { ++ // Purpur end + entityliving.broadcastItemBreak(EnumItemSlot.CHEST); + }); + } +diff --git a/src/main/java/net/minecraft/server/ItemFireworks.java b/src/main/java/net/minecraft/server/ItemFireworks.java +index e775fe69ee7e555721bc73e7cb0dd3136736bc9c..990eb656699d9ead26b42fbb305530b7ae860ee8 100644 +--- a/src/main/java/net/minecraft/server/ItemFireworks.java ++++ b/src/main/java/net/minecraft/server/ItemFireworks.java +@@ -43,6 +43,14 @@ public class ItemFireworks extends Item { + // Paper start + com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Firework) entityfireworks.getBukkitEntity()); + if (event.callEvent() && world.addEntity(entityfireworks)) { ++ // Purpur start ++ if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { ++ ItemStack chestItem = entityhuman.getEquipment(EnumItemSlot.CHEST); ++ if (chestItem.getItem() == Items.ELYTRA) { ++ chestItem.damage(world.purpurConfig.elytraDamagePerFireworkBoost, entityhuman, (entityliving) -> entityliving.broadcastItemBreak(EnumItemSlot.CHEST)); ++ } ++ } ++ // Purpur end + if (event.shouldConsume() && !entityhuman.abilities.canInstantlyBuild) { + itemstack.subtract(1); + } else ((EntityPlayer) entityhuman).getBukkitEntity().updateInventory(); +diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java +index e747ee83f1a69a4a4bad87e720abc9b085fb6149..a4edfb02fd350433020b0f3699726b6127ab9933 100644 +--- a/src/main/java/net/minecraft/server/ItemStack.java ++++ b/src/main/java/net/minecraft/server/ItemStack.java +@@ -445,7 +445,7 @@ public final class ItemStack { + int j; + + if (i > 0) { +- j = EnchantmentManager.getEnchantmentLevel(Enchantments.DURABILITY, this); ++ j = (getItem() == Items.ELYTRA && entityplayer != null && entityplayer.world.purpurConfig.elytraIgnoreUnbreaking) ? 0 : EnchantmentManager.getEnchantmentLevel(Enchantments.DURABILITY, this); // Purpur + int k = 0; + + for (int l = 0; j > 0 && l < i; ++l) { +@@ -491,6 +491,12 @@ public final class ItemStack { + if (this.isDamaged(i, t0.getRandom(), t0 instanceof EntityPlayer ? (EntityPlayer) t0 : null)) { + consumer.accept(t0); + Item item = this.getItem(); ++ // Purpur start ++ if (item == Items.ELYTRA) { ++ setDamage(item.getMaxDurability() - 1); ++ return; ++ } ++ // Purpur end + // CraftBukkit start - Check for item breaking + if (this.count == 1 && t0 instanceof EntityHuman) { + org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((EntityHuman) t0, this); +diff --git a/src/main/java/net/minecraft/server/ItemTrident.java b/src/main/java/net/minecraft/server/ItemTrident.java +index 3663b701736b583db80e3119b642e47b8a0e6ff3..18f767dc574e7345d2db73e6be44f4e65eb58c63 100644 +--- a/src/main/java/net/minecraft/server/ItemTrident.java ++++ b/src/main/java/net/minecraft/server/ItemTrident.java +@@ -102,6 +102,16 @@ public class ItemTrident extends Item implements ItemVanishable { + f2 *= f6 / f5; + f3 *= f6 / f5; + f4 *= f6 / f5; ++ ++ // Purpur start ++ ItemStack chestItem = entityhuman.getEquipment(EnumItemSlot.CHEST); ++ if (chestItem.getItem() == Items.ELYTRA && world.purpurConfig.elytraDamagePerTridentBoost > 0) { ++ chestItem.damage(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, (entity) -> { ++ entity.broadcastItemBreak(EnumItemSlot.CHEST); ++ }); ++ } ++ // Purpur end ++ + entityhuman.i((double) f2, (double) f3, (double) f4); + entityhuman.r(20); + if (entityhuman.isOnGround()) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d61a96f0d9fc92780da0c698ffb4b903878a4198..886c3b98d6bff04a93cd963a092654ef1c9729d1 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -129,6 +129,19 @@ public class PurpurWorldConfig { + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + } + ++ public int elytraDamagePerSecond = 1; ++ public double elytraDamageMultiplyBySpeed = 0; ++ public boolean elytraIgnoreUnbreaking = false; ++ public int elytraDamagePerFireworkBoost = 0; ++ public int elytraDamagePerTridentBoost = 0; ++ private void elytraSettings() { ++ elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond); ++ elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed); ++ elytraIgnoreUnbreaking = getBoolean("gameplay-mechanics.elytra.ignore-unbreaking", elytraIgnoreUnbreaking); ++ elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost); ++ elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost); ++ } ++ + public String playerDeathExpDropEquation = "expLevel * 7"; + public int playerDeathExpDropMax = 100; + private void playerDeathExpSettings() { diff --git a/patches/Purpur/patches/server/0077-Item-entity-immunities.patch b/patches/Purpur/patches/server/0077-Item-entity-immunities.patch new file mode 100644 index 00000000..2713cfdb --- /dev/null +++ b/patches/Purpur/patches/server/0077-Item-entity-immunities.patch @@ -0,0 +1,168 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 22 Feb 2020 15:54:08 -0600 +Subject: [PATCH] Item entity immunities + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 9eb2e73c084b142bf04f798b572e6ca87f4f8416..f5159b76dee6127d7db2addfcb512b5b1f5b9c41 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -1472,6 +1472,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + + } + ++ public boolean isInLiquid(Tag tag) { return a(tag); } // Purpur - OBFHELPER + public boolean a(Tag tag) { + return this.O == tag; + } +diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java +index d99cecc4075338d7b8f154ab94d8ac04190ba371..ec37d2c3b0393c43097bdfc6064ebe3ab8bc28ce 100644 +--- a/src/main/java/net/minecraft/server/EntityItem.java ++++ b/src/main/java/net/minecraft/server/EntityItem.java +@@ -23,6 +23,9 @@ public class EntityItem extends Entity { + public final float b; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit + public boolean canMobPickup = true; // Paper ++ public boolean immuneToCactus = false; // Purpur ++ public boolean immuneToExplosion = false; // Purpur ++ public boolean immuneToFire = false; // Purpur + + public EntityItem(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -274,6 +277,16 @@ public class EntityItem extends Entity { + return false; + } else if (!this.getItemStack().getItem().a(damagesource)) { + return false; ++ // Purpur start ++ } else if (immuneToCactus && damagesource == DamageSource.CACTUS) { ++ respawnOnClient(); ++ return false; ++ } else if (immuneToFire && (damagesource.isFire() || damagesource == DamageSource.FIRE)) { ++ return false; ++ } else if (immuneToExplosion && damagesource.isExplosion()) { ++ respawnOnClient(); ++ return false; ++ // Purpur end + } else { + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damagesource, f)) { +@@ -454,6 +467,9 @@ public class EntityItem extends Entity { + com.google.common.base.Preconditions.checkArgument(!itemstack.isEmpty(), "Cannot drop air"); // CraftBukkit + this.getDataWatcher().set(EntityItem.ITEM, itemstack); + this.getDataWatcher().markDirty(EntityItem.ITEM); // CraftBukkit - SPIGOT-4591, must mark dirty ++ if (world.purpurConfig.itemImmuneToCactus.contains(itemstack.getItem())) immuneToCactus = true; // Purpur ++ if (world.purpurConfig.itemImmuneToExplosion.contains(itemstack.getItem())) immuneToExplosion = true; // Purpur ++ if (world.purpurConfig.itemImmuneToFire.contains(itemstack.getItem())) immuneToFire = true; // Purpur + } + + @Override +@@ -535,4 +551,15 @@ public class EntityItem extends Entity { + super.setPositionRaw(x, y, z); + } + // Paper end - fix MC-4 ++ ++ // Purpur start ++ public void respawnOnClient() { ++ Packet spawnPacket = new PacketPlayOutSpawnEntity(this); ++ Packet metadataPacket = new PacketPlayOutEntityMetadata(getId(), getDataWatcher(), true); ++ for (EntityPlayer entityplayer : getTracker().trackerEntry.trackedPlayers) { ++ entityplayer.playerConnection.sendPacket(spawnPacket); ++ entityplayer.playerConnection.sendPacket(metadataPacket); ++ } ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java +index 228236bce14bfdf930570b453862dcfaae9e4823..ad06bd81eded5d60f16c2d0ad1a4390f4b5a11a5 100644 +--- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java +@@ -106,6 +106,15 @@ public class EntityTrackerEntry { + this.c(); + } + ++ // Purpur start - respawn burning item entities on client (client kills them) ++ if (tracker.fireTicks > 0 && tracker instanceof EntityItem) { ++ EntityItem item = (EntityItem) tracker; ++ if (item.immuneToFire && !item.dead) { ++ item.respawnOnClient(); ++ } ++ } ++ // Purpur end ++ + if (this.tickCounter % this.d == 0 || this.tracker.impulse || this.tracker.getDataWatcher().a()) { + int i; + int j; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 886c3b98d6bff04a93cd963a092654ef1c9729d1..75250392135f416e766e44ffdad7eecd06a89ae5 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -101,6 +101,27 @@ public class PurpurWorldConfig { + } + } + ++ public List itemImmuneToCactus = new ArrayList<>(); ++ public List itemImmuneToExplosion = new ArrayList<>(); ++ public List itemImmuneToFire = new ArrayList<>(); ++ private void itemSettings() { ++ itemImmuneToCactus.clear(); ++ getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { ++ Item item = IRegistry.ITEM.get(new MinecraftKey(key.toString())); ++ if (item != Items.AIR) itemImmuneToCactus.add(item); ++ }); ++ itemImmuneToExplosion.clear(); ++ getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> { ++ Item item = IRegistry.ITEM.get(new MinecraftKey(key.toString())); ++ if (item != Items.AIR) itemImmuneToExplosion.add(item); ++ }); ++ itemImmuneToFire.clear(); ++ getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> { ++ Item item = IRegistry.ITEM.get(new MinecraftKey(key.toString())); ++ if (item != Items.AIR) itemImmuneToFire.add(item); ++ }); ++ } ++ + public boolean idleTimeoutKick = true; + public boolean idleTimeoutTickNearbyEntities = true; + public boolean idleTimeoutCountAsSleeping = false; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index bcbaad11852a51436a00c8e172bdd841ba93ec3c..8c0b618ac66688f1fef42ae292745c2844aa12df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -114,4 +114,36 @@ public class CraftItem extends CraftEntity implements Item { + public EntityType getType() { + return EntityType.DROPPED_ITEM; + } ++ ++ // Purpur start ++ @Override ++ public void setImmuneToCactus(boolean immuneToCactus) { ++ item.immuneToCactus = immuneToCactus; ++ } ++ ++ @Override ++ public boolean isImmuneToCactus() { ++ return item.immuneToCactus; ++ } ++ ++ @Override ++ public void setImmuneToExplosion(boolean immuneToExplosion) { ++ item.immuneToExplosion = immuneToExplosion; ++ } ++ ++ @Override ++ public boolean isImmuneToExplosion() { ++ return item.immuneToExplosion; ++ } ++ ++ @Override ++ public void setImmuneToFire(boolean immuneToFire) { ++ item.immuneToFire = immuneToFire; ++ } ++ ++ @Override ++ public boolean isImmuneToFire() { ++ return item.immuneToFire; ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0078-Add-ping-command.patch b/patches/Purpur/patches/server/0078-Add-ping-command.patch new file mode 100644 index 00000000..fe0c9fc1 --- /dev/null +++ b/patches/Purpur/patches/server/0078-Add-ping-command.patch @@ -0,0 +1,128 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 13 Mar 2020 22:29:10 -0500 +Subject: [PATCH] Add /ping command + + +diff --git a/src/main/java/net/minecraft/server/ArgumentEntity.java b/src/main/java/net/minecraft/server/ArgumentEntity.java +index 1194f91a51f87bc461af039fe0819aaf3e5c8bdd..9141976d1d6c5cef9eb4d415df2ad3b8f14ce913 100644 +--- a/src/main/java/net/minecraft/server/ArgumentEntity.java ++++ b/src/main/java/net/minecraft/server/ArgumentEntity.java +@@ -69,10 +69,12 @@ public class ArgumentEntity implements ArgumentType { + return ((EntitySelector) commandcontext.getArgument(s, EntitySelector.class)).c((CommandListenerWrapper) commandcontext.getSource()); + } + ++ public static ArgumentEntity players() { return d(); } // Purpur - OBFHELPER + public static ArgumentEntity d() { + return new ArgumentEntity(false, true); + } + ++ public static Collection getPlayers(CommandContext commandcontext, String s) throws CommandSyntaxException { return f(commandcontext, s); } // Purpur - OBFHELPER + public static Collection f(CommandContext commandcontext, String s) throws CommandSyntaxException { + List list = ((EntitySelector) commandcontext.getArgument(s, EntitySelector.class)).d((CommandListenerWrapper) commandcontext.getSource()); + +diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java +index 17753c8a997aa286460be5d8eb6508e2eaed18ce..d080bf58ebc9c1dc9d41fae7d515547bc3f26d54 100644 +--- a/src/main/java/net/minecraft/server/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/server/CommandDispatcher.java +@@ -107,6 +107,7 @@ public class CommandDispatcher { + CommandIdleTimeout.a(this.b); + CommandStop.a(this.b); + CommandWhitelist.a(this.b); ++ net.pl3x.purpur.command.PingCommand.register(getDispatcher()); // Purpur + } + + if (commanddispatcher_servertype.d) { +@@ -338,10 +339,12 @@ public class CommandDispatcher { + + } + ++ public static LiteralArgumentBuilder literal(String s) { return a(s); } // Purpur - OBFHELPER + public static LiteralArgumentBuilder a(String s) { + return LiteralArgumentBuilder.literal(s); + } + ++ public static RequiredArgumentBuilder argument(String s, ArgumentType argumenttype) { return a(s, argumenttype); } // Purpur - OBFHELPER + public static RequiredArgumentBuilder a(String s, ArgumentType argumenttype) { + return RequiredArgumentBuilder.argument(s, argumenttype); + } +@@ -357,6 +360,7 @@ public class CommandDispatcher { + }; + } + ++ public com.mojang.brigadier.CommandDispatcher getDispatcher() { return a(); } // Purpur - OBFHELPER + public com.mojang.brigadier.CommandDispatcher a() { + return this.b; + } +diff --git a/src/main/java/net/minecraft/server/CommandListenerWrapper.java b/src/main/java/net/minecraft/server/CommandListenerWrapper.java +index 86f1cfe454ea0a989775b49a6b88375c766ef647..da53af61d1171db3c167c6e007adf95355771653 100644 +--- a/src/main/java/net/minecraft/server/CommandListenerWrapper.java ++++ b/src/main/java/net/minecraft/server/CommandListenerWrapper.java +@@ -189,6 +189,7 @@ public class CommandListenerWrapper implements ICompletionProvider, com.destroys + } + } + ++ public EntityPlayer getPlayerOrException() throws CommandSyntaxException { return h(); } // Purpur - OBFHELPER + public EntityPlayer h() throws CommandSyntaxException { + if (!(this.k instanceof EntityPlayer)) { + throw CommandListenerWrapper.a.create(); +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 10900ef825f88657f287c8c5b71a89338ba54c11..ebf74ddb9786d3bcccb62c85352bb6c2278433f3 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -133,10 +133,12 @@ public class PurpurConfig { + public static String afkBroadcastAway = "§e§o%s is now AFK"; + public static String afkBroadcastBack = "§e§o%s is no longer AFK"; + public static String afkTabListPrefix = "[AFK] "; ++ public static String pingCommandOutput = "§a%s's ping is %sms"; + private static void messages() { + afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); + afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); + afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix); ++ pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); + } + + public static String serverModName = "Purpur"; +diff --git a/src/main/java/net/pl3x/purpur/command/PingCommand.java b/src/main/java/net/pl3x/purpur/command/PingCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3d4762c4a179aaf4e270af018b940f93f1a7d9cd +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/command/PingCommand.java +@@ -0,0 +1,37 @@ ++package net.pl3x.purpur.command; ++ ++import net.minecraft.server.ArgumentEntity; ++import net.minecraft.server.CommandDispatcher; ++import net.minecraft.server.CommandListenerWrapper; ++import net.minecraft.server.EntityPlayer; ++import net.pl3x.purpur.PurpurConfig; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++ ++import java.util.Collection; ++import java.util.Collections; ++ ++public class PingCommand { ++ public static void register(com.mojang.brigadier.CommandDispatcher dispatcher) { ++ dispatcher.register(CommandDispatcher.literal("ping") ++ .requires((listener) -> { ++ return listener.hasPermission(2); ++ }) ++ .executes((context) -> { ++ return execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())); ++ }) ++ .then(CommandDispatcher.argument("targets", ArgumentEntity.players()) ++ .executes((context) -> { ++ return execute(context.getSource(), ArgumentEntity.getPlayers(context, "targets")); ++ }) ++ ) ++ ).setPermission("bukkit.command.ping"); ++ } ++ ++ private static int execute(CommandListenerWrapper sender, Collection targets) { ++ for (EntityPlayer player : targets) { ++ String output = String.format(PurpurConfig.pingCommandOutput, player.getProfile().getName(), player.ping); ++ sender.sendMessage(CraftChatMessage.fromStringOrNull(output), false); ++ } ++ return targets.size(); ++ } ++} diff --git a/patches/Purpur/patches/server/0079-Configurable-jockey-options.patch b/patches/Purpur/patches/server/0079-Configurable-jockey-options.patch new file mode 100644 index 00000000..8430f8c4 --- /dev/null +++ b/patches/Purpur/patches/server/0079-Configurable-jockey-options.patch @@ -0,0 +1,266 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Mar 2020 21:39:32 -0500 +Subject: [PATCH] Configurable jockey options + + +diff --git a/src/main/java/net/minecraft/server/EntityDrowned.java b/src/main/java/net/minecraft/server/EntityDrowned.java +index 9ae0897bfd6a4577901d9189d0dba22f3ec2110c..1a102816921fa3b40f6d364bb826db4459f68eb2 100644 +--- a/src/main/java/net/minecraft/server/EntityDrowned.java ++++ b/src/main/java/net/minecraft/server/EntityDrowned.java +@@ -21,6 +21,23 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + this.navigationLand = new Navigation(this, world); + } + ++ // Purpur start ++ @Override ++ public boolean jockeyOnlyBaby() { ++ return world.purpurConfig.drownedJockeyOnlyBaby; ++ } ++ ++ @Override ++ public double jockeyChance() { ++ return world.purpurConfig.drownedJockeyChance; ++ } ++ ++ @Override ++ public boolean jockeyTryExistingChickens() { ++ return world.purpurConfig.drownedJockeyTryExistingChickens; ++ } ++ // Purpur end ++ + @Override + protected void m() { + this.goalSelector.a(1, new EntityDrowned.c(this, 1.0D)); +diff --git a/src/main/java/net/minecraft/server/EntityPigZombie.java b/src/main/java/net/minecraft/server/EntityPigZombie.java +index 65bae8c9f28c23b0b5dd5d048e7fc7daf328075c..32b75f710b12efbcecec2c8d72d4d8cb725870fe 100644 +--- a/src/main/java/net/minecraft/server/EntityPigZombie.java ++++ b/src/main/java/net/minecraft/server/EntityPigZombie.java +@@ -21,6 +21,23 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + this.a(PathType.LAVA, 8.0F); + } + ++ // Purpur start ++ @Override ++ public boolean jockeyOnlyBaby() { ++ return world.purpurConfig.zombifiedPiglinJockeyOnlyBaby; ++ } ++ ++ @Override ++ public double jockeyChance() { ++ return world.purpurConfig.zombifiedPiglinJockeyChance; ++ } ++ ++ @Override ++ public boolean jockeyTryExistingChickens() { ++ return world.purpurConfig.zombifiedPiglinJockeyTryExistingChickens; ++ } ++ // Purpur end ++ + @Override + public void setAngerTarget(@Nullable UUID uuid) { + this.br = uuid; +diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java +index 752e39ad94ea9e8254853a3fda846be2bd436918..03263b94aaeeb8667e0f82c832e4743f4c63108e 100644 +--- a/src/main/java/net/minecraft/server/EntityZombie.java ++++ b/src/main/java/net/minecraft/server/EntityZombie.java +@@ -3,6 +3,7 @@ package net.minecraft.server; + import com.mojang.serialization.DynamicOps; + import java.time.LocalDate; + import java.time.temporal.ChronoField; ++import java.util.Collections; + import java.util.List; + import java.util.Random; + import java.util.UUID; +@@ -45,6 +46,20 @@ public class EntityZombie extends EntityMonster { + this(EntityTypes.ZOMBIE, world); + } + ++ // Purpur start ++ public boolean jockeyOnlyBaby() { ++ return world.purpurConfig.zombieJockeyOnlyBaby; ++ } ++ ++ public double jockeyChance() { ++ return world.purpurConfig.zombieJockeyChance; ++ } ++ ++ public boolean jockeyTryExistingChickens() { ++ return world.purpurConfig.zombieJockeyTryExistingChickens; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + if (world.paperConfig.zombiesTargetTurtleEggs) this.goalSelector.a(4, new EntityZombie.a(this, 1.0D, 3)); // Paper +@@ -442,19 +457,19 @@ public class EntityZombie extends EntityMonster { + if (object instanceof EntityZombie.GroupDataZombie) { + EntityZombie.GroupDataZombie entityzombie_groupdatazombie = (EntityZombie.GroupDataZombie) object; + +- if (entityzombie_groupdatazombie.a) { +- this.setBaby(true); ++ // Purpur start ++ if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby()) { ++ this.setBaby(entityzombie_groupdatazombie.isBaby()); + if (entityzombie_groupdatazombie.b) { +- if ((double) worldaccess.getRandom().nextFloat() < 0.05D) { +- List list = worldaccess.a(EntityChicken.class, this.getBoundingBox().grow(5.0D, 3.0D, 5.0D), IEntitySelector.c); ++ if ((double) worldaccess.getRandom().nextFloat() < jockeyChance()) { ++ List list = jockeyTryExistingChickens() ? worldaccess.a(EntityChicken.class, this.getBoundingBox().grow(5.0D, 3.0D, 5.0D), IEntitySelector.c) : Collections.emptyList(); + + if (!list.isEmpty()) { + EntityChicken entitychicken = (EntityChicken) list.get(0); + + entitychicken.setChickenJockey(true); + this.startRiding(entitychicken); +- } +- } else if ((double) worldaccess.getRandom().nextFloat() < 0.05D) { ++ } else { // Purpur + EntityChicken entitychicken1 = (EntityChicken) EntityTypes.CHICKEN.a(this.world); + + entitychicken1.setPositionRotation(this.locX(), this.locY(), this.locZ(), this.yaw, 0.0F); +@@ -462,6 +477,7 @@ public class EntityZombie extends EntityMonster { + entitychicken1.setChickenJockey(true); + this.startRiding(entitychicken1); + worldaccess.addEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit ++ } // Purpur + } + } + } +@@ -564,7 +580,7 @@ public class EntityZombie extends EntityMonster { + + public static class GroupDataZombie implements GroupDataEntity { + +- public final boolean a; ++ public final boolean a; public boolean isBaby() { return a; } // Purpur - OBFHELPER + public final boolean b; + + public GroupDataZombie(boolean flag, boolean flag1) { +diff --git a/src/main/java/net/minecraft/server/EntityZombieHusk.java b/src/main/java/net/minecraft/server/EntityZombieHusk.java +index 2d2830adc5229a8db7fc4b1170ea4c6f263e7182..ce6d79780197eb9300130036a8ed84648a17f9cf 100644 +--- a/src/main/java/net/minecraft/server/EntityZombieHusk.java ++++ b/src/main/java/net/minecraft/server/EntityZombieHusk.java +@@ -8,6 +8,23 @@ public class EntityZombieHusk extends EntityZombie { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean jockeyOnlyBaby() { ++ return world.purpurConfig.huskJockeyOnlyBaby; ++ } ++ ++ @Override ++ public double jockeyChance() { ++ return world.purpurConfig.huskJockeyChance; ++ } ++ ++ @Override ++ public boolean jockeyTryExistingChickens() { ++ return world.purpurConfig.huskJockeyTryExistingChickens; ++ } ++ // Purpur end ++ + public static boolean a(EntityTypes entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { + return b(entitytypes, worldaccess, enummobspawn, blockposition, random) && (enummobspawn == EnumMobSpawn.SPAWNER || worldaccess.e(blockposition)); + } +diff --git a/src/main/java/net/minecraft/server/EntityZombieVillager.java b/src/main/java/net/minecraft/server/EntityZombieVillager.java +index e021c43b43839c00f81a01bfbec59c546d61ba50..505c83f3e3ad61c2d4d40c4df017e1f7a9a3ad8c 100644 +--- a/src/main/java/net/minecraft/server/EntityZombieVillager.java ++++ b/src/main/java/net/minecraft/server/EntityZombieVillager.java +@@ -28,6 +28,23 @@ public class EntityZombieVillager extends EntityZombie implements VillagerDataHo + this.setVillagerData(this.getVillagerData().withProfession((VillagerProfession) IRegistry.VILLAGER_PROFESSION.a(this.random))); + } + ++ // Purpur start ++ @Override ++ public boolean jockeyOnlyBaby() { ++ return world.purpurConfig.zombieVillagerJockeyOnlyBaby; ++ } ++ ++ @Override ++ public double jockeyChance() { ++ return world.purpurConfig.zombieVillagerJockeyChance; ++ } ++ ++ @Override ++ public boolean jockeyTryExistingChickens() { ++ return world.purpurConfig.zombieVillagerJockeyTryExistingChickens; ++ } ++ // Purpur end ++ + @Override + protected void initDatawatcher() { + super.initDatawatcher(); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 75250392135f416e766e44ffdad7eecd06a89ae5..54afa5afa20f317d55b36df0aa325e0ac3e814a7 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -263,6 +263,15 @@ public class PurpurWorldConfig { + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + } + ++ public boolean drownedJockeyOnlyBaby = true; ++ public double drownedJockeyChance = 0.05D; ++ public boolean drownedJockeyTryExistingChickens = true; ++ private void drownedSettings() { ++ drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); ++ drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); ++ drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); ++ } ++ + public boolean enderDragonAlwaysDropsEggBlock = false; + public boolean enderDragonAlwaysDropsFullExp = false; + private void enderDragonSettings() { +@@ -302,6 +311,15 @@ public class PurpurWorldConfig { + giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); + } + ++ public boolean huskJockeyOnlyBaby = true; ++ public double huskJockeyChance = 0.05D; ++ public boolean huskJockeyTryExistingChickens = true; ++ private void huskSettings() { ++ huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); ++ huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); ++ huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); ++ } ++ + public double illusionerMovementSpeed = 0.5D; + public double illusionerFollowRange = 18.0D; + public double illusionerMaxHealth = 32.0D; +@@ -377,8 +395,35 @@ public class PurpurWorldConfig { + witherSkeletonTakesWitherDamage = getBoolean("mobs.wither_skeleton.takes-wither-damage", witherSkeletonTakesWitherDamage); + } + ++ public boolean zombieJockeyOnlyBaby = true; ++ public double zombieJockeyChance = 0.05D; ++ public boolean zombieJockeyTryExistingChickens = true; ++ private void zombieSettings() { ++ zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); ++ zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); ++ zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); ++ } ++ + public double zombieHorseSpawnChance = 0.0D; + private void zombieHorseSettings() { + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); + } ++ ++ public boolean zombifiedPiglinJockeyOnlyBaby = true; ++ public double zombifiedPiglinJockeyChance = 0.05D; ++ public boolean zombifiedPiglinJockeyTryExistingChickens = true; ++ private void zombifiedPiglinSettings() { ++ zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); ++ zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); ++ zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); ++ } ++ ++ public boolean zombieVillagerJockeyOnlyBaby = true; ++ public double zombieVillagerJockeyChance = 0.05D; ++ public boolean zombieVillagerJockeyTryExistingChickens = true; ++ private void zombieVillagerSettings() { ++ zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); ++ zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); ++ zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); ++ } + } diff --git a/patches/Purpur/patches/server/0080-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/Purpur/patches/server/0080-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch new file mode 100644 index 00000000..346a6e9b --- /dev/null +++ b/patches/Purpur/patches/server/0080-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch @@ -0,0 +1,342 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 9 May 2019 18:26:06 -0500 +Subject: [PATCH] Phantoms attracted to crystals and crystals shoot phantoms + + +diff --git a/src/main/java/net/minecraft/server/DamageSource.java b/src/main/java/net/minecraft/server/DamageSource.java +index 6fe5678cffc2487fe00c953d772f764bb37a4b11..bd0267ee4b3782f6d1ec39cba7966ba4f62f1adf 100644 +--- a/src/main/java/net/minecraft/server/DamageSource.java ++++ b/src/main/java/net/minecraft/server/DamageSource.java +@@ -88,6 +88,7 @@ public class DamageSource { + return (new EntityDamageSourceIndirect("thrown", entity, entity1)).c(); + } + ++ public static DamageSource indirectMagic(Entity entity, @Nullable Entity owner) { return c(entity, owner); } // Purpur - OBFHELPER + public static DamageSource c(Entity entity, @Nullable Entity entity1) { + return (new EntityDamageSourceIndirect("indirectMagic", entity, entity1)).setIgnoreArmor().setMagic(); + } +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index f5159b76dee6127d7db2addfcb512b5b1f5b9c41..905e3a98fe900c82053b1514122bfe6cf1000dfa 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -2138,8 +2138,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return this.a(new ItemStack(imaterial), (float) i); + } + +- @Nullable +- public EntityItem a(ItemStack itemstack) { ++ @Nullable public EntityItem dropItem(ItemStack itemstack) { return this.a(itemstack); } // Purpur - OBFHELPER ++ @Nullable public EntityItem a(ItemStack itemstack) { + return this.a(itemstack, 0.0F); + } + +diff --git a/src/main/java/net/minecraft/server/EntityEnderCrystal.java b/src/main/java/net/minecraft/server/EntityEnderCrystal.java +index 362ca695d88a7c788421cc557b8110b954d8153a..1942fae27051af79b6eb1d790a219da100bf889e 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderCrystal.java ++++ b/src/main/java/net/minecraft/server/EntityEnderCrystal.java +@@ -13,6 +13,12 @@ public class EntityEnderCrystal extends Entity { + private static final DataWatcherObject> c = DataWatcher.a(EntityEnderCrystal.class, DataWatcherRegistry.m); + private static final DataWatcherObject d = DataWatcher.a(EntityEnderCrystal.class, DataWatcherRegistry.i); + public int b; ++ // Purpur start ++ private EntityPhantom targetPhantom; ++ private int phantomBeamTicks = 0; ++ private int phantomDamageCooldown = 0; ++ private int idleCooldown = 0; ++ // Purpur end + + public EntityEnderCrystal(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -51,7 +57,50 @@ public class EntityEnderCrystal extends Entity { + } + } + ++ // Purpur start ++ if (world.purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) { ++ return; // on cooldown ++ } ++ ++ if (targetPhantom == null) { ++ for (EntityPhantom phantom : world.getEntitiesInAABB(EntityPhantom.class, getBoundingBox().grow(world.purpurConfig.phantomAttackedByCrystalRadius))) { ++ if (phantom.hasLineOfSight(this)) { ++ attackPhantom(phantom); ++ break; ++ } ++ } ++ } else { ++ setBeamTarget(new BlockPosition(targetPhantom).add(0, -2, 0)); ++ if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) { ++ phantomDamageCooldown--; ++ if (targetPhantom.hasLineOfSight(this)) { ++ if (phantomDamageCooldown <= 0) { ++ phantomDamageCooldown = 20; ++ targetPhantom.damageEntity(DamageSource.indirectMagic(this, this), world.purpurConfig.phantomAttackedByCrystalDamage); ++ } ++ } else { ++ forgetPhantom(); // no longer in sight ++ } ++ } else { ++ forgetPhantom(); // attacked long enough ++ } ++ } ++ } ++ ++ private void attackPhantom(EntityPhantom phantom) { ++ phantomDamageCooldown = 0; ++ phantomBeamTicks = 60; ++ targetPhantom = phantom; ++ } ++ ++ private void forgetPhantom() { ++ targetPhantom = null; ++ setBeamTarget(null); ++ phantomBeamTicks = 0; ++ phantomDamageCooldown = 0; ++ idleCooldown = 60; + } ++ // Purpur end + + @Override + protected void saveData(NBTTagCompound nbttagcompound) { +diff --git a/src/main/java/net/minecraft/server/EntityPhantom.java b/src/main/java/net/minecraft/server/EntityPhantom.java +index 3a70900c73e3a6c4f40cf74406534f4bfd3eb67b..dfce36368da9bdd9285c490a802f7a0cc4a339f6 100644 +--- a/src/main/java/net/minecraft/server/EntityPhantom.java ++++ b/src/main/java/net/minecraft/server/EntityPhantom.java +@@ -9,9 +9,10 @@ import javax.annotation.Nullable; + public class EntityPhantom extends EntityFlying implements IMonster { + + private static final DataWatcherObject b = DataWatcher.a(EntityPhantom.class, DataWatcherRegistry.b); +- private Vec3D c; +- private BlockPosition d; +- private EntityPhantom.AttackPhase bo; ++ private Vec3D c; public void setHomeOffset(Vec3D offset) { this.c = offset; } public Vec3D getHomeOffset() { return this.c; } // Purpur - OBFHELPER ++ private BlockPosition d; public void setHome(BlockPosition home) { this.d = home; } public BlockPosition getHome() { return this.d; } // Purpur - OBFHELPER ++ private EntityPhantom.AttackPhase bo; public AttackPhase getAttackPhase() { return this.bo; } // Purpur - OBFHELPER ++ private Vec3D crystalPosition; // Purpur + + public EntityPhantom(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -30,11 +31,37 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + protected void initPathfinder() { +- this.goalSelector.a(1, new EntityPhantom.c()); +- this.goalSelector.a(2, new EntityPhantom.i()); +- this.goalSelector.a(3, new EntityPhantom.e()); +- this.targetSelector.a(1, new EntityPhantom.b()); ++ // Purpur start ++ if (world.purpurConfig.phantomOrbitCrystalRadius > 0) { ++ this.goalSelector.a(1, new FindCrystalGoal(this)); ++ this.goalSelector.a(2, new OrbitCrystalGoal(this)); ++ } ++ this.goalSelector.a(3, new EntityPhantom.c()); // PickAttackGoal ++ this.goalSelector.a(4, new EntityPhantom.i()); // SweepAttackGoal ++ this.goalSelector.a(5, new EntityPhantom.e()); // OrbitPointGoal ++ this.targetSelector.a(1, new EntityPhantom.b()); // AttackPlayer Goal ++ // Purpur end ++ } ++ ++ // Purpur start ++ @Override ++ protected LootTableInfo.Builder a(boolean wasRecentlyHit, DamageSource damagesource) { // dropLoot ++ boolean dropped = false; ++ if (killer == null && damagesource.getEntity() instanceof EntityEnderCrystal) { ++ if (random.nextInt(5) < 1) { // 1 out of 5 chance (20%) ++ dropped = dropItem(new ItemStack(Items.PHANTOM_MEMBRANE)) != null; ++ } ++ } ++ if (!dropped) { ++ return super.a(wasRecentlyHit, damagesource); // dropLoot ++ } ++ return new LootTableInfo.Builder((WorldServer) world); ++ } ++ ++ public boolean isCirclingCrystal() { ++ return crystalPosition != null; + } ++ // Purpur end + + @Override + protected void initDatawatcher() { +@@ -198,6 +225,136 @@ public class EntityPhantom extends EntityFlying implements IMonster { + } + // Paper end + ++ // Purpur start ++ class FindCrystalGoal extends PathfinderGoal { ++ private final EntityPhantom phantom; ++ private EntityEnderCrystal crystal; ++ private java.util.Comparator comparator; ++ ++ FindCrystalGoal(EntityPhantom phantom) { ++ this.phantom = phantom; ++ comparator = java.util.Comparator.comparingDouble(phantom::h); ++ this.a(EnumSet.of(PathfinderGoal.Type.LOOK)); ++ } ++ ++ @Override ++ public boolean a() { // shouldExecute ++ double range = maxTargetRange(); ++ List crystals = world.getEntitiesInAABB(EntityEnderCrystal.class, phantom.getBoundingBox().grow(range)); ++ if (crystals.isEmpty()) { ++ return false; ++ } ++ crystals.sort(comparator); ++ crystal = crystals.get(0); ++ if (phantom.getDistanceSquared(crystal) > range * range) { ++ crystal = null; ++ return false; ++ } ++ return true; ++ } ++ ++ @Override ++ public boolean b() { // shouldContinueExecuting ++ if (crystal == null || !crystal.isAlive()) { ++ return false; ++ } ++ double range = maxTargetRange(); ++ return phantom.getDistanceSquared(crystal) <= (range * range) * 2; ++ } ++ ++ @Override ++ public void c() { // startExecuting ++ phantom.crystalPosition = new Vec3D(crystal.locX(), crystal.locY() + (phantom.getRandom().nextInt(10) + 10), crystal.locZ()); ++ } ++ ++ @Override ++ public void d() { // resetTask ++ crystal = null; ++ phantom.crystalPosition = null; ++ super.d(); ++ } ++ ++ private double maxTargetRange() { ++ return phantom.world.purpurConfig.phantomOrbitCrystalRadius; ++ } ++ ++ class DistanceComparator implements java.util.Comparator { ++ private final Entity entity; ++ ++ public DistanceComparator(Entity entity) { ++ this.entity = entity; ++ } ++ ++ public int compare(Entity entity1, Entity entity2) { ++ return Double.compare(entity.getDistanceSquared(entity1), entity.getDistanceSquared(entity2)); ++ } ++ } ++ } ++ ++ class OrbitCrystalGoal extends PathfinderGoal { ++ private final EntityPhantom phantom; ++ private float offset; ++ private float radius; ++ private float verticalChange; ++ private float direction; ++ ++ OrbitCrystalGoal(EntityPhantom phantom) { ++ this.phantom = phantom; ++ this.a(EnumSet.of(PathfinderGoal.Type.MOVE)); ++ } ++ ++ @Override ++ public boolean a() { // shouldExecute ++ return phantom.isCirclingCrystal(); ++ } ++ ++ @Override ++ public void c() { // startExecuting ++ this.radius = 5.0F + phantom.random.nextFloat() * 10.0F; ++ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; ++ this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F; ++ updateOffset(); ++ } ++ ++ @Override ++ public void e() { // tick ++ if (phantom.random.nextInt(350) == 0) { ++ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; ++ } ++ if (phantom.random.nextInt(250) == 0) { ++ ++this.radius; ++ if (this.radius > 15.0F) { ++ this.radius = 5.0F; ++ this.direction = -this.direction; ++ } ++ } ++ if (phantom.random.nextInt(450) == 0) { ++ this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F; ++ updateOffset(); ++ } ++ if (phantom.getHomeOffset().c(phantom.locX(), phantom.locY(), phantom.locZ()) < 4.0D) { ++ updateOffset(); ++ } ++ if (phantom.getHomeOffset().y < phantom.locY() && !phantom.world.isEmpty((new BlockPosition(phantom)).down(1))) { ++ this.verticalChange = Math.max(1.0F, this.verticalChange); ++ updateOffset(); ++ } ++ if (phantom.getHomeOffset().y > phantom.locY() && !phantom.world.isEmpty((new BlockPosition(phantom)).up(1))) { ++ this.verticalChange = Math.min(-1.0F, this.verticalChange); ++ updateOffset(); ++ } ++ } ++ ++ private void updateOffset() { ++ this.offset += this.direction * 15.0F * 0.017453292F; ++ phantom.setHomeOffset(phantom.crystalPosition.add( ++ this.radius * MathHelper.cos(this.offset), ++ -4.0F + this.verticalChange, ++ this.radius * MathHelper.sin(this.offset))); ++ } ++ } ++ // Purpur end ++ + class b extends PathfinderGoal { + + private final PathfinderTargetCondition b; +@@ -210,6 +367,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public boolean a() { ++ if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag + if (this.c > 0) { + --this.c; + return false; +@@ -238,6 +396,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public boolean b() { ++ if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag + EntityLiving entityliving = EntityPhantom.this.getGoalTarget(); + + return entityliving != null ? EntityPhantom.this.a(entityliving, PathfinderTargetCondition.a) : false; +@@ -252,6 +411,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public boolean a() { ++ if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag + EntityLiving entityliving = EntityPhantom.this.getGoalTarget(); + + return entityliving != null ? EntityPhantom.this.a(EntityPhantom.this.getGoalTarget(), PathfinderTargetCondition.a) : false; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 54afa5afa20f317d55b36df0aa325e0ac3e814a7..4d12719b4b695328e364d4c39f5d54cd00522f09 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -339,6 +339,15 @@ public class PurpurWorldConfig { + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + } + ++ public double phantomAttackedByCrystalRadius = 0.0D; ++ public float phantomAttackedByCrystalDamage = 1.0F; ++ public double phantomOrbitCrystalRadius = 0.0D; ++ private void phantomSettings() { ++ phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); ++ phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); ++ phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); ++ } ++ + public boolean pigGiveSaddleBack = false; + private void pigSettings() { + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); diff --git a/patches/Purpur/patches/server/0081-Add-phantom-spawning-options.patch b/patches/Purpur/patches/server/0081-Add-phantom-spawning-options.patch new file mode 100644 index 00000000..bdc5f5db --- /dev/null +++ b/patches/Purpur/patches/server/0081-Add-phantom-spawning-options.patch @@ -0,0 +1,285 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 Jul 2020 00:03:52 -0500 +Subject: [PATCH] Add phantom spawning options + + +diff --git a/src/main/java/net/minecraft/server/DifficultyDamageScaler.java b/src/main/java/net/minecraft/server/DifficultyDamageScaler.java +index 75745af3435434bddd420536ca3bbea3a9f181d6..84c0ec1b50758b433980765d087e15d8a8eaaaf7 100644 +--- a/src/main/java/net/minecraft/server/DifficultyDamageScaler.java ++++ b/src/main/java/net/minecraft/server/DifficultyDamageScaler.java +@@ -13,6 +13,7 @@ public class DifficultyDamageScaler { + this.b = this.a(enumdifficulty, i, j, f); + } + ++ public EnumDifficulty getGlobalDifficulty() { return a(); } // Purpur - OBFHELPER + public EnumDifficulty a() { + return this.a; + } +@@ -21,6 +22,7 @@ public class DifficultyDamageScaler { + return this.b; + } + ++ public boolean isHarderThan(float f) { return a(f); } // Purpur - OBFHELPER + public boolean a(float f) { + return this.b > f; + } +diff --git a/src/main/java/net/minecraft/server/EnumDifficulty.java b/src/main/java/net/minecraft/server/EnumDifficulty.java +index e0e72779c0f4e4856ed5682122fedf0321db11cb..386eae71a146e06ca60f8bf11429fe2131232a21 100644 +--- a/src/main/java/net/minecraft/server/EnumDifficulty.java ++++ b/src/main/java/net/minecraft/server/EnumDifficulty.java +@@ -19,6 +19,7 @@ public enum EnumDifficulty { + this.g = s; + } + ++ public int getId() { return a(); } // Purpur - OBFHELPER + public int a() { + return this.f; + } +diff --git a/src/main/java/net/minecraft/server/IBlockLightAccess.java b/src/main/java/net/minecraft/server/IBlockLightAccess.java +index 03a89301f2b3d29e39c6c9d05ee072527f652efc..fa5eb188da64cc1409cc614515e8c70b0a341dd0 100644 +--- a/src/main/java/net/minecraft/server/IBlockLightAccess.java ++++ b/src/main/java/net/minecraft/server/IBlockLightAccess.java +@@ -12,6 +12,7 @@ public interface IBlockLightAccess extends IBlockAccess { + return this.e().b(blockposition, i); + } + ++ default boolean isSkyVisible(BlockPosition blockposition) { return e(blockposition); } // Purpur - OBFHELPER + default boolean e(BlockPosition blockposition) { + return this.getBrightness(EnumSkyBlock.SKY, blockposition) >= this.K(); + } +diff --git a/src/main/java/net/minecraft/server/MobSpawnerPhantom.java b/src/main/java/net/minecraft/server/MobSpawnerPhantom.java +index 4e3f01bc79b6ed2a322155f29f1d0dcf298c8b82..ac1ea2f0c15bccf94f203194a5a7b10ec125ffb8 100644 +--- a/src/main/java/net/minecraft/server/MobSpawnerPhantom.java ++++ b/src/main/java/net/minecraft/server/MobSpawnerPhantom.java +@@ -1,85 +1,99 @@ + package net.minecraft.server; + +-import java.util.Iterator; +-import java.util.Random; ++import com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent; ++import org.bukkit.event.entity.CreatureSpawnEvent; + + public class MobSpawnerPhantom implements MobSpawner { +- +- private int a; ++ private int spawnDelay; + + public MobSpawnerPhantom() {} + + @Override +- public int a(WorldServer worldserver, boolean flag, boolean flag1) { +- if (!flag) { ++ public int a(WorldServer world, boolean allowMonsters, boolean allowAnimals) { ++ // Purpur start - rewrite entire thing ++ if (!allowMonsters) { ++ return 0; ++ } ++ ++ if (!world.getGameRules().getBoolean(GameRules.DO_INSOMNIA)) { + return 0; +- } else if (!worldserver.getGameRules().getBoolean(GameRules.DO_INSOMNIA)) { ++ } ++ ++ --this.spawnDelay; ++ if (this.spawnDelay > 0) { + return 0; +- } else { +- Random random = worldserver.random; +- +- --this.a; +- if (this.a > 0) { +- return 0; +- } else { +- this.a += (60 + random.nextInt(60)) * 20; +- if (worldserver.c() < 5 && worldserver.getDimensionManager().hasSkyLight()) { +- return 0; +- } else { +- int i = 0; +- Iterator iterator = worldserver.getPlayers().iterator(); +- +- while (iterator.hasNext()) { +- EntityHuman entityhuman = (EntityHuman) iterator.next(); +- +- if (!entityhuman.isSpectator() && (!worldserver.paperConfig.phantomIgnoreCreative || !entityhuman.isCreative())) { // Paper +- BlockPosition blockposition = entityhuman.getChunkCoordinates(); +- +- if (!worldserver.getDimensionManager().hasSkyLight() || blockposition.getY() >= worldserver.getSeaLevel() && worldserver.e(blockposition)) { +- DifficultyDamageScaler difficultydamagescaler = worldserver.getDamageScaler(blockposition); +- +- if (difficultydamagescaler.a(random.nextFloat() * 3.0F)) { +- ServerStatisticManager serverstatisticmanager = ((EntityPlayer) entityhuman).getStatisticManager(); +- int j = MathHelper.clamp(serverstatisticmanager.getStatisticValue(StatisticList.CUSTOM.b(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); +- boolean flag2 = true; +- +- if (random.nextInt(j) >= 72000) { +- BlockPosition blockposition1 = blockposition.up(20 + random.nextInt(15)).east(-10 + random.nextInt(21)).south(-10 + random.nextInt(21)); +- IBlockData iblockdata = worldserver.getType(blockposition1); +- Fluid fluid = worldserver.getFluid(blockposition1); +- +- if (SpawnerCreature.a((IBlockAccess) worldserver, blockposition1, iblockdata, fluid, EntityTypes.PHANTOM)) { +- GroupDataEntity groupdataentity = null; +- int k = 1 + random.nextInt(difficultydamagescaler.a().a() + 1); +- +- for (int l = 0; l < k; ++l) { +- // Paper start +- com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(MCUtil.toLocation(worldserver, blockposition1), ((EntityPlayer) entityhuman).getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); +- if (!event.callEvent()) { +- if (event.shouldAbortSpawn()) { +- break; +- } +- continue; +- } +- // Paper end +- EntityPhantom entityphantom = (EntityPhantom) EntityTypes.PHANTOM.a((World) worldserver); +- entityphantom.spawningEntity = entityhuman.uniqueID; // Paper +- entityphantom.setPositionRotation(blockposition1, 0.0F, 0.0F); +- groupdataentity = entityphantom.prepare(worldserver, difficultydamagescaler, EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null); +- worldserver.addAllEntities(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit +- } +- +- i += k; +- } +- } +- } +- } +- } ++ } ++ this.spawnDelay += world.purpurConfig.phantomSpawnDelayMin + world.random.nextInt(world.purpurConfig.phantomSpawnDelayMax - world.purpurConfig.phantomSpawnDelayMin); ++ ++ if (!world.getDimensionManager().hasSkyLight() || world.getSkyDarkness() < world.purpurConfig.phantomSpawnMinSkyDarkness) { ++ return 0; ++ } ++ ++ int numberSpawnsAttempted = 0; ++ for (EntityPlayer player : world.getPlayers()) { ++ if (player.isSpectator() || (player.isCreative() && world.paperConfig.phantomIgnoreCreative)) { // Paper ++ continue; ++ } ++ ++ BlockPosition playerPos = player.getChunkCoordinates(); ++ if (playerPos.getY() < world.getSeaLevel() && world.purpurConfig.phantomSpawnOnlyAboveSeaLevel) { ++ continue; ++ } ++ ++ if (!world.isSkyVisible(playerPos) && world.purpurConfig.phantomSpawnOnlyWithVisibleSky) { ++ continue; ++ } ++ ++ DifficultyDamageScaler dmgScaler = world.getDamageScaler(playerPos); ++ if (!dmgScaler.isHarderThan(world.random.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) { ++ continue; ++ } ++ ++ ServerStatisticManager stats = player.getStatisticManager(); ++ int timeSinceRest = MathHelper.clamp(stats.getStatisticValue(StatisticList.CUSTOM.get(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); ++ if (world.random.nextInt(timeSinceRest) < world.purpurConfig.phantomSpawnMinTimeSinceSlept) { ++ continue; ++ } ++ ++ BlockPosition spawnPos = playerPos ++ .up(world.purpurConfig.phantomSpawnMinOverhead + world.random.nextInt(world.purpurConfig.phantomSpawnMaxOverhead - world.purpurConfig.phantomSpawnMinOverhead)) ++ .east(-world.purpurConfig.phantomSpawnOverheadRadius + world.random.nextInt(world.purpurConfig.phantomSpawnOverheadRadius + 1)) ++ .south(-world.purpurConfig.phantomSpawnOverheadRadius + world.random.nextInt(world.purpurConfig.phantomSpawnOverheadRadius + 1)); ++ ++ if (!SpawnerCreature.canSpawn(world, spawnPos, world.getType(spawnPos), world.getFluid(spawnPos), EntityTypes.PHANTOM)) { ++ continue; ++ } ++ ++ int difficulty = dmgScaler.getGlobalDifficulty().getId(); ++ int spawnAttempts = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficulty : world.purpurConfig.phantomSpawnMaxPerAttempt) + world.purpurConfig.phantomSpawnMinPerAttempt); ++ ++ GroupDataEntity groupData = null; ++ for (int count = 0; count < spawnAttempts; ++count) { ++ // Paper start ++ PhantomPreSpawnEvent event = new PhantomPreSpawnEvent(MCUtil.toLocation(world, spawnPos), player.getBukkitEntity(), CreatureSpawnEvent.SpawnReason.NATURAL); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ break; + } ++ continue; ++ } ++ // Paper end + +- return i; ++ EntityPhantom phantom = EntityTypes.PHANTOM.create(world); ++ if (phantom == null) { ++ continue; + } ++ ++ phantom.spawningEntity = player.uniqueID; // Paper ++ phantom.setPositionRotation(spawnPos, 0.0F, 0.0F); ++ groupData = phantom.prepare(world, dmgScaler, EnumMobSpawn.NATURAL, groupData, null); ++ world.addEntity(phantom, CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit + } ++ ++ numberSpawnsAttempted += spawnAttempts; + } ++ ++ return numberSpawnsAttempted; ++ // Purpur end - rewrite entire thing + } + } +diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java +index 661ad8f8e67046211e001ea40d97660d7c88f8e5..ee91c33a7a2edca02caf5c71fd6429f97eac7e2d 100644 +--- a/src/main/java/net/minecraft/server/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/server/SpawnerCreature.java +@@ -385,6 +385,7 @@ public final class SpawnerCreature { + return new BlockPosition(i, l, j); + } + ++ public static boolean canSpawn(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, EntityTypes entitytypes) { return a(iblockaccess, blockposition, iblockdata, fluid, entitytypes); } // Purpur - OBFHELPER + public static boolean a(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, EntityTypes entitytypes) { + return iblockdata.r(iblockaccess, blockposition) ? false : (iblockdata.isPowerSource() ? false : (!fluid.isEmpty() ? false : (iblockdata.a((Tag) TagsBlock.PREVENT_MOB_SPAWNING_INSIDE) ? false : !entitytypes.a(iblockdata)))); + } +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index fb650c09dbcefa0ff021f7c508ff6811a48bee7a..68cce071c740c90149225b4a28af849237b4492b 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1567,6 +1567,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return new DifficultyDamageScaler(this.getDifficulty(), this.getDayTime(), i, f); + } + ++ public int getSkyDarkness() { return c(); } // Purpur - OBFHELPER + @Override + public int c() { + return this.d; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 4d12719b4b695328e364d4c39f5d54cd00522f09..7b3714ff8b7e6dafe1475c9fc176a69d60e8eef7 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -342,10 +342,34 @@ public class PurpurWorldConfig { + public double phantomAttackedByCrystalRadius = 0.0D; + public float phantomAttackedByCrystalDamage = 1.0F; + public double phantomOrbitCrystalRadius = 0.0D; ++ public int phantomSpawnDelayMin = 1200; ++ public int phantomSpawnDelayMax = 2400; ++ public int phantomSpawnMinSkyDarkness = 5; ++ public boolean phantomSpawnOnlyAboveSeaLevel = true; ++ public boolean phantomSpawnOnlyWithVisibleSky = true; ++ public double phantomSpawnLocalDifficultyChance = 3.0D; ++ public int phantomSpawnMinTimeSinceSlept = 72000; ++ public int phantomSpawnMinOverhead = 20; ++ public int phantomSpawnMaxOverhead = 35; ++ public int phantomSpawnOverheadRadius = 10; ++ public int phantomSpawnMinPerAttempt = 1; ++ public int phantomSpawnMaxPerAttempt = -1; + private void phantomSettings() { + phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); + phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); + phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); ++ phantomSpawnDelayMin = getInt("mobs.phantom.spawn.delay.min", phantomSpawnDelayMin); ++ phantomSpawnDelayMax = getInt("mobs.phantom.spawn.delay.max", phantomSpawnDelayMax); ++ phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness); ++ phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel); ++ phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky); ++ phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance); ++ phantomSpawnMinTimeSinceSlept = getInt("mobs.phantom.spawn.min-time-since-slept", phantomSpawnMinTimeSinceSlept); ++ phantomSpawnMinOverhead = getInt("mobs.phantom.spawn.overhead.min", phantomSpawnMinOverhead); ++ phantomSpawnMaxOverhead = getInt("mobs.phantom.spawn.overhead.max", phantomSpawnMaxOverhead); ++ phantomSpawnOverheadRadius = getInt("mobs.phantom.spawn.overhead.radius", phantomSpawnOverheadRadius); ++ phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); ++ phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); + } + + public boolean pigGiveSaddleBack = false; diff --git a/patches/Purpur/patches/server/0082-Implement-bed-explosion-options.patch b/patches/Purpur/patches/server/0082-Implement-bed-explosion-options.patch new file mode 100644 index 00000000..934fd52d --- /dev/null +++ b/patches/Purpur/patches/server/0082-Implement-bed-explosion-options.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 4 Jul 2020 13:12:43 -0500 +Subject: [PATCH] Implement bed explosion options + + +diff --git a/src/main/java/net/minecraft/server/BlockBed.java b/src/main/java/net/minecraft/server/BlockBed.java +index 24a5c64b65f0098b1395d352cc2f99bbbbaf9c94..738535d01adf8bfd895b5057e595042253f32f87 100644 +--- a/src/main/java/net/minecraft/server/BlockBed.java ++++ b/src/main/java/net/minecraft/server/BlockBed.java +@@ -88,7 +88,7 @@ public class BlockBed extends BlockFacingHorizontal implements ITileEntity { + world.a(blockposition1, false); + } + +- world.createExplosion((Entity) null, DamageSource.a(), (ExplosionDamageCalculator) null, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, 5.0F, true, Explosion.Effect.DESTROY); ++ if (world.purpurConfig.bedExplode) world.createExplosion(null, DamageSource.a(), null, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur + return EnumInteractionResult.SUCCESS; + } + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 7b3714ff8b7e6dafe1475c9fc176a69d60e8eef7..240f1592b04848fc0c45c5ffe4c59145e043c16a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -2,6 +2,7 @@ package net.pl3x.purpur; + + import net.minecraft.server.Block; + import net.minecraft.server.Blocks; ++import net.minecraft.server.Explosion; + import net.minecraft.server.IRegistry; + import net.minecraft.server.Item; + import net.minecraft.server.Items; +@@ -12,6 +13,7 @@ import java.util.ArrayList; + import java.util.HashMap; + import java.util.List; + import java.util.Map; ++import java.util.logging.Level; + + import static net.pl3x.purpur.PurpurConfig.log; + +@@ -200,6 +202,22 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean bedExplode = true; ++ public double bedExplosionPower = 5.0D; ++ public boolean bedExplosionFire = true; ++ public Explosion.Effect bedExplosionEffect = Explosion.Effect.DESTROY; ++ private void bedSettings() { ++ bedExplode = getBoolean("blocks.bed.explode", bedExplode); ++ bedExplosionPower = getDouble("blocks.bed.explosion-power", bedExplosionPower); ++ bedExplosionFire = getBoolean("blocks.bed.explosion-fire", bedExplosionFire); ++ try { ++ bedExplosionEffect = Explosion.Effect.valueOf(getString("blocks.bed.explosion-effect", bedExplosionEffect.name())); ++ } catch (IllegalArgumentException e) { ++ log(Level.SEVERE, "Unknown value for `blocks.bed.explosion-effect`! Using default of `DESTROY`"); ++ bedExplosionEffect = Explosion.Effect.DESTROY; ++ } ++ } ++ + public boolean dispenserApplyCursedArmor = true; + private void dispenserSettings() { + dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor); diff --git a/patches/Purpur/patches/server/0083-Implement-respawn-anchor-explosion-options.patch b/patches/Purpur/patches/server/0083-Implement-respawn-anchor-explosion-options.patch new file mode 100644 index 00000000..7227c91f --- /dev/null +++ b/patches/Purpur/patches/server/0083-Implement-respawn-anchor-explosion-options.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 4 Jul 2020 13:23:19 -0500 +Subject: [PATCH] Implement respawn anchor explosion options + + +diff --git a/src/main/java/net/minecraft/server/BlockRespawnAnchor.java b/src/main/java/net/minecraft/server/BlockRespawnAnchor.java +index 21c5ad83a95bcd23071423c0e8336656b760fcde..521d161a3d015b641b61de21401c79ac20eb73a7 100644 +--- a/src/main/java/net/minecraft/server/BlockRespawnAnchor.java ++++ b/src/main/java/net/minecraft/server/BlockRespawnAnchor.java +@@ -83,10 +83,7 @@ public class BlockRespawnAnchor extends Block { + + private void d(IBlockData iblockdata, World world, final BlockPosition blockposition) { + world.a(blockposition, false); +- Stream stream = EnumDirection.EnumDirectionLimit.HORIZONTAL.a(); +- +- blockposition.getClass(); +- boolean flag = stream.map(blockposition::shift).anyMatch((blockposition1) -> { ++ boolean flag = EnumDirection.EnumDirectionLimit.HORIZONTAL.a().map(blockposition::shift).anyMatch((blockposition1) -> { // Purpur - decompile error + return a(blockposition1, world); + }); + final boolean flag1 = flag || world.getFluid(blockposition.up()).a((Tag) TagsFluid.WATER); +@@ -97,7 +94,7 @@ public class BlockRespawnAnchor extends Block { + } + }; + +- world.createExplosion((Entity) null, DamageSource.a(), explosiondamagecalculator, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, 5.0F, true, Explosion.Effect.DESTROY); ++ if (world.purpurConfig.respawnAnchorExplode) world.createExplosion(null, DamageSource.a(), explosiondamagecalculator, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // Purpur + } + + public static boolean a(World world) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 240f1592b04848fc0c45c5ffe4c59145e043c16a..3d6f4de01409c70c5f4d2fcd7a52d158aa0fdf80 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -239,6 +239,22 @@ public class PurpurWorldConfig { + lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); + } + ++ public boolean respawnAnchorExplode = true; ++ public double respawnAnchorExplosionPower = 5.0D; ++ public boolean respawnAnchorExplosionFire = true; ++ public Explosion.Effect respawnAnchorExplosionEffect = Explosion.Effect.DESTROY; ++ private void respawnAnchorSettings() { ++ respawnAnchorExplode = getBoolean("blocks.respawn_anchor.explode", respawnAnchorExplode); ++ respawnAnchorExplosionPower = getDouble("blocks.respawn_anchor.explosion-power", respawnAnchorExplosionPower); ++ respawnAnchorExplosionFire = getBoolean("blocks.respawn_anchor.explosion-fire", respawnAnchorExplosionFire); ++ try { ++ respawnAnchorExplosionEffect = Explosion.Effect.valueOf(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name())); ++ } catch (IllegalArgumentException e) { ++ log(Level.SEVERE, "Unknown value for `blocks.respawn_anchor.explosion-effect`! Using default of `DESTROY`"); ++ respawnAnchorExplosionEffect = Explosion.Effect.DESTROY; ++ } ++ } ++ + public boolean signAllowColors = false; + public boolean signRightClickEdit = false; + private void signSettings() { diff --git a/patches/Purpur/patches/server/0084-Add-allow-water-in-end-world-option.patch b/patches/Purpur/patches/server/0084-Add-allow-water-in-end-world-option.patch new file mode 100644 index 00000000..8c9ed235 --- /dev/null +++ b/patches/Purpur/patches/server/0084-Add-allow-water-in-end-world-option.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Jul 2020 23:40:16 -0500 +Subject: [PATCH] Add allow water in end world option + + +diff --git a/src/main/java/net/minecraft/server/BlockIce.java b/src/main/java/net/minecraft/server/BlockIce.java +index aba5c6b8713c04302632bd1d289fd968c9e0607a..b75f6ded5d4bd3150789fba773a2c76c3e55bb90 100644 +--- a/src/main/java/net/minecraft/server/BlockIce.java ++++ b/src/main/java/net/minecraft/server/BlockIce.java +@@ -13,7 +13,7 @@ public class BlockIce extends BlockHalfTransparent { + public void a(World world, EntityHuman entityhuman, BlockPosition blockposition, IBlockData iblockdata, @Nullable TileEntity tileentity, ItemStack itemstack) { + super.a(world, entityhuman, blockposition, iblockdata, tileentity, itemstack); + if (EnchantmentManager.getEnchantmentLevel(Enchantments.SILK_TOUCH, itemstack) == 0) { +- if (world.getDimensionManager().isNether()) { ++ if (world.isNether() || (world.isTheEnd() && !net.pl3x.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur + world.a(blockposition, false); + return; + } +@@ -41,7 +41,7 @@ public class BlockIce extends BlockHalfTransparent { + return; + } + // CraftBukkit end +- if (world.getDimensionManager().isNether()) { ++ if (world.isNether() || (world.isTheEnd() && !net.pl3x.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur + world.a(blockposition, false); + } else { + world.setTypeUpdate(blockposition, Blocks.WATER.getBlockData()); +diff --git a/src/main/java/net/minecraft/server/ItemBucket.java b/src/main/java/net/minecraft/server/ItemBucket.java +index 120bf8436fd82294c339add2e7bff1cda8311aea..848a185c04aa90a62e6bcc49ad68a7489ca28970 100644 +--- a/src/main/java/net/minecraft/server/ItemBucket.java ++++ b/src/main/java/net/minecraft/server/ItemBucket.java +@@ -122,7 +122,7 @@ public class ItemBucket extends Item { + // CraftBukkit end + if (!flag1) { + return movingobjectpositionblock != null && this.a(entityhuman, world, movingobjectpositionblock.getBlockPosition().shift(movingobjectpositionblock.getDirection()), (MovingObjectPositionBlock) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit // Paper - add enumhand +- } else if (world.getDimensionManager().isNether() && this.fluidType.a((Tag) TagsFluid.WATER)) { ++ } else if ((world.isNether() || (world.isTheEnd() && !net.pl3x.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.fluidType.a((Tag) TagsFluid.WATER)) { // Purpur + int i = blockposition.getX(); + int j = blockposition.getY(); + int k = blockposition.getZ(); +@@ -130,7 +130,7 @@ public class ItemBucket extends Item { + world.playSound(entityhuman, blockposition, SoundEffects.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); + + for (int l = 0; l < 8; ++l) { +- world.addParticle(Particles.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D); ++ ((WorldServer) world).sendParticles(null, Particles.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D, true); // Purpur + } + + return true; +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 68cce071c740c90149225b4a28af849237b4492b..f260d01aad4db512952e5a53bf5bc01023bbd43d 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1642,4 +1642,14 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public final boolean isDebugWorld() { + return this.debugWorld; + } ++ ++ // Purpur start ++ public boolean isNether() { ++ return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER; ++ } ++ ++ public boolean isTheEnd() { ++ return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index ebf74ddb9786d3bcccb62c85352bb6c2278433f3..c12146fe8ac1d89c6c1051a8f64ea29a9214e64b 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -1,7 +1,6 @@ + package net.pl3x.purpur; + + import com.google.common.base.Throwables; +-import net.minecraft.server.LocaleLanguage; + import net.minecraft.server.MinecraftServer; + import net.pl3x.purpur.command.PurpurCommand; + import org.bukkit.Bukkit; +@@ -186,6 +185,11 @@ public class PurpurConfig { + dontSendUselessEntityPackets = getBoolean("settings.dont-send-useless-entity-packets", dontSendUselessEntityPackets); + } + ++ public static boolean allowWaterPlacementInTheEnd = true; ++ private static void allowWaterPlacementInEnd() { ++ allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd); ++ } ++ + public static boolean loggerSuppressInitLegacyMaterialError = false; + public static boolean loggerSuppressIgnoredAdvancementWarnings = false; + private static void loggerSettings() { diff --git a/patches/Purpur/patches/server/0085-Allow-color-codes-in-books.patch b/patches/Purpur/patches/server/0085-Allow-color-codes-in-books.patch new file mode 100644 index 00000000..9192e913 --- /dev/null +++ b/patches/Purpur/patches/server/0085-Allow-color-codes-in-books.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 3 Nov 2020 01:25:06 -0600 +Subject: [PATCH] Allow color codes in books + + +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 3fca11948fad36b6adec55883d365e58a17925fa..ffccdc3bf1d28836f4fc2772ebfde843415ea232 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -1029,7 +1029,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + if (itemstack.getItem() == Items.WRITABLE_BOOK) { + NBTTagList nbttaglist = new NBTTagList(); + +- list.stream().map(NBTTagString::a).forEach(nbttaglist::add); ++ boolean hasPerm = getPlayer().hasPermission("purpur.book.color.edit"); // Purpur - edit book ++ list.stream().map(s -> s = color(s, hasPerm, false)).map(NBTTagString::a).forEach(nbttaglist::add); // Purpur - edit book + ItemStack old = itemstack.cloneItemStack(); // CraftBukkit + itemstack.a("pages", (NBTBase) nbttaglist); + CraftEventFactory.handleEditBookEvent(player, i, old, itemstack); // CraftBukkit +@@ -1047,13 +1048,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + itemstack1.setTag(nbttagcompound.clone()); + } + ++ boolean hasPerm = getPlayer().hasPermission("purpur.book.color.edit") || getPlayer().hasPermission("purpur.book.color.sign"); // Purpur + itemstack1.a("author", (NBTBase) NBTTagString.a(this.player.getDisplayName().getString())); +- itemstack1.a("title", (NBTBase) NBTTagString.a(s)); ++ itemstack1.a("title", (NBTBase) NBTTagString.a(color(s, hasPerm))); // Purpur - sign book + NBTTagList nbttaglist = new NBTTagList(); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +- String s1 = (String) iterator.next(); ++ String s1 = color((String) iterator.next(), hasPerm);// Purpur - sign book + ChatComponentText chatcomponenttext = new ChatComponentText(s1); + String s2 = IChatBaseComponent.ChatSerializer.a((IChatBaseComponent) chatcomponenttext); + +@@ -1065,6 +1067,16 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } + ++ // Purpur start ++ private String color(String str, boolean hasPerm) { ++ return color(str, hasPerm, true); ++ } ++ ++ private String color(String str, boolean hasPerm, boolean parseHex) { ++ return hasPerm ? org.bukkit.ChatColor.color(str, parseHex) : str; ++ } ++ // Purpur end ++ + @Override + public void a(PacketPlayInEntityNBTQuery packetplayinentitynbtquery) { + PlayerConnectionUtils.ensureMainThread(packetplayinentitynbtquery, this, this.player.getWorldServer()); diff --git a/patches/Purpur/patches/server/0086-Entity-lifespan.patch b/patches/Purpur/patches/server/0086-Entity-lifespan.patch new file mode 100644 index 00000000..488711da --- /dev/null +++ b/patches/Purpur/patches/server/0086-Entity-lifespan.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 11 Jul 2020 19:41:34 -0500 +Subject: [PATCH] Entity lifespan + + +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index c9791be9385c83c8ab626ff3661b0c6cb45822ad..bacb631e0fc59bbcca9ae1e116ba5b9ed1eccfca 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -53,7 +53,7 @@ public abstract class EntityInsentient extends EntityLiving { + private NBTTagCompound by; + private BlockPosition bz; + private float bA; +- ++ public int ticksSinceLastInteraction; // Purpur + public boolean aware = true; // CraftBukkit + + protected EntityInsentient(EntityTypes entitytypes, World world) { +@@ -205,6 +205,7 @@ public abstract class EntityInsentient extends EntityLiving { + entityliving = null; + } + } ++ if (entityliving instanceof EntityPlayer) this.ticksSinceLastInteraction = 0; // Purpur + this.goalTarget = entityliving; + return true; + // CraftBukkit end +@@ -249,10 +250,35 @@ public abstract class EntityInsentient extends EntityLiving { + this.m(); + this.F(); + } +- ++ incrementTicksSinceLastInteraction(); // Purpur + this.world.getMethodProfiler().exit(); + } + ++ // Purpur start ++ private void incrementTicksSinceLastInteraction() { ++ ++ticksSinceLastInteraction; ++ //if (hasRider()) { ++ // ticksSinceLastInteraction = 0; ++ // return; ++ //} ++ if (world.purpurConfig.entityLifeSpan <= 0) { ++ return; // feature disabled ++ } ++ if (!isTypeNotPersistent(0) || isPersistent() || isSpecialPersistence() || hasCustomName()) { ++ return; // mob persistent ++ } ++ if (ticksSinceLastInteraction > world.purpurConfig.entityLifeSpan) { ++ this.dead = true; ++ } ++ } ++ ++ @Override ++ public boolean damageEntity(DamageSource damagesource, float f) { ++ if (damagesource.getEntity() instanceof EntityPlayer) this.ticksSinceLastInteraction = 0; // Purpur ++ return super.damageEntity(damagesource, f); ++ } ++ // Purpur end ++ + @Override + protected void c(DamageSource damagesource) { + this.m(); +@@ -426,6 +452,7 @@ public abstract class EntityInsentient extends EntityLiving { + } + + nbttagcompound.setBoolean("Bukkit.Aware", this.aware); // CraftBukkit ++ nbttagcompound.setInt("Purpur.ticksSinceLastInteraction", ticksSinceLastInteraction); // Purpur + } + + @Override +@@ -496,6 +523,11 @@ public abstract class EntityInsentient extends EntityLiving { + this.aware = nbttagcompound.getBoolean("Bukkit.Aware"); + } + // CraftBukkit end ++ // Purpur start ++ if (nbttagcompound.hasKey("Purpur.ticksSinceLastInteraction")) { ++ ticksSinceLastInteraction = nbttagcompound.getInt("Purpur.ticksSinceLastInteraction"); ++ } ++ // Purpur end + } + + @Override +@@ -1543,7 +1575,7 @@ public abstract class EntityInsentient extends EntityLiving { + this.a((EntityLiving) this, entity); + this.z(entity); + } +- ++ if (entity instanceof EntityPlayer) this.ticksSinceLastInteraction = 0; // Purpur + return flag; + } + +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index ffccdc3bf1d28836f4fc2772ebfde843415ea232..8932f4854d9fc52fb2ec66a748e640dfd8806461 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -2271,6 +2271,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof EntityInsentient; + Item origItem = this.player.inventory.getItemInHand() == null ? null : this.player.inventory.getItemInHand().getItem(); + PlayerInteractEntityEvent event; ++ if (entity instanceof EntityInsentient) ((EntityInsentient) entity).ticksSinceLastInteraction = 0; // Purpur + if (packetplayinuseentity.b() == PacketPlayInUseEntity.EnumEntityUseAction.INTERACT) { + event = new PlayerInteractEntityEvent((Player) this.getPlayer(), entity.getBukkitEntity(), (packetplayinuseentity.c() == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND); + } else { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 3d6f4de01409c70c5f4d2fcd7a52d158aa0fdf80..d7635a3b2abb15407f55119bc29a5ee583ea714a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -103,6 +103,11 @@ public class PurpurWorldConfig { + } + } + ++ public int entityLifeSpan = 0; ++ private void entitySettings() { ++ entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); ++ } ++ + public List itemImmuneToCactus = new ArrayList<>(); + public List itemImmuneToExplosion = new ArrayList<>(); + public List itemImmuneToFire = new ArrayList<>(); diff --git a/patches/Purpur/patches/server/0087-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch b/patches/Purpur/patches/server/0087-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch new file mode 100644 index 00000000..b6a68159 --- /dev/null +++ b/patches/Purpur/patches/server/0087-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 13 Jul 2020 11:40:00 -0500 +Subject: [PATCH] Add option to teleport to spawn if outside world border + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 82ef4c9c534f4a0840a61d33c7727bd4770c7511..bfbe9b82c14a525bffa96a8dfa071e8805ce6006 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -280,6 +280,7 @@ public abstract class EntityLiving extends Entity { + double d1 = this.world.getWorldBorder().getDamageAmount(); + + if (d1 > 0.0D) { ++ if (world.purpurConfig.teleportIfOutsideBorder && this instanceof EntityPlayer) { ((EntityPlayer) this).teleport(MCUtil.toLocation(world, world.getSpawn())); return; } // Purpur + this.damageEntity(DamageSource.STUCK, (float) Math.max(1, MathHelper.floor(-d0 * d1))); + } + } +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 5c81b2ad016325b1b04b7da489c413ee2e5128d2..b68f342b89935f4b023823b7ff92d6268aafaa0e 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -2389,4 +2389,26 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + return (CraftPlayer) super.getBukkitEntity(); + } + // CraftBukkit end ++ ++ // Purpur start ++ public void teleport(Location to) { ++ this.ejectPassengers(); ++ this.stopRiding(true); ++ ++ if (this.isSleeping()) { ++ this.wakeup(true, false); ++ } ++ ++ if (this.activeContainer != this.defaultContainer) { ++ this.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); ++ } ++ ++ WorldServer toWorld = ((CraftWorld) to.getWorld()).getHandle(); ++ if (this.world == toWorld) { ++ this.playerConnection.teleport(to); ++ } else { ++ this.server.getPlayerList().moveToWorld(this, toWorld, true, to, !toWorld.paperConfig.disableTeleportationSuffocationCheck); ++ } ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d7635a3b2abb15407f55119bc29a5ee583ea714a..876aaf4fcfef01427a898dcf8c8db334334814b0 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -184,6 +184,11 @@ public class PurpurWorldConfig { + playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); + } + ++ public boolean teleportIfOutsideBorder = false; ++ private void teleportIfOutsideBorder() { ++ teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); ++ } ++ + public boolean silkTouchEnabled = false; + public String silkTouchSpawnerName = "Spawner"; + public List silkTouchSpawnerLore = new ArrayList<>(); diff --git a/patches/Purpur/patches/server/0088-Squid-EAR-immunity.patch b/patches/Purpur/patches/server/0088-Squid-EAR-immunity.patch new file mode 100644 index 00000000..23db2e87 --- /dev/null +++ b/patches/Purpur/patches/server/0088-Squid-EAR-immunity.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 13 Jul 2020 13:49:41 -0500 +Subject: [PATCH] Squid EAR immunity + + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 876aaf4fcfef01427a898dcf8c8db334334814b0..5c22c9166c6f6d65e89325653b7ee959ebff6939 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -443,6 +443,11 @@ public class PurpurWorldConfig { + snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); + } + ++ public boolean squidImmuneToEAR = true; ++ private void squidSettings() { ++ squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); ++ } ++ + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + public boolean villagerCanBeLeashed = false; +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index b47d6fa2de3368d1afe329573bc18c3541bb7377..ff0621e08aaf058154f228abd46475ffaa3920cf 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -378,6 +378,7 @@ public class ActivationRange + */ + public static boolean checkIfActive(Entity entity) + { ++ if (entity.world.purpurConfig.squidImmuneToEAR && entity instanceof net.minecraft.server.EntitySquid) return true; // Purpur + // Never safe to skip fireworks or entities not yet added to chunk + if ( !entity.inChunk || entity instanceof EntityFireworks ) { + return true; diff --git a/patches/Purpur/patches/server/0089-Configurable-end-spike-seed.patch b/patches/Purpur/patches/server/0089-Configurable-end-spike-seed.patch new file mode 100644 index 00000000..fd9b5fce --- /dev/null +++ b/patches/Purpur/patches/server/0089-Configurable-end-spike-seed.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 15 Jul 2020 11:49:36 -0500 +Subject: [PATCH] Configurable end spike seed + + +diff --git a/src/main/java/net/minecraft/server/WorldGenEnder.java b/src/main/java/net/minecraft/server/WorldGenEnder.java +index 00a1707209b21fc0163960c957864f798da15554..5eb9b3d35e12c2d3dc5f158cbed0b0a0853ee116 100644 +--- a/src/main/java/net/minecraft/server/WorldGenEnder.java ++++ b/src/main/java/net/minecraft/server/WorldGenEnder.java +@@ -23,12 +23,14 @@ public class WorldGenEnder extends WorldGenerator a(GeneratorAccessSeed generatoraccessseed) { +- Random random = new Random(generatoraccessseed.getSeed()); ++ int seed = net.pl3x.purpur.PurpurConfig.endSpikeSeed; // Purpur ++ Random random = new Random(seed == -1 ? generatoraccessseed.getSeed() : seed); // Purpur + long i = random.nextLong() & 65535L; + + return (List) WorldGenEnder.a.getUnchecked(i); + } + ++ public boolean generate(GeneratorAccessSeed generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPosition blockposition, WorldGenFeatureEndSpikeConfiguration worldgenfeatureendspikeconfiguration) { return a(generatoraccessseed, chunkgenerator, random, blockposition, worldgenfeatureendspikeconfiguration); } // Purpur - decompile error? + public boolean a(GeneratorAccessSeed generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPosition blockposition, WorldGenFeatureEndSpikeConfiguration worldgenfeatureendspikeconfiguration) { + List list = worldgenfeatureendspikeconfiguration.c(); + +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index c12146fe8ac1d89c6c1051a8f64ea29a9214e64b..c86f5178590f0b496f018858e21086a594de5dce 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -1,5 +1,6 @@ + package net.pl3x.purpur; + ++import co.aikar.timings.TimingsManager; + import com.google.common.base.Throwables; + import net.minecraft.server.MinecraftServer; + import net.pl3x.purpur.command.PurpurCommand; +@@ -140,6 +141,13 @@ public class PurpurConfig { + pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); + } + ++ public static int endSpikeSeed = -1; ++ private static void seedSettings() { ++ endSpikeSeed = getInt("settings.seed.end-spike", endSpikeSeed); ++ if (!TimingsManager.hiddenConfigs.contains("settings.seed")) TimingsManager.hiddenConfigs.add("settings.seed"); ++ if (!TimingsManager.hiddenConfigs.contains("settings.seed.end-spike")) TimingsManager.hiddenConfigs.add("settings.seed.end-spike"); ++ } ++ + public static String serverModName = "Purpur"; + private static void serverModName() { + serverModName = getString("settings.server-mod-name", serverModName); diff --git a/patches/Purpur/patches/server/0090-Configurable-dungeon-seed.patch b/patches/Purpur/patches/server/0090-Configurable-dungeon-seed.patch new file mode 100644 index 00000000..c63b1a9b --- /dev/null +++ b/patches/Purpur/patches/server/0090-Configurable-dungeon-seed.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 15 Jul 2020 12:40:25 -0500 +Subject: [PATCH] Configurable dungeon seed + + +diff --git a/src/main/java/net/minecraft/server/WorldGenDungeons.java b/src/main/java/net/minecraft/server/WorldGenDungeons.java +index 07f7b69fefe66ec4f26ca7b4fb3b752e80aeed07..6aa1626f38df0cd8ae8a417dc4fca213b82664c9 100644 +--- a/src/main/java/net/minecraft/server/WorldGenDungeons.java ++++ b/src/main/java/net/minecraft/server/WorldGenDungeons.java +@@ -11,12 +11,21 @@ public class WorldGenDungeons extends WorldGenerator[] ab = new EntityTypes[]{EntityTypes.SKELETON, EntityTypes.ZOMBIE, EntityTypes.ZOMBIE, EntityTypes.SPIDER}; + private static final IBlockData ac = Blocks.CAVE_AIR.getBlockData(); ++ private Random random; // Purpur + + public WorldGenDungeons(Codec codec) { + super(codec); + } + ++ public boolean generate(GeneratorAccessSeed generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPosition blockposition, WorldGenFeatureEmptyConfiguration worldgenfeatureemptyconfiguration) { return a(generatoraccessseed, chunkgenerator, random, blockposition, worldgenfeatureemptyconfiguration); } // Purpur - decompile error? + public boolean a(GeneratorAccessSeed generatoraccessseed, ChunkGenerator chunkgenerator, Random random, BlockPosition blockposition, WorldGenFeatureEmptyConfiguration worldgenfeatureemptyconfiguration) { ++ // Purpur start ++ if (this.random == null) { ++ int seed = net.pl3x.purpur.PurpurConfig.dungeonSeed; ++ this.random = seed == -1 ? random : new Random(seed); ++ } ++ random = this.random; ++ // Purpur end + boolean flag = true; + int i = random.nextInt(2) + 2; + int j = -i - 1; +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index c86f5178590f0b496f018858e21086a594de5dce..d0b4a9a1a9dea069ea543aa63b950fc5d08c9d02 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -141,10 +141,13 @@ public class PurpurConfig { + pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); + } + ++ public static int dungeonSeed = -1; + public static int endSpikeSeed = -1; + private static void seedSettings() { ++ dungeonSeed = getInt("settings.seed.dungeon", dungeonSeed); + endSpikeSeed = getInt("settings.seed.end-spike", endSpikeSeed); + if (!TimingsManager.hiddenConfigs.contains("settings.seed")) TimingsManager.hiddenConfigs.add("settings.seed"); ++ if (!TimingsManager.hiddenConfigs.contains("settings.seed.dungeon")) TimingsManager.hiddenConfigs.add("settings.seed.dungeon"); + if (!TimingsManager.hiddenConfigs.contains("settings.seed.end-spike")) TimingsManager.hiddenConfigs.add("settings.seed.end-spike"); + } + diff --git a/patches/Purpur/patches/server/0091-Phantoms-burn-in-light.patch b/patches/Purpur/patches/server/0091-Phantoms-burn-in-light.patch new file mode 100644 index 00000000..d0b80ba2 --- /dev/null +++ b/patches/Purpur/patches/server/0091-Phantoms-burn-in-light.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: draycia +Date: Sun, 12 Apr 2020 20:41:59 -0700 +Subject: [PATCH] Phantoms burn in light + + +diff --git a/src/main/java/net/minecraft/server/EntityPhantom.java b/src/main/java/net/minecraft/server/EntityPhantom.java +index dfce36368da9bdd9285c490a802f7a0cc4a339f6..bdfe073dcd255a7359127f9ae3a962642be5526d 100644 +--- a/src/main/java/net/minecraft/server/EntityPhantom.java ++++ b/src/main/java/net/minecraft/server/EntityPhantom.java +@@ -13,6 +13,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + private BlockPosition d; public void setHome(BlockPosition home) { this.d = home; } public BlockPosition getHome() { return this.d; } // Purpur - OBFHELPER + private EntityPhantom.AttackPhase bo; public AttackPhase getAttackPhase() { return this.bo; } // Purpur - OBFHELPER + private Vec3D crystalPosition; // Purpur ++ private static final RecipeItemStack TORCH = RecipeItemStack.a(Items.torch(), Items.soulTorch()); // Purpur + + public EntityPhantom(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -125,7 +126,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public void movementTick() { +- if (this.isAlive() && this.eG()) { ++ if (this.isAlive() && ((world.purpurConfig.phantomBurnInDaylight && this.isInDaylight()) || (world.purpurConfig.phantomBurnInLight > 0 && world.getLightLevel(new BlockPosition(this)) >= world.purpurConfig.phantomBurnInLight))) { // Purpur + this.setOnFire(8); + } + +@@ -374,7 +375,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + } else { + this.c = 60; + List list = EntityPhantom.this.world.a(this.b, (EntityLiving) EntityPhantom.this, EntityPhantom.this.getBoundingBox().grow(16.0D, 64.0D, 16.0D)); +- ++ if (world.purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(EnumHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(EnumHand.OFF_HAND)));// Purpur + if (!list.isEmpty()) { + list.sort(Comparator.comparing(Entity::locY).reversed()); + Iterator iterator = list.iterator(); +@@ -473,6 +474,12 @@ public class EntityPhantom extends EntityFlying implements IMonster { + return false; + } else if (entityliving instanceof EntityHuman && (((EntityHuman) entityliving).isSpectator() || ((EntityHuman) entityliving).isCreative())) { + return false; ++ // Purpur start ++ } else if (world.purpurConfig.phantomBurnInLight > 0 && world.getLightLevel(new BlockPosition(EntityPhantom.this)) >= world.purpurConfig.phantomBurnInLight) { ++ return false; ++ } else if (world.purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(entityliving.getItemInHand(EnumHand.MAIN_HAND)) || TORCH.test(entityliving.getItemInHand(EnumHand.OFF_HAND)))) { ++ return false; ++ // Purpur end + } else if (!this.a()) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/server/Items.java b/src/main/java/net/minecraft/server/Items.java +index 4c379916d8d7797038d2980761c49f44c010dea8..ea6b61c9b88f33d29f7266d7614c80372db31b72 100644 +--- a/src/main/java/net/minecraft/server/Items.java ++++ b/src/main/java/net/minecraft/server/Items.java +@@ -173,7 +173,7 @@ public class Items { + public static final Item cm = a(Blocks.BOOKSHELF, CreativeModeTab.b); + public static final Item cn = a(Blocks.MOSSY_COBBLESTONE, CreativeModeTab.b); + public static final Item co = a(Blocks.OBSIDIAN, CreativeModeTab.b); +- public static final Item cp = a((ItemBlock) (new ItemBlockWallable(Blocks.TORCH, Blocks.WALL_TORCH, (new Item.Info()).a(CreativeModeTab.c)))); ++ public static final Item cp = a((ItemBlock) (new ItemBlockWallable(Blocks.TORCH, Blocks.WALL_TORCH, (new Item.Info()).a(CreativeModeTab.c)))); public static final Item torch() { return cp; } // Purpur - OBFHELPER + public static final Item cq = a(Blocks.END_ROD, CreativeModeTab.c); + public static final Item cr = a(Blocks.CHORUS_PLANT, CreativeModeTab.c); + public static final Item cs = a(Blocks.CHORUS_FLOWER, CreativeModeTab.c); +@@ -225,7 +225,7 @@ public class Items { + public static final Item dm = a(Blocks.SOUL_SOIL, CreativeModeTab.b); + public static final Item dn = a(Blocks.BASALT, CreativeModeTab.b); + public static final Item do_ = a(Blocks.cP, CreativeModeTab.b); +- public static final Item dp = a((ItemBlock) (new ItemBlockWallable(Blocks.SOUL_TORCH, Blocks.SOUL_WALL_TORCH, (new Item.Info()).a(CreativeModeTab.c)))); ++ public static final Item dp = a((ItemBlock) (new ItemBlockWallable(Blocks.SOUL_TORCH, Blocks.SOUL_WALL_TORCH, (new Item.Info()).a(CreativeModeTab.c)))); public static final Item soulTorch() { return dp; } // Purpur - OBFHELPER + public static final Item dq = a(Blocks.GLOWSTONE, CreativeModeTab.b); + public static final Item dr = a(Blocks.JACK_O_LANTERN, CreativeModeTab.b); + public static final Item ds = a(Blocks.OAK_TRAPDOOR, CreativeModeTab.d); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 5c22c9166c6f6d65e89325653b7ee959ebff6939..42399ce5b19f4e96087bde3c6902a5c8e78d6c50 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -398,6 +398,9 @@ public class PurpurWorldConfig { + public int phantomSpawnOverheadRadius = 10; + public int phantomSpawnMinPerAttempt = 1; + public int phantomSpawnMaxPerAttempt = -1; ++ public int phantomBurnInLight = 0; ++ public boolean phantomIgnorePlayersWithTorch = false; ++ public boolean phantomBurnInDaylight = true; + private void phantomSettings() { + phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); + phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); +@@ -414,6 +417,9 @@ public class PurpurWorldConfig { + phantomSpawnOverheadRadius = getInt("mobs.phantom.spawn.overhead.radius", phantomSpawnOverheadRadius); + phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); + phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); ++ phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); ++ phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); ++ phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); + } + + public boolean pigGiveSaddleBack = false; diff --git a/patches/Purpur/patches/server/0092-Configurable-villager-breeding.patch b/patches/Purpur/patches/server/0092-Configurable-villager-breeding.patch new file mode 100644 index 00000000..8a070b09 --- /dev/null +++ b/patches/Purpur/patches/server/0092-Configurable-villager-breeding.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: draycia +Date: Tue, 31 Mar 2020 23:48:55 -0700 +Subject: [PATCH] Configurable villager breeding + + +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index c06425434c97645b914c07940528901a2979ce1b..a0bfef54c853d57c9a5c6d3f9f19591649295357 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -642,7 +642,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + + @Override + public boolean canBreed() { +- return this.bx + this.fv() >= 12 && this.getAge() == 0; ++ return world.purpurConfig.villagerCanBreed && this.bx + this.fv() >= 12 && this.getAge() == 0; // Purpur + } + + private boolean fr() { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 42399ce5b19f4e96087bde3c6902a5c8e78d6c50..8c730d1ec0eebde4210f5c8eab55eb1b71f9584d 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -461,6 +461,7 @@ public class PurpurWorldConfig { + public boolean villagerFollowEmeraldBlock = false; + public int villagerSpawnIronGolemRadius = 0; + public int villagerSpawnIronGolemLimit = 0; ++ public boolean villagerCanBreed = true; + private void villagerSettings() { + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); +@@ -469,6 +470,7 @@ public class PurpurWorldConfig { + villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); + villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); + villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); ++ villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); + } + + public boolean villagerTraderCanBeLeashed = false; diff --git a/patches/Purpur/patches/server/0093-Redstone-deactivates-spawners.patch b/patches/Purpur/patches/server/0093-Redstone-deactivates-spawners.patch new file mode 100644 index 00000000..a3262a10 --- /dev/null +++ b/patches/Purpur/patches/server/0093-Redstone-deactivates-spawners.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: draycia +Date: Tue, 14 Apr 2020 00:35:12 -0700 +Subject: [PATCH] Redstone deactivates spawners + + +diff --git a/src/main/java/net/minecraft/server/MobSpawnerAbstract.java b/src/main/java/net/minecraft/server/MobSpawnerAbstract.java +index c2e830db7aa2944a477624e149a3ba66d112b68a..3b4cc9871c123cfd134783745145c407bbccea0f 100644 +--- a/src/main/java/net/minecraft/server/MobSpawnerAbstract.java ++++ b/src/main/java/net/minecraft/server/MobSpawnerAbstract.java +@@ -51,6 +51,7 @@ public abstract class MobSpawnerAbstract { + private boolean h() { + BlockPosition blockposition = this.b(); + ++ if (getWorld().purpurConfig.spawnerDeactivateByRedstone && getWorld().isBlockIndirectlyPowered(blockposition)) { return false; } // Purpur + return this.a().isAffectsSpawningPlayerNearby((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper + } + +@@ -329,7 +330,7 @@ public abstract class MobSpawnerAbstract { + + public abstract void a(int i); + +- public abstract World a(); ++ public abstract World a(); public World getWorld() { return a(); } // Purpur - OBFHELPER + + public abstract BlockPosition b(); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 8c730d1ec0eebde4210f5c8eab55eb1b71f9584d..03de0b6385193b3ba571fd0f8f0bb316a7f158d0 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -272,6 +272,11 @@ public class PurpurWorldConfig { + signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); + } + ++ public boolean spawnerDeactivateByRedstone = false; ++ private void spawnerSettings() { ++ spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); ++ } ++ + public boolean turtleEggsBreakFromExpOrbs = true; + public boolean turtleEggsBreakFromItems = true; + public boolean turtleEggsBreakFromMinecarts = true; diff --git a/patches/Purpur/patches/server/0094-Totems-work-in-inventory.patch b/patches/Purpur/patches/server/0094-Totems-work-in-inventory.patch new file mode 100644 index 00000000..9aca8061 --- /dev/null +++ b/patches/Purpur/patches/server/0094-Totems-work-in-inventory.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: draycia +Date: Wed, 29 Apr 2020 00:45:58 -0700 +Subject: [PATCH] Totems work in inventory + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index bfbe9b82c14a525bffa96a8dfa071e8805ce6006..c22fa35197ae23526a29bcbf69f3022ffc0701e1 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -1327,6 +1327,19 @@ public abstract class EntityLiving extends Entity { + } + } + ++ // Purpur start ++ if (world.purpurConfig.totemOfUndyingWorksInInventory && this instanceof EntityPlayer && (itemstack == null || itemstack.getItem() != Items.TOTEM_OF_UNDYING)) { ++ EntityPlayer player = (EntityPlayer) this; ++ for (ItemStack item : player.inventory.items) { ++ if (item.getItem() == Items.TOTEM_OF_UNDYING) { ++ itemstack1 = item; ++ itemstack = item.cloneItemStack(); ++ break; ++ } ++ } ++ } ++ // Purpur end ++ + EntityResurrectEvent event = new EntityResurrectEvent((LivingEntity) this.getBukkitEntity()); + event.setCancelled(itemstack == null); + this.world.getServer().getPluginManager().callEvent(event); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 03de0b6385193b3ba571fd0f8f0bb316a7f158d0..97bace9aac1d907730e991beb3fcd9aa7bbe480c 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -189,6 +189,11 @@ public class PurpurWorldConfig { + teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); + } + ++ public boolean totemOfUndyingWorksInInventory = false; ++ private void totemOfUndyingWorksInInventory() { ++ totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); ++ } ++ + public boolean silkTouchEnabled = false; + public String silkTouchSpawnerName = "Spawner"; + public List silkTouchSpawnerLore = new ArrayList<>(); diff --git a/patches/Purpur/patches/server/0095-Fix-death-message-colors.patch b/patches/Purpur/patches/server/0095-Fix-death-message-colors.patch new file mode 100644 index 00000000..a51e0fe7 --- /dev/null +++ b/patches/Purpur/patches/server/0095-Fix-death-message-colors.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 19 Jul 2020 14:08:33 -0500 +Subject: [PATCH] Fix death message colors + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index b68f342b89935f4b023823b7ff92d6268aafaa0e..aba0647c9b297d532a5e7100eb95337e6a0db776 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -852,7 +852,24 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + IChatBaseComponent defaultMessage = this.getCombatTracker().getDeathMessage(); + +- String deathmessage = defaultMessage.getString(); ++ // Purpur start ++ String deathmessage; ++ String json = ""; ++ try { ++ json = IChatBaseComponent.ChatSerializer.componentToJson(defaultMessage); ++ net.md_5.bungee.api.chat.BaseComponent[] parsed = net.md_5.bungee.chat.ComponentSerializer.parse(json); ++ net.md_5.bungee.api.chat.TextComponent wrapped = new net.md_5.bungee.api.chat.TextComponent(parsed); ++ deathmessage = wrapped.toLegacyText(); ++ } catch (Exception e) { ++ deathmessage = defaultMessage.getString(); ++ MinecraftServer.LOGGER.warn("There was a problem processing the death message json component!"); ++ MinecraftServer.LOGGER.warn("We have fallen back to legacy colorless death message to prevent real errors"); ++ MinecraftServer.LOGGER.warn("Please report this to Purpur!"); ++ MinecraftServer.LOGGER.warn("JSON: " + json); ++ MinecraftServer.LOGGER.warn("The following error describes what went wrong:"); ++ e.printStackTrace(); ++ } ++ // Purpur end + org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory); + // Paper start - cancellable death event + if (event.isCancelled()) { diff --git a/patches/Purpur/patches/server/0096-Duplicate-paper-s-vanilla-scoreboard-colors-patch-to.patch b/patches/Purpur/patches/server/0096-Duplicate-paper-s-vanilla-scoreboard-colors-patch-to.patch new file mode 100644 index 00000000..b5f43b10 --- /dev/null +++ b/patches/Purpur/patches/server/0096-Duplicate-paper-s-vanilla-scoreboard-colors-patch-to.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 19 Jul 2020 15:30:50 -0500 +Subject: [PATCH] Duplicate paper's vanilla scoreboard colors patch to sync + chat event + + +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 8932f4854d9fc52fb2ec66a748e640dfd8806461..eff58bba46e5cb4bd412fcb65e293d5b9eb58aba 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -2030,7 +2030,15 @@ public class PlayerConnection implements PacketListenerPlayIn { + return null; + } + +- String message = String.format(queueEvent.getFormat(), queueEvent.getPlayer().getDisplayName(), queueEvent.getMessage()); ++ // Purpur Start - Support for vanilla world scoreboard name coloring (copied from paper's diff below) ++ String displayName = queueEvent.getPlayer().getDisplayName(); ++ if (PlayerConnection.this.player.getWorld().paperConfig.useVanillaScoreboardColoring) { ++ IChatBaseComponent nameFromTeam = ScoreboardTeam.a(PlayerConnection.this.player.getScoreboardTeam(), PlayerConnection.this.player.getDisplayName()); ++ // Explicitly add a RESET here, vanilla uses components for this now... ++ displayName = new net.md_5.bungee.api.chat.TextComponent(net.md_5.bungee.chat.ComponentSerializer.parse(IChatBaseComponent.ChatSerializer.componentToJson(nameFromTeam))).toLegacyText() + org.bukkit.ChatColor.RESET; ++ } ++ String message = String.format(queueEvent.getFormat(), displayName, queueEvent.getMessage()); ++ // Purpur end + PlayerConnection.this.minecraftServer.console.sendMessage(message); + if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) { + for (Object player : PlayerConnection.this.minecraftServer.getPlayerList().players) { +@@ -2063,7 +2071,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + // Paper Start - (Meh) Support for vanilla world scoreboard name coloring + String displayName = event.getPlayer().getDisplayName(); + if (this.player.getWorld().paperConfig.useVanillaScoreboardColoring) { +- IChatBaseComponent nameFromTeam = ScoreboardTeam.a(this.player.getScoreboardTeam(), ((CraftPlayer) player).getHandle().getDisplayName()); ++ IChatBaseComponent nameFromTeam = ScoreboardTeam.a(this.player.getScoreboardTeam(), this.player.getDisplayName()); // Purpur - why are we casting bukkit player back to nms player when we already have a nms player... + // Explicitly add a RESET here, vanilla uses components for this now... + displayName = new net.md_5.bungee.api.chat.TextComponent(net.md_5.bungee.chat.ComponentSerializer.parse(IChatBaseComponent.ChatSerializer.componentToJson(nameFromTeam))).toLegacyText() + org.bukkit.ChatColor.RESET; + } diff --git a/patches/Purpur/patches/server/0097-Populator-seed-controls.patch b/patches/Purpur/patches/server/0097-Populator-seed-controls.patch new file mode 100644 index 00000000..f721d6a0 --- /dev/null +++ b/patches/Purpur/patches/server/0097-Populator-seed-controls.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 18 Jul 2020 11:27:43 -0500 +Subject: [PATCH] Populator seed controls + + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index dae2e5d70756c5b61163d57099b65f7e415b288c..55b67f1057224101272f9d6023a93872c4423405 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -293,7 +293,7 @@ public class TimingsExport extends Thread { + JSONObject object = new JSONObject(); + for (String key : config.getKeys(false)) { + String fullKey = (parentKey != null ? parentKey + "." + key : key); +- if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) { ++ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld") || fullKey.contains("worldgen.seeds.populator")) { // Tuinity + continue; + } + final Object val = config.get(key); +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index ceeb515f4528551659598d9999917f8596e3eded..274b93dce71d630107c2647a045ab28e3eec58c4 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -1,5 +1,6 @@ + package com.tuinity.tuinity.config; + ++import co.aikar.timings.TimingsManager; + import com.destroystokyo.paper.util.SneakyThrow; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.TicketType; +@@ -376,6 +377,19 @@ public final class TuinityConfig { + this.spawnLimitAmbient = this.getInt(path + ".ambient", -1); + } + ++ public Long populatorSeed; ++ public boolean useRandomPopulatorSeed; ++ ++ private void populatorSeed() { ++ final String seedString = this.getString("worldgen.seeds.populator", "default"); ++ if (seedString.equalsIgnoreCase("random")) { ++ this.useRandomPopulatorSeed = true; ++ } else if (!seedString.equalsIgnoreCase("default")) { ++ this.populatorSeed = Long.parseLong(seedString); ++ } ++ if (!TimingsManager.hiddenConfigs.contains("worldgen.seeds.populator")) TimingsManager.hiddenConfigs.add("worldgen.seeds.populator"); ++ } ++ + } + + } +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java +index 0854ac9ef586b378420d9899f3afd2755e6f9f33..df6874c1cf5fc5764dc575866aab87883b6cf035 100644 +--- a/src/main/java/net/minecraft/server/BiomeBase.java ++++ b/src/main/java/net/minecraft/server/BiomeBase.java +@@ -189,6 +189,10 @@ public final class BiomeBase { + return this.k; + } + ++ // Tuinity start - populator seed control ++ private static final java.security.SecureRandom SECURE_RANDOM = new java.security.SecureRandom(); ++ // Tuinity end - populator seed control ++ + public void a(StructureManager structuremanager, ChunkGenerator chunkgenerator, RegionLimitedWorldAccess regionlimitedworldaccess, long i, SeededRandom seededrandom, BlockPosition blockposition) { + List>>> list = this.k.c(); + int j = WorldGenStage.Decoration.values().length; +@@ -225,12 +229,24 @@ public final class BiomeBase { + } + } + ++ // Tuinity start - populator seed control ++ long populatorSeed; ++ WorldServer world = (WorldServer)((ChunkProviderServer)regionlimitedworldaccess.getChunkProvider()).getWorld(); ++ if (world.tuinityConfig.useRandomPopulatorSeed) { ++ populatorSeed = SECURE_RANDOM.nextLong(); ++ } else if (world.tuinityConfig.populatorSeed != null) { ++ populatorSeed = world.tuinityConfig.populatorSeed.longValue(); ++ } else { ++ populatorSeed = i; ++ } ++ // Tuinity end - populator seed control ++ + if (list.size() > k) { + for (Iterator iterator1 = ((List) list.get(k)).iterator(); iterator1.hasNext(); ++l) { + Supplier> supplier = (Supplier) iterator1.next(); + WorldGenFeatureConfigured worldgenfeatureconfigured = (WorldGenFeatureConfigured) supplier.get(); + +- seededrandom.b(i, l, k); ++ seededrandom.b(populatorSeed, l, k); // Tuinity - populator seed control - move i up into default branch + + try { + worldgenfeatureconfigured.a(regionlimitedworldaccess, chunkgenerator, seededrandom, blockposition); diff --git a/patches/Purpur/patches/server/0098-Add-vindicator-johnny-spawn-chance.patch b/patches/Purpur/patches/server/0098-Add-vindicator-johnny-spawn-chance.patch new file mode 100644 index 00000000..c4bb7c89 --- /dev/null +++ b/patches/Purpur/patches/server/0098-Add-vindicator-johnny-spawn-chance.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 24 Jul 2020 19:38:21 -0500 +Subject: [PATCH] Add vindicator johnny spawn chance + + +diff --git a/src/main/java/net/minecraft/server/EntityVindicator.java b/src/main/java/net/minecraft/server/EntityVindicator.java +index 4761ddfedeed54e79788a6f60f06a805efd60ab1..23b350f793539672b6990327ed52e9bb3bdbf54e 100644 +--- a/src/main/java/net/minecraft/server/EntityVindicator.java ++++ b/src/main/java/net/minecraft/server/EntityVindicator.java +@@ -82,6 +82,12 @@ public class EntityVindicator extends EntityIllagerAbstract { + ((Navigation) this.getNavigation()).a(true); + this.a(difficultydamagescaler); + this.b(difficultydamagescaler); ++ // Purpur start ++ World world = worldaccess.getMinecraftWorld(); ++ if (world.purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= world.purpurConfig.vindicatorJohnnySpawnChance) { ++ setCustomName(new ChatMessage("Johnny")); ++ } ++ // Purpur end + return groupdataentity1; + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 97bace9aac1d907730e991beb3fcd9aa7bbe480c..a2aa975ce17ceebee97afb9594ac9f403f162fce 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -40,6 +40,11 @@ public class PurpurWorldConfig { + } + } + ++ public double vindicatorJohnnySpawnChance = 0D; ++ private void vindicatorSettings() { ++ vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); ++ } ++ + private ConfigurationSection getConfigurationSection(String path) { + ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); + return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); diff --git a/patches/Purpur/patches/server/0099-DragonEggPlaceEvent.patch b/patches/Purpur/patches/server/0099-DragonEggPlaceEvent.patch new file mode 100644 index 00000000..da8be5b4 --- /dev/null +++ b/patches/Purpur/patches/server/0099-DragonEggPlaceEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 30 Jul 2020 18:15:13 -0500 +Subject: [PATCH] DragonEggPlaceEvent + + +diff --git a/src/main/java/net/minecraft/server/EnderDragonBattle.java b/src/main/java/net/minecraft/server/EnderDragonBattle.java +index f853f6c424da77c40ee3d5b5dc2279ba8918977c..5199cba870a6ea004ee72ce225a0b6f483aed2ed 100644 +--- a/src/main/java/net/minecraft/server/EnderDragonBattle.java ++++ b/src/main/java/net/minecraft/server/EnderDragonBattle.java +@@ -359,7 +359,13 @@ public class EnderDragonBattle { + this.generateExitPortal(true); + this.n(); + if (this.world.purpurConfig.enderDragonAlwaysDropsEggBlock || !this.previouslyKilled) { // Purpur - always place dragon egg +- this.world.setTypeUpdate(this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a), Blocks.DRAGON_EGG.getBlockData()); ++ // Purpur start ++ BlockPosition pos = this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, WorldGenEndTrophy.a); ++ net.pl3x.purpur.event.block.DragonEggPlaceEvent event = new net.pl3x.purpur.event.block.DragonEggPlaceEvent(MCUtil.toLocation(world, pos)); ++ if (event.callEvent()) { ++ this.world.setTypeUpdate(MCUtil.toBlockPosition(event.getLocation()), Blocks.DRAGON_EGG.getBlockData()); ++ } ++ // Purpur end + } + + this.previouslyKilled = true; diff --git a/patches/Purpur/patches/server/0100-Add-option-to-disable-mushroom-block-updates.patch b/patches/Purpur/patches/server/0100-Add-option-to-disable-mushroom-block-updates.patch new file mode 100644 index 00000000..34604e82 --- /dev/null +++ b/patches/Purpur/patches/server/0100-Add-option-to-disable-mushroom-block-updates.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 4 Aug 2020 17:11:58 -0500 +Subject: [PATCH] Add option to disable mushroom block updates + + +diff --git a/src/main/java/net/minecraft/server/BlockHugeMushroom.java b/src/main/java/net/minecraft/server/BlockHugeMushroom.java +index e8f340b9f2a1e5de9cf7e8cf595de8a806cb528a..3490af6fe0cf2eccac9753778cf4085f95d5da93 100644 +--- a/src/main/java/net/minecraft/server/BlockHugeMushroom.java ++++ b/src/main/java/net/minecraft/server/BlockHugeMushroom.java +@@ -14,30 +14,57 @@ public class BlockHugeMushroom extends Block { + + public BlockHugeMushroom(BlockBase.Info blockbase_info) { + super(blockbase_info); +- this.j((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) this.blockStateList.getBlockData()).set(BlockHugeMushroom.a, true)).set(BlockHugeMushroom.b, true)).set(BlockHugeMushroom.c, true)).set(BlockHugeMushroom.d, true)).set(BlockHugeMushroom.e, true)).set(BlockHugeMushroom.f, true)); ++ this.j(this.blockStateList.getBlockData() ++ .set(BlockHugeMushroom.a, true) ++ .set(BlockHugeMushroom.b, true) ++ .set(BlockHugeMushroom.c, true) ++ .set(BlockHugeMushroom.d, true) ++ .set(BlockHugeMushroom.e, true) ++ .set(BlockHugeMushroom.f, true)); + } + + @Override + public IBlockData getPlacedState(BlockActionContext blockactioncontext) { + World world = blockactioncontext.getWorld(); ++ if (net.pl3x.purpur.PurpurConfig.disableMushroomBlockUpdates) return this.getBlockData(); + BlockPosition blockposition = blockactioncontext.getClickPosition(); +- +- return (IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) this.getBlockData().set(BlockHugeMushroom.f, this != world.getType(blockposition.down()).getBlock())).set(BlockHugeMushroom.e, this != world.getType(blockposition.up()).getBlock())).set(BlockHugeMushroom.a, this != world.getType(blockposition.north()).getBlock())).set(BlockHugeMushroom.b, this != world.getType(blockposition.east()).getBlock())).set(BlockHugeMushroom.c, this != world.getType(blockposition.south()).getBlock())).set(BlockHugeMushroom.d, this != world.getType(blockposition.west()).getBlock()); ++ return this.getBlockData() ++ .set(BlockHugeMushroom.f, this != world.getType(blockposition.down()).getBlock()) ++ .set(BlockHugeMushroom.e, this != world.getType(blockposition.up()).getBlock()) ++ .set(BlockHugeMushroom.a, this != world.getType(blockposition.north()).getBlock()) ++ .set(BlockHugeMushroom.b, this != world.getType(blockposition.east()).getBlock()) ++ .set(BlockHugeMushroom.c, this != world.getType(blockposition.south()).getBlock()) ++ .set(BlockHugeMushroom.d, this != world.getType(blockposition.west()).getBlock()); + } + + @Override + public IBlockData updateState(IBlockData iblockdata, EnumDirection enumdirection, IBlockData iblockdata1, GeneratorAccess generatoraccess, BlockPosition blockposition, BlockPosition blockposition1) { +- return iblockdata1.a((Block) this) ? (IBlockData) iblockdata.set((IBlockState) BlockHugeMushroom.g.get(enumdirection), false) : super.updateState(iblockdata, enumdirection, iblockdata1, generatoraccess, blockposition, blockposition1); ++ if (net.pl3x.purpur.PurpurConfig.disableMushroomBlockUpdates) return iblockdata; ++ return iblockdata1.a(this) ? iblockdata.set(BlockHugeMushroom.g.get(enumdirection), false) : super.updateState(iblockdata, enumdirection, iblockdata1, generatoraccess, blockposition, blockposition1); + } + + @Override + public IBlockData a(IBlockData iblockdata, EnumBlockRotation enumblockrotation) { +- return (IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) iblockdata.set((IBlockState) BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.NORTH)), iblockdata.get(BlockHugeMushroom.a))).set((IBlockState) BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.SOUTH)), iblockdata.get(BlockHugeMushroom.c))).set((IBlockState) BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.EAST)), iblockdata.get(BlockHugeMushroom.b))).set((IBlockState) BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.WEST)), iblockdata.get(BlockHugeMushroom.d))).set((IBlockState) BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.UP)), iblockdata.get(BlockHugeMushroom.e))).set((IBlockState) BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.DOWN)), iblockdata.get(BlockHugeMushroom.f)); ++ if (net.pl3x.purpur.PurpurConfig.disableMushroomBlockUpdates) return iblockdata; ++ return iblockdata ++ .set(BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.NORTH)), iblockdata.get(BlockHugeMushroom.a)) ++ .set(BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.SOUTH)), iblockdata.get(BlockHugeMushroom.c)) ++ .set(BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.EAST)), iblockdata.get(BlockHugeMushroom.b)) ++ .set(BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.WEST)), iblockdata.get(BlockHugeMushroom.d)) ++ .set(BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.UP)), iblockdata.get(BlockHugeMushroom.e)) ++ .set(BlockHugeMushroom.g.get(enumblockrotation.a(EnumDirection.DOWN)), iblockdata.get(BlockHugeMushroom.f)); + } + + @Override + public IBlockData a(IBlockData iblockdata, EnumBlockMirror enumblockmirror) { +- return (IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) ((IBlockData) iblockdata.set((IBlockState) BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.NORTH)), iblockdata.get(BlockHugeMushroom.a))).set((IBlockState) BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.SOUTH)), iblockdata.get(BlockHugeMushroom.c))).set((IBlockState) BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.EAST)), iblockdata.get(BlockHugeMushroom.b))).set((IBlockState) BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.WEST)), iblockdata.get(BlockHugeMushroom.d))).set((IBlockState) BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.UP)), iblockdata.get(BlockHugeMushroom.e))).set((IBlockState) BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.DOWN)), iblockdata.get(BlockHugeMushroom.f)); ++ if (net.pl3x.purpur.PurpurConfig.disableMushroomBlockUpdates) return iblockdata; ++ return iblockdata ++ .set(BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.NORTH)), iblockdata.get(BlockHugeMushroom.a)) ++ .set(BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.SOUTH)), iblockdata.get(BlockHugeMushroom.c)) ++ .set(BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.EAST)), iblockdata.get(BlockHugeMushroom.b)) ++ .set(BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.WEST)), iblockdata.get(BlockHugeMushroom.d)) ++ .set(BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.UP)), iblockdata.get(BlockHugeMushroom.e)) ++ .set(BlockHugeMushroom.g.get(enumblockmirror.b(EnumDirection.DOWN)), iblockdata.get(BlockHugeMushroom.f)); + } + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index d0b4a9a1a9dea069ea543aa63b950fc5d08c9d02..17a8b43892fdab87a80cbf076c7814bfab087142 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -201,6 +201,11 @@ public class PurpurConfig { + allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd); + } + ++ public static boolean disableMushroomBlockUpdates = false; ++ private static void disableMushroomBlockUpdates() { ++ disableMushroomBlockUpdates= getBoolean("settings.blocks.disable-mushroom-updates", disableMushroomBlockUpdates); ++ } ++ + public static boolean loggerSuppressInitLegacyMaterialError = false; + public static boolean loggerSuppressIgnoredAdvancementWarnings = false; + private static void loggerSettings() { diff --git a/patches/Purpur/patches/server/0101-Dispensers-place-anvils-option.patch b/patches/Purpur/patches/server/0101-Dispensers-place-anvils-option.patch new file mode 100644 index 00000000..7655b7d7 --- /dev/null +++ b/patches/Purpur/patches/server/0101-Dispensers-place-anvils-option.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 4 Aug 2020 21:11:03 -0500 +Subject: [PATCH] Dispensers place anvils option + + +diff --git a/src/main/java/net/minecraft/server/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java +index 24e6f3141ff4434f770e956a8d240bf856442933..c187f04ef657e9012d2fcdc2fd5b4cca69d78c3f 100644 +--- a/src/main/java/net/minecraft/server/EnumDirection.java ++++ b/src/main/java/net/minecraft/server/EnumDirection.java +@@ -102,6 +102,7 @@ public enum EnumDirection implements INamable { + return fromType1(this.h); + } + ++ public EnumDirection rotateCW() { return g(); } // Purpur - OBFHELPER + public EnumDirection g() { + switch (this) { + case NORTH: +@@ -117,6 +118,7 @@ public enum EnumDirection implements INamable { + } + } + ++ public EnumDirection rotateCCW() { return h(); } // Purpur - OBFHELPER + public EnumDirection h() { + switch (this) { + case NORTH: +@@ -148,6 +150,7 @@ public enum EnumDirection implements INamable { + return this.j; + } + ++ public EnumDirection.EnumAxis getAxis() { return n(); } // Purpur - OBFHELPER + public EnumDirection.EnumAxis n() { + return this.k; + } +diff --git a/src/main/java/net/minecraft/server/IDispenseBehavior.java b/src/main/java/net/minecraft/server/IDispenseBehavior.java +index 7b8a470d97ccf0fdcdb8eef9368195486e09913b..d1f9d2884d055efbe72b01f86b0bdaf13ed122a5 100644 +--- a/src/main/java/net/minecraft/server/IDispenseBehavior.java ++++ b/src/main/java/net/minecraft/server/IDispenseBehavior.java +@@ -915,6 +915,23 @@ public interface IDispenseBehavior { + } + })); + BlockDispenser.a((IMaterial) Items.SHEARS.getItem(), (IDispenseBehavior) (new DispenseBehaviorShears())); ++ // Purpur start ++ BlockDispenser.a(Blocks.ANVIL, new DispenseBehaviorMaybe() { ++ @Override ++ protected ItemStack a(ISourceBlock dispenser, ItemStack itemstack) { ++ World world = dispenser.getWorld(); ++ if (!world.purpurConfig.dispenserPlaceAnvils) return super.a(dispenser, itemstack); ++ EnumDirection facing = dispenser.getBlockData().get(BlockDispenser.FACING); ++ BlockPosition blockposition = dispenser.getBlockPosition().shift(facing); ++ IBlockData iblockdata = world.getType(blockposition); ++ if (iblockdata.isAir()) { ++ world.setTypeUpdate(blockposition, Blocks.ANVIL.getBlockData().set(BlockAnvil.FACING, facing.getAxis() == EnumDirection.EnumAxis.Y ? EnumDirection.NORTH : facing.rotateCW())); ++ itemstack.subtract(1); ++ } ++ return itemstack; ++ } ++ }); ++ // Purpur end + } + + static void a(ISourceBlock isourceblock, Entity entity, EnumDirection enumdirection) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index a2aa975ce17ceebee97afb9594ac9f403f162fce..2d426d35a328ad3d5ce11615dc300bfacfef6df4 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -239,8 +239,10 @@ public class PurpurWorldConfig { + } + + public boolean dispenserApplyCursedArmor = true; ++ public boolean dispenserPlaceAnvils = false; + private void dispenserSettings() { + dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor); ++ dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); + } + + public boolean farmlandGetsMoistFromBelow = false; diff --git a/patches/Purpur/patches/server/0102-Allow-anvil-colors.patch b/patches/Purpur/patches/server/0102-Allow-anvil-colors.patch new file mode 100644 index 00000000..05cd0b72 --- /dev/null +++ b/patches/Purpur/patches/server/0102-Allow-anvil-colors.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 4 Aug 2020 22:08:23 -0500 +Subject: [PATCH] Allow anvil colors + + +diff --git a/src/main/java/net/minecraft/server/ContainerAnvil.java b/src/main/java/net/minecraft/server/ContainerAnvil.java +index 4aa6b035a6a8ea39401c6566cd286de39f60e942..5e4e59e5c8478b76078fdda14e5ced76304d489c 100644 +--- a/src/main/java/net/minecraft/server/ContainerAnvil.java ++++ b/src/main/java/net/minecraft/server/ContainerAnvil.java +@@ -251,6 +251,25 @@ public class ContainerAnvil extends ContainerAnvilAbstract { + } else if (!this.renameText.equals(itemstack.getName().getString())) { + b1 = 1; + i += b1; ++ // Purpur start ++ if (player != null && player.world.purpurConfig.anvilAllowColors && player.getBukkitEntity().hasPermission("purpur.anvil.color")) { ++ String json = ""; ++ try { ++ String coloredText = net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', this.renameText); ++ net.md_5.bungee.api.chat.BaseComponent[] bungeeComp = net.md_5.bungee.api.chat.TextComponent.fromLegacyText(coloredText); ++ json = net.md_5.bungee.chat.ComponentSerializer.toString(bungeeComp); ++ IChatBaseComponent nmsComp = IChatBaseComponent.ChatSerializer.jsonToComponent(json); ++ itemstack1.a(nmsComp); ++ } catch (Exception e) { ++ MinecraftServer.LOGGER.warn("There was a problem processing item name json component on anvil!"); ++ MinecraftServer.LOGGER.warn("We have fallen back to legacy colorless item name to prevent real errors"); ++ MinecraftServer.LOGGER.warn("Please report this to Purpur!"); ++ MinecraftServer.LOGGER.warn("JSON: " + json); ++ MinecraftServer.LOGGER.warn("The following error describes what went wrong:"); ++ e.printStackTrace(); ++ } ++ } else ++ // Purpur end + itemstack1.a((IChatBaseComponent) (new ChatComponentText(this.renameText))); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 2d426d35a328ad3d5ce11615dc300bfacfef6df4..a1da26acfc1aa6f367206223222bdeb877241ba3 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -222,6 +222,11 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean anvilAllowColors = false; ++ private void anvilSettings() { ++ anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors); ++ } ++ + public boolean bedExplode = true; + public double bedExplosionPower = 5.0D; + public boolean bedExplosionFire = true; diff --git a/patches/Purpur/patches/server/0103-Add-no-tick-block-list.patch b/patches/Purpur/patches/server/0103-Add-no-tick-block-list.patch new file mode 100644 index 00000000..c9a6a226 --- /dev/null +++ b/patches/Purpur/patches/server/0103-Add-no-tick-block-list.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 7 Aug 2020 12:53:36 -0500 +Subject: [PATCH] Add no-tick block list + + +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index 829d4a7508e1656dbdc912096b7eafcf30cbb5b2..6aea156d7c7a9ca8a357aad6a6781d7209c9b8ae 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -617,10 +617,12 @@ public abstract class BlockBase { + } + + public void a(WorldServer worldserver, BlockPosition blockposition, Random random) { ++ if (worldserver.purpurConfig.noTickBlocks.contains(getBlock())) return; // Purpur + this.getBlock().tickAlways(this.p(), worldserver, blockposition, random); + } + + public void b(WorldServer worldserver, BlockPosition blockposition, Random random) { ++ if (worldserver.purpurConfig.noTickBlocks.contains(getBlock())) return; // Purpur + this.getBlock().tick(this.p(), worldserver, blockposition, random); + } + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 2f41a537fb1d8348b5f65a3e85da841761311744..d5007d00da9a10e99d7fa46a4368d41fb421ac31 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -321,14 +321,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // CraftBukkit end + if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) { + this.nextTickListBlock = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, (block) -> { +- return block == null || block.getBlockData().isAir(); ++ return block == null || block.getBlockData().isAir() || purpurConfig.noTickBlocks.contains(block); // Purpur + }, IRegistry.BLOCK::getKey, this::b, "Blocks"); // Paper - Timings + this.nextTickListFluid = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, (fluidtype) -> { + return fluidtype == null || fluidtype == FluidTypes.EMPTY; + }, IRegistry.FLUID::getKey, this::a, "Fluids"); // Paper - Timings + } else { + this.nextTickListBlock = new TickListServer<>(this, (block) -> { +- return block == null || block.getBlockData().isAir(); ++ return block == null || block.getBlockData().isAir() || purpurConfig.noTickBlocks.contains(block); // Purpur + }, IRegistry.BLOCK::getKey, this::b, "Blocks"); // Paper - Timings + this.nextTickListFluid = new TickListServer<>(this, (fluidtype) -> { + return fluidtype == null || fluidtype == FluidTypes.EMPTY; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index a1da26acfc1aa6f367206223222bdeb877241ba3..8c1842e38a617416040ad1c6776a84252004ff90 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -11,8 +11,10 @@ import org.bukkit.configuration.ConfigurationSection; + + import java.util.ArrayList; + import java.util.HashMap; ++import java.util.HashSet; + import java.util.List; + import java.util.Map; ++import java.util.Set; + import java.util.logging.Level; + + import static net.pl3x.purpur.PurpurConfig.log; +@@ -189,6 +191,16 @@ public class PurpurWorldConfig { + playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); + } + ++ public Set noTickBlocks = new HashSet<>(); ++ private void noTickBlocks() { ++ getList("blocks.no-tick", new ArrayList<>()).forEach(key -> { ++ Block block = IRegistry.BLOCK.get(new MinecraftKey(key.toString())); ++ if (!block.getBlockData().isAir()) { ++ noTickBlocks.add(block); ++ } ++ }); ++ } ++ + public boolean teleportIfOutsideBorder = false; + private void teleportIfOutsideBorder() { + teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); diff --git a/patches/Purpur/patches/server/0104-Add-option-to-disable-dolphin-treasure-searching.patch b/patches/Purpur/patches/server/0104-Add-option-to-disable-dolphin-treasure-searching.patch new file mode 100644 index 00000000..df0626bd --- /dev/null +++ b/patches/Purpur/patches/server/0104-Add-option-to-disable-dolphin-treasure-searching.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 8 Aug 2020 16:11:51 -0500 +Subject: [PATCH] Add option to disable dolphin treasure searching + + +diff --git a/src/main/java/net/minecraft/server/EntityDolphin.java b/src/main/java/net/minecraft/server/EntityDolphin.java +index 2916ee89ef7a14619703dfbe7efd5c78a2f34337..664f9693368852bfb06a7a3bd0862a10cbc81747 100644 +--- a/src/main/java/net/minecraft/server/EntityDolphin.java ++++ b/src/main/java/net/minecraft/server/EntityDolphin.java +@@ -329,6 +329,7 @@ public class EntityDolphin extends EntityWaterAnimal { + + @Override + public boolean a() { ++ if (this.a.world.purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur + return this.a.gotFish() && this.a.getAirTicks() >= 100 && this.a.world.getWorld().canGenerateStructures(); // MC-151364, SPIGOT-5494: hangs if generate-structures=false + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 8c1842e38a617416040ad1c6776a84252004ff90..5fe074cab7afdcb1a30801f2f59b5a3080d93759 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -341,6 +341,11 @@ public class PurpurWorldConfig { + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + } + ++ public boolean dolphinDisableTreasureSearching = false; ++ private void dolphinSettings() { ++ dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); ++ } ++ + public boolean drownedJockeyOnlyBaby = true; + public double drownedJockeyChance = 0.05D; + public boolean drownedJockeyTryExistingChickens = true; diff --git a/patches/Purpur/patches/server/0105-Short-enderman-height.patch b/patches/Purpur/patches/server/0105-Short-enderman-height.patch new file mode 100644 index 00000000..0b73b0a9 --- /dev/null +++ b/patches/Purpur/patches/server/0105-Short-enderman-height.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 10 Aug 2020 21:46:22 -0500 +Subject: [PATCH] Short enderman height + + +diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java +index 7b175240e44b0c7eb5044d7bcaf54dac22f50f2a..e3364032669b473c799b759f5f89468b7584d9f5 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderman.java ++++ b/src/main/java/net/minecraft/server/EntityEnderman.java +@@ -321,6 +321,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + public boolean damageEntity(DamageSource damagesource, float f) { + if (this.isInvulnerable(damagesource)) { + return false; ++ } else if (net.pl3x.purpur.PurpurConfig.endermanShortHeight && damagesource == DamageSource.STUCK) { return false; // Purpur - no suffocation damage if short height + } else if (damagesource instanceof EntityDamageSourceIndirect) { + if (this.tryEscape(EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start + for (int i = 0; i < 64; ++i) { +diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java +index 37b984a5b6c1c6e146e1c4b0947d1e39a051cfbb..919cf670327bed6faa50f29c9bf7a9b54174f7f2 100644 +--- a/src/main/java/net/minecraft/server/EntityTypes.java ++++ b/src/main/java/net/minecraft/server/EntityTypes.java +@@ -137,7 +137,8 @@ public class EntityTypes { + private IChatBaseComponent bp; + @Nullable + private MinecraftKey bq; +- private final EntitySize br; ++ public void setEntitySize(EntitySize entitySize) { this.br = entitySize; } // Purpur - OBFHELPER ++ private EntitySize br; // Purpur - remove final + + private static EntityTypes a(String s, EntityTypes.Builder entitytypes_builder) { // CraftBukkit - decompile error + return (EntityTypes) IRegistry.a((IRegistry) IRegistry.ENTITY_TYPE, s, (Object) entitytypes_builder.a(s)); +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 17a8b43892fdab87a80cbf076c7814bfab087142..c314a8c9a921a95cea43b748e2037521d948e1e7 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -2,6 +2,8 @@ package net.pl3x.purpur; + + import co.aikar.timings.TimingsManager; + import com.google.common.base.Throwables; ++import net.minecraft.server.EntitySize; ++import net.minecraft.server.EntityTypes; + import net.minecraft.server.MinecraftServer; + import net.pl3x.purpur.command.PurpurCommand; + import org.bukkit.Bukkit; +@@ -191,6 +193,12 @@ public class PurpurConfig { + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); + } + ++ public static boolean endermanShortHeight = false; ++ private static void entitySettings() { ++ endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight); ++ if (endermanShortHeight) EntityTypes.ENDERMAN.setEntitySize(EntitySize.b(0.6F, 1.9F)); ++ } ++ + public static boolean dontSendUselessEntityPackets = false; + private static void dontSendUselessEntityPackets() { + dontSendUselessEntityPackets = getBoolean("settings.dont-send-useless-entity-packets", dontSendUselessEntityPackets); diff --git a/patches/Purpur/patches/server/0106-Stop-squids-floating-on-top-of-water.patch b/patches/Purpur/patches/server/0106-Stop-squids-floating-on-top-of-water.patch new file mode 100644 index 00000000..3c4866d4 --- /dev/null +++ b/patches/Purpur/patches/server/0106-Stop-squids-floating-on-top-of-water.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 13 Aug 2020 04:00:26 -0500 +Subject: [PATCH] Stop squids floating on top of water + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 905e3a98fe900c82053b1514122bfe6cf1000dfa..e59826b3e822a49378ae0d7b7cbca8c5d5ff476d 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -3448,8 +3448,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.lastYaw = this.yaw; + } + ++ // Purpur start ++ public AxisAlignedBB getAxisForFluidCheck() { ++ return this.getBoundingBox().shrink(0.001D); ++ } + public boolean a(Tag tag, double d0) { +- AxisAlignedBB axisalignedbb = this.getBoundingBox().shrink(0.001D); ++ AxisAlignedBB axisalignedbb = getAxisForFluidCheck(); ++ // Purpur end + int i = MathHelper.floor(axisalignedbb.minX); + int j = MathHelper.f(axisalignedbb.maxX); + int k = MathHelper.floor(axisalignedbb.minY); +diff --git a/src/main/java/net/minecraft/server/EntitySquid.java b/src/main/java/net/minecraft/server/EntitySquid.java +index b21605a62365fe24f315f35bd840b4740fc80f0e..148e4b158734f136832e5c17bdc69634c0f294aa 100644 +--- a/src/main/java/net/minecraft/server/EntitySquid.java ++++ b/src/main/java/net/minecraft/server/EntitySquid.java +@@ -25,6 +25,14 @@ public class EntitySquid extends EntityWaterAnimal { + this.bu = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; + } + ++ // Purpur start ++ @Override ++ public AxisAlignedBB getAxisForFluidCheck() { ++ // Stops squids from floating just over the water ++ return this.getBoundingBox().shrink(0.001D).offsetY(world.purpurConfig.squidOffsetWaterCheck); ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new EntitySquid.PathfinderGoalSquid(this)); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 5fe074cab7afdcb1a30801f2f59b5a3080d93759..2a997302e1df398f9ca546a1acfa3a9cf70c7d54 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -489,8 +489,10 @@ public class PurpurWorldConfig { + } + + public boolean squidImmuneToEAR = true; ++ public double squidOffsetWaterCheck = 0.0D; + private void squidSettings() { + squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); ++ squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); + } + + public int villagerBrainTicks = 1; diff --git a/patches/Purpur/patches/server/0107-Ridables.patch b/patches/Purpur/patches/server/0107-Ridables.patch new file mode 100644 index 00000000..fd8c4aa2 --- /dev/null +++ b/patches/Purpur/patches/server/0107-Ridables.patch @@ -0,0 +1,6431 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Jul 2020 22:19:49 -0500 +Subject: [PATCH] Ridables + + +diff --git a/src/main/java/net/minecraft/server/AttributeDefaults.java b/src/main/java/net/minecraft/server/AttributeDefaults.java +index 8f95a4e49714e352b9cdf82d7f4acdd3459978ad..ab05b5e403cf54756aa35891dc0d950187a9d4ec 100644 +--- a/src/main/java/net/minecraft/server/AttributeDefaults.java ++++ b/src/main/java/net/minecraft/server/AttributeDefaults.java +@@ -9,7 +9,80 @@ import org.apache.logging.log4j.Logger; + public class AttributeDefaults { + + private static final Logger LOGGER = LogManager.getLogger(); +- private static final Map, AttributeProvider> b = ImmutableMap.builder().put(EntityTypes.ARMOR_STAND, EntityLiving.cL().a()).put(EntityTypes.BAT, EntityBat.m().a()).put(EntityTypes.BEE, EntityBee.eZ().a()).put(EntityTypes.BLAZE, EntityBlaze.m().a()).put(EntityTypes.CAT, EntityCat.fa().a()).put(EntityTypes.CAVE_SPIDER, EntityCaveSpider.m().a()).put(EntityTypes.CHICKEN, EntityChicken.eK().a()).put(EntityTypes.COD, EntityFish.m().a()).put(EntityTypes.COW, EntityCow.eK().a()).put(EntityTypes.CREEPER, EntityCreeper.m().a()).put(EntityTypes.DOLPHIN, EntityDolphin.eM().a()).put(EntityTypes.DONKEY, EntityHorseChestedAbstract.eL().a()).put(EntityTypes.DROWNED, EntityZombie.eS().a()).put(EntityTypes.ELDER_GUARDIAN, EntityGuardianElder.m().a()).put(EntityTypes.ENDERMAN, EntityEnderman.m().a()).put(EntityTypes.ENDERMITE, EntityEndermite.m().a()).put(EntityTypes.ENDER_DRAGON, EntityEnderDragon.m().a()).put(EntityTypes.EVOKER, EntityEvoker.eK().a()).put(EntityTypes.FOX, EntityFox.eK().a()).put(EntityTypes.GHAST, EntityGhast.eJ().a()).put(EntityTypes.GIANT, EntityGiantZombie.m().a()).put(EntityTypes.GUARDIAN, EntityGuardian.eM().a()).put(EntityTypes.HOGLIN, EntityHoglin.eK().a()).put(EntityTypes.HORSE, EntityHorseAbstract.fi().a()).put(EntityTypes.HUSK, EntityZombie.eS().a()).put(EntityTypes.ILLUSIONER, EntityIllagerIllusioner.eK().a()).put(EntityTypes.IRON_GOLEM, EntityIronGolem.m().a()).put(EntityTypes.LLAMA, EntityLlama.fw().a()).put(EntityTypes.MAGMA_CUBE, EntityMagmaCube.m().a()).put(EntityTypes.MOOSHROOM, EntityCow.eK().a()).put(EntityTypes.MULE, EntityHorseChestedAbstract.eL().a()).put(EntityTypes.OCELOT, EntityOcelot.eK().a()).put(EntityTypes.PANDA, EntityPanda.eY().a()).put(EntityTypes.PARROT, EntityParrot.eU().a()).put(EntityTypes.PHANTOM, EntityMonster.eR().a()).put(EntityTypes.PIG, EntityPig.eK().a()).put(EntityTypes.PIGLIN, EntityPiglin.eT().a()).put(EntityTypes.PIGLIN_BRUTE, EntityPiglinBrute.eS().a()).put(EntityTypes.PILLAGER, EntityPillager.eK().a()).put(EntityTypes.PLAYER, EntityHuman.ep().a()).put(EntityTypes.POLAR_BEAR, EntityPolarBear.eK().a()).put(EntityTypes.PUFFERFISH, EntityFish.m().a()).put(EntityTypes.RABBIT, EntityRabbit.eL().a()).put(EntityTypes.RAVAGER, EntityRavager.m().a()).put(EntityTypes.SALMON, EntityFish.m().a()).put(EntityTypes.SHEEP, EntitySheep.eK().a()).put(EntityTypes.SHULKER, EntityShulker.m().a()).put(EntityTypes.SILVERFISH, EntitySilverfish.m().a()).put(EntityTypes.SKELETON, EntitySkeletonAbstract.m().a()).put(EntityTypes.SKELETON_HORSE, EntityHorseSkeleton.eL().a()).put(EntityTypes.SLIME, EntityMonster.eR().a()).put(EntityTypes.SNOW_GOLEM, EntitySnowman.m().a()).put(EntityTypes.SPIDER, EntitySpider.eK().a()).put(EntityTypes.SQUID, EntitySquid.m().a()).put(EntityTypes.STRAY, EntitySkeletonAbstract.m().a()).put(EntityTypes.STRIDER, EntityStrider.eM().a()).put(EntityTypes.TRADER_LLAMA, EntityLlama.fw().a()).put(EntityTypes.TROPICAL_FISH, EntityFish.m().a()).put(EntityTypes.TURTLE, EntityTurtle.eM().a()).put(EntityTypes.VEX, EntityVex.m().a()).put(EntityTypes.VILLAGER, EntityVillager.eY().a()).put(EntityTypes.VINDICATOR, EntityVindicator.eK().a()).put(EntityTypes.WANDERING_TRADER, EntityInsentient.p().a()).put(EntityTypes.WITCH, EntityWitch.eK().a()).put(EntityTypes.WITHER, EntityWither.eK().a()).put(EntityTypes.WITHER_SKELETON, EntitySkeletonAbstract.m().a()).put(EntityTypes.WOLF, EntityWolf.eU().a()).put(EntityTypes.ZOGLIN, EntityZoglin.m().a()).put(EntityTypes.ZOMBIE, EntityZombie.eS().a()).put(EntityTypes.ZOMBIE_HORSE, EntityHorseZombie.eL().a()).put(EntityTypes.ZOMBIE_VILLAGER, EntityZombie.eS().a()).put(EntityTypes.ZOMBIFIED_PIGLIN, EntityPigZombie.eW().a()).build(); ++ private static final Map, AttributeProvider> b = ImmutableMap., AttributeProvider>builder() // Purpur decompile error ++ .put(EntityTypes.ARMOR_STAND, EntityLiving.cL().a()) ++ .put(EntityTypes.BAT, EntityBat.m().a()) ++ .put(EntityTypes.BEE, EntityBee.eZ().a()) ++ .put(EntityTypes.BLAZE, EntityBlaze.m().a()) ++ .put(EntityTypes.CAT, EntityCat.fa().a()) ++ .put(EntityTypes.CAVE_SPIDER, EntityCaveSpider.m().a()) ++ .put(EntityTypes.CHICKEN, EntityChicken.eK().a()) ++ .put(EntityTypes.COD, EntityFish.m().a()) ++ .put(EntityTypes.COW, EntityCow.eK().a()) ++ .put(EntityTypes.CREEPER, EntityCreeper.m().a()) ++ .put(EntityTypes.DOLPHIN, EntityDolphin.eM().a()) ++ .put(EntityTypes.DONKEY, EntityHorseChestedAbstract.eL().a()) ++ .put(EntityTypes.DROWNED, EntityZombie.eS().a()) ++ .put(EntityTypes.ELDER_GUARDIAN, EntityGuardianElder.m().a()) ++ .put(EntityTypes.ENDERMAN, EntityEnderman.m().a()) ++ .put(EntityTypes.ENDERMITE, EntityEndermite.m().a()) ++ .put(EntityTypes.ENDER_DRAGON, EntityEnderDragon.m().a()) ++ .put(EntityTypes.EVOKER, EntityEvoker.eK().a()) ++ .put(EntityTypes.FOX, EntityFox.eK().a()) ++ .put(EntityTypes.GHAST, EntityGhast.eJ().a()) ++ .put(EntityTypes.GIANT, EntityGiantZombie.m().a()) ++ .put(EntityTypes.GUARDIAN, EntityGuardian.eM().a()) ++ .put(EntityTypes.HOGLIN, EntityHoglin.eK().a()) ++ .put(EntityTypes.HORSE, EntityHorseAbstract.fi().a()) ++ .put(EntityTypes.HUSK, EntityZombie.eS().a()) ++ .put(EntityTypes.ILLUSIONER, EntityIllagerIllusioner.eK().a()) ++ .put(EntityTypes.IRON_GOLEM, EntityIronGolem.m().a()) ++ .put(EntityTypes.LLAMA, EntityLlama.fw().a()) ++ .put(EntityTypes.MAGMA_CUBE, EntityMagmaCube.m().a()) ++ .put(EntityTypes.MOOSHROOM, EntityCow.eK().a()) ++ .put(EntityTypes.MULE, EntityHorseChestedAbstract.eL().a()) ++ .put(EntityTypes.OCELOT, EntityOcelot.eK().a()) ++ .put(EntityTypes.PANDA, EntityPanda.eY().a()) ++ .put(EntityTypes.PARROT, EntityParrot.eU().a()) ++ .put(EntityTypes.PHANTOM, EntityPhantom.defaultAttributes().build()) // Purpur ++ .put(EntityTypes.PIG, EntityPig.eK().a()) ++ .put(EntityTypes.PIGLIN, EntityPiglin.eT().a()) ++ .put(EntityTypes.PIGLIN_BRUTE, EntityPiglinBrute.eS().a()) ++ .put(EntityTypes.PILLAGER, EntityPillager.eK().a()) ++ .put(EntityTypes.PLAYER, EntityHuman.ep().a()) ++ .put(EntityTypes.POLAR_BEAR, EntityPolarBear.eK().a()) ++ .put(EntityTypes.PUFFERFISH, EntityFish.m().a()) ++ .put(EntityTypes.RABBIT, EntityRabbit.eL().a()) ++ .put(EntityTypes.RAVAGER, EntityRavager.m().a()) ++ .put(EntityTypes.SALMON, EntityFish.m().a()) ++ .put(EntityTypes.SHEEP, EntitySheep.eK().a()) ++ .put(EntityTypes.SHULKER, EntityShulker.m().a()) ++ .put(EntityTypes.SILVERFISH, EntitySilverfish.m().a()) ++ .put(EntityTypes.SKELETON, EntitySkeletonAbstract.m().a()) ++ .put(EntityTypes.SKELETON_HORSE, EntityHorseSkeleton.eL().a()) ++ .put(EntityTypes.SLIME, EntityMonster.eR().a()) ++ .put(EntityTypes.SNOW_GOLEM, EntitySnowman.m().a()) ++ .put(EntityTypes.SPIDER, EntitySpider.eK().a()) ++ .put(EntityTypes.SQUID, EntitySquid.m().a()) ++ .put(EntityTypes.STRAY, EntitySkeletonAbstract.m().a()) ++ .put(EntityTypes.STRIDER, EntityStrider.eM().a()) ++ .put(EntityTypes.TRADER_LLAMA, EntityLlama.fw().a()) ++ .put(EntityTypes.TROPICAL_FISH, EntityFish.m().a()) ++ .put(EntityTypes.TURTLE, EntityTurtle.eM().a()) ++ .put(EntityTypes.VEX, EntityVex.m().a()) ++ .put(EntityTypes.VILLAGER, EntityVillager.eY().a()) ++ .put(EntityTypes.VINDICATOR, EntityVindicator.eK().a()) ++ .put(EntityTypes.WANDERING_TRADER, EntityInsentient.p().a()) ++ .put(EntityTypes.WITCH, EntityWitch.eK().a()) ++ .put(EntityTypes.WITHER, EntityWither.eK().a()) ++ .put(EntityTypes.WITHER_SKELETON, EntitySkeletonAbstract.m().a()) ++ .put(EntityTypes.WOLF, EntityWolf.eU().a()) ++ .put(EntityTypes.ZOGLIN, EntityZoglin.m().a()) ++ .put(EntityTypes.ZOMBIE, EntityZombie.eS().a()) ++ .put(EntityTypes.ZOMBIE_HORSE, EntityHorseZombie.eL().a()) ++ .put(EntityTypes.ZOMBIE_VILLAGER, EntityZombie.eS().a()) ++ .put(EntityTypes.ZOMBIFIED_PIGLIN, EntityPigZombie.eW().a()) ++ .build(); + + public static AttributeProvider a(EntityTypes entitytypes) { + return (AttributeProvider) AttributeDefaults.b.get(entitytypes); +diff --git a/src/main/java/net/minecraft/server/AttributeProvider.java b/src/main/java/net/minecraft/server/AttributeProvider.java +index dd235a6fc4bc731a344d7211879f9b40bb622c3e..ac902c614dd3054f1a09298a42a88248b69552cd 100644 +--- a/src/main/java/net/minecraft/server/AttributeProvider.java ++++ b/src/main/java/net/minecraft/server/AttributeProvider.java +@@ -101,6 +101,7 @@ public class AttributeProvider { + return this; + } + ++ public AttributeProvider build() { return a(); } // Purpur - OBFHELPER + public AttributeProvider a() { + this.b = true; + return new AttributeProvider(this.a); +diff --git a/src/main/java/net/minecraft/server/ControllerLookDolphin.java b/src/main/java/net/minecraft/server/ControllerLookDolphin.java +index e5bdddfc14b36b3a7b72ca92a9a14245fddb8833..8200ba60b7642a49742809a9c2e2ab7587259a71 100644 +--- a/src/main/java/net/minecraft/server/ControllerLookDolphin.java ++++ b/src/main/java/net/minecraft/server/ControllerLookDolphin.java +@@ -1,6 +1,6 @@ + package net.minecraft.server; + +-public class ControllerLookDolphin extends ControllerLook { ++public class ControllerLookDolphin extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur + + private final int h; + +@@ -10,7 +10,7 @@ public class ControllerLookDolphin extends ControllerLook { + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (this.d) { + this.d = false; + this.a.aC = this.a(this.a.aC, this.h() + 20.0F, this.b); +diff --git a/src/main/java/net/minecraft/server/ControllerMove.java b/src/main/java/net/minecraft/server/ControllerMove.java +index 8f9fb058c11ba5c2e887df048025284cd834ffe9..eaf44694683685e06dd1f012699e5c9d1f91c850 100644 +--- a/src/main/java/net/minecraft/server/ControllerMove.java ++++ b/src/main/java/net/minecraft/server/ControllerMove.java +@@ -6,9 +6,9 @@ public class ControllerMove { + protected double b; + protected double c; + protected double d; +- protected double e; +- protected float f; +- protected float g; ++ protected double e; public double getSpeed() { return e; } public void setSpeed(double speed) { this.e = speed; } // Purpur - OBFHELPER ++ protected float f; public float getForward() { return f; } public void setForward(float forward) { this.f = forward; } // Purpur - OBFHELPER ++ protected float g; public float getStrafe() { return g; } public void setStrafe(float strafe) { this.g = strafe; } // Purpur - OBFHELPER + protected ControllerMove.Operation h; + + public ControllerMove(EntityInsentient entityinsentient) { +diff --git a/src/main/java/net/minecraft/server/DamageSource.java b/src/main/java/net/minecraft/server/DamageSource.java +index bd0267ee4b3782f6d1ec39cba7966ba4f62f1adf..8b36ac2b0950a827763aa2357700f37eec5d00d3 100644 +--- a/src/main/java/net/minecraft/server/DamageSource.java ++++ b/src/main/java/net/minecraft/server/DamageSource.java +@@ -56,6 +56,7 @@ public class DamageSource { + return new EntityDamageSource("mob", entityliving); + } + ++ public static DamageSource indirectMobAttack(Entity entity, EntityLiving entityliving) { return a(entity, entityliving); } // Purpur - OBFHELPER + public static DamageSource a(Entity entity, EntityLiving entityliving) { + return new EntityDamageSourceIndirect("mob", entity, entityliving); + } +@@ -117,6 +118,7 @@ public class DamageSource { + return this.B; + } + ++ public DamageSource setProjectile() { return c(); } // Purpur - OBFHELPER + public DamageSource c() { + this.B = true; + return this; +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index e59826b3e822a49378ae0d7b7cbca8c5d5ff476d..0115e2c73eff9d5e4c6778e32fc54b9c116b6b22 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -80,7 +80,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + +- PlayerChunkMap.EntityTracker tracker; // Paper ++ PlayerChunkMap.EntityTracker tracker; public PlayerChunkMap.EntityTracker getTracker() { return tracker; } // Paper // Purpur + boolean collisionLoadChunks = false; // Paper + Throwable addedToWorldStack; // Paper - entity debug + public CraftEntity getBukkitEntity() { +@@ -105,7 +105,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + private int id; + public boolean i; public final boolean blocksEntitySpawning() { return this.i; } // Paper - OBFHELPER + public final List passengers; +- protected int j; ++ protected int j; public int getRideCooldown() { return j; } // Purpur - OBFHELPER + @Nullable + private Entity vehicle; + public boolean attachedToPlayer; +@@ -121,7 +121,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + public float lastYaw; + public float lastPitch; + private AxisAlignedBB boundingBox; +- protected boolean onGround; ++ public boolean onGround; // Purpur - protected -> public + public boolean positionChanged; + public boolean v; + public boolean velocityChanged; +@@ -177,7 +177,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + private boolean az; + private final double[] aA; + private long aB; +- private EntitySize size; ++ protected EntitySize size; // Purpur - private -> protected + private float headHeight; + // CraftBukkit start + public boolean persist = true; +@@ -1482,6 +1482,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return !this.justCreated && this.M.getDouble(TagsFluid.LAVA) > 0.0D; + } + ++ public void moveRelative(float speed, Vec3D motion) { this.a(speed, motion); } // Purpur - OBFHELPER + public void a(float f, Vec3D vec3d) { + Vec3D vec3d1 = a(vec3d, f, this.yaw); + +@@ -2238,6 +2239,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return this.a(entity, false); + } + ++ public boolean startRiding(Entity entity, boolean flag) { return a(entity, flag); } // Purpur - OBFHELPER + public boolean a(Entity entity, boolean flag) { + for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) { + if (entity1.vehicle == this) { +@@ -2333,6 +2335,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.passengers.add(entity); + } + ++ // Purpur start ++ if (isRidable() && passengers.get(0) == entity && entity instanceof EntityHuman) { ++ EntityHuman entityhuman = (EntityHuman) entity; ++ onMount(entityhuman); ++ this.rider = entityhuman; ++ } ++ // Purpur end + } + return true; // CraftBukkit + } +@@ -2373,6 +2382,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return false; + } + // Spigot end ++ // Purpur start ++ if (rider != null && passengers.get(0) == rider) { ++ onDismount(rider); ++ this.rider = null; ++ } ++ // Purpur end + this.passengers.remove(entity); + entity.j = 60; + } +@@ -2538,6 +2553,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.setFlag(4, flag); + } + ++ public boolean isGlowing() { return bE(); } // Purpur - OBFHELPER + public boolean bE() { + return this.glowing || this.world.isClientSide && this.getFlag(6); + } +@@ -2760,6 +2776,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + + public void setHeadRotation(float f) {} + ++ public void setBodyYaw(float yaw) { n(yaw); } // Purpur - OBFHELPER + public void n(float f) {} + + public boolean bL() { +@@ -3201,6 +3218,18 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return false; + } + ++ // Purpur Start ++ public void sendMessage(String str) { ++ sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(str)); ++ } ++ ++ public void sendMessage(IChatBaseComponent ichatbasecomponent) { ++ if (ichatbasecomponent != null) { ++ sendMessage(ichatbasecomponent, SystemUtils.getNullUUID()); ++ } ++ } ++ // Purpur end ++ + @Override + public void sendMessage(IChatBaseComponent ichatbasecomponent, UUID uuid) {} + +@@ -3653,4 +3682,47 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return ((ChunkProviderServer) world.getChunkProvider()).isInEntityTickingChunk(this); + } + // Paper end ++ ++ // Purpur start ++ private EntityHuman rider; ++ ++ public EntityHuman getRider() { ++ return rider; ++ } ++ ++ public boolean hasRider() { ++ return rider != null; ++ } ++ ++ public boolean isRidable() { ++ return false; ++ } ++ ++ public boolean isRidableInWater() { ++ return false; ++ } ++ ++ public void onMount(EntityHuman entityhuman) { ++ if (this instanceof EntityInsentient) { ++ ((EntityInsentient) this).setGoalTarget(null, null, false); ++ ((EntityInsentient) this).getNavigation().stopPathfinding(); ++ } ++ entityhuman.setJumping(false); // fixes jump on mount ++ } ++ ++ public void onDismount(EntityHuman entityhuman) { ++ } ++ ++ public boolean onSpacebar() { ++ return false; ++ } ++ ++ public boolean onClick(EnumHand hand) { ++ return false; ++ } ++ ++ public boolean processClick(EnumHand hand) { ++ return false; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityBat.java b/src/main/java/net/minecraft/server/EntityBat.java +index 0a59e02d762a096cb3de62e0f8105cc5a5fab8d4..bdf4e798ac8ca27edebd0122b894d15a76ecb9d0 100644 +--- a/src/main/java/net/minecraft/server/EntityBat.java ++++ b/src/main/java/net/minecraft/server/EntityBat.java +@@ -14,9 +14,48 @@ public class EntityBat extends EntityAmbient { + + public EntityBat(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.075F); // Purpur + this.setAsleep(true); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.batRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.batRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.batMaxY; ++ } ++ ++ @Override ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ if (isAsleep()) { ++ setAsleep(false); ++ world.playEffect(null, 1025, new BlockPosition(this).up(), 0); ++ } ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 2; ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, 0.25, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ // Purpur end ++ + @Override + protected void initDatawatcher() { + super.initDatawatcher(); +@@ -61,7 +100,7 @@ public class EntityBat extends EntityAmbient { + protected void collideNearby() {} + + public static AttributeProvider.Builder m() { +- return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 6.0D); ++ return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 6.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur + } + + public boolean isAsleep() { +@@ -93,6 +132,13 @@ public class EntityBat extends EntityAmbient { + + @Override + protected void mobTick() { ++ // Purpur start ++ if (hasRider()) { ++ Vec3D mot = getMot(); ++ setMot(mot.x, mot.y + (getVertical() > 0 ? 0.07D : 0.0D), mot.z); ++ return; ++ } ++ // Purpur end + super.mobTick(); + BlockPosition blockposition = this.getChunkCoordinates(); + BlockPosition blockposition1 = blockposition.up(); +diff --git a/src/main/java/net/minecraft/server/EntityBee.java b/src/main/java/net/minecraft/server/EntityBee.java +index f73641ddb3e82bc653732105ef0a3d41a28e845f..d8354ec4d19fc3fbddc2551ee217acb137482e63 100644 +--- a/src/main/java/net/minecraft/server/EntityBee.java ++++ b/src/main/java/net/minecraft/server/EntityBee.java +@@ -37,6 +37,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + public EntityBee(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ final net.pl3x.purpur.controller.ControllerMoveWASDFlying flyingController = new net.pl3x.purpur.controller.ControllerMoveWASDFlying(this, 0.25F, false); // Purpur + // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 + this.moveController = new ControllerMoveFlying(this, 20, true) { + @Override +@@ -46,6 +47,22 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + } + super.tick(); + } ++ ++ // Purpur start ++ @Override ++ public void a() { // tick ++ if (getEntity().hasRider()) { ++ flyingController.tick(getEntity().getRider()); ++ } else { ++ tick(); ++ } ++ } ++ ++ @Override ++ public boolean b() { // isUpdating ++ return getEntity().hasRider() || super.b(); ++ } ++ // Purpur end + }; + // Paper end + this.lookController = new EntityBee.j(this); +@@ -56,6 +73,35 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + this.a(PathType.FENCE, -1.0F); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.beeRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.beeRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.beeMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 2; ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, speed, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ // Purpur end ++ + @Override + protected void initDatawatcher() { + super.initDatawatcher(); +@@ -70,6 +116,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(0, new EntityBee.b(this, 1.399999976158142D, true)); + this.goalSelector.a(1, new EntityBee.d()); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); +@@ -85,6 +132,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + this.goalSelector.a(7, new EntityBee.g()); + this.goalSelector.a(8, new EntityBee.l()); + this.goalSelector.a(9, new PathfinderGoalFloat(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new EntityBee.h(this)).a(new Class[0])); + this.targetSelector.a(2, new EntityBee.c(this)); + this.targetSelector.a(3, new PathfinderGoalUniversalAngerReset<>(this, true)); +@@ -555,6 +603,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + private d() { + super(); // CraftBukkit - decompile error ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - enter hive + } + + @Override +@@ -617,6 +666,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + private g() { + super(); // CraftBukkit - decompile error ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - grow crop + } + + @Override +@@ -681,6 +731,7 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + + private i() { + super(); // CraftBukkit - decompile error ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - go to hive + } + + @Override +@@ -904,16 +955,16 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + } + } + +- class j extends ControllerLook { ++ class j extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur + + j(EntityInsentient entityinsentient) { + super(entityinsentient); + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (!EntityBee.this.isAngry()) { +- super.a(); ++ super.tick(); // Purpur + } + } + +diff --git a/src/main/java/net/minecraft/server/EntityBlaze.java b/src/main/java/net/minecraft/server/EntityBlaze.java +index 74082136b38491a0a50d152c455edfa61a6afb9f..dcfad16e06450068d5801fc002c9650102dbf995 100644 +--- a/src/main/java/net/minecraft/server/EntityBlaze.java ++++ b/src/main/java/net/minecraft/server/EntityBlaze.java +@@ -10,6 +10,7 @@ public class EntityBlaze extends EntityMonster { + + public EntityBlaze(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.3F); // Purpur + this.a(PathType.WATER, -1.0F); + this.a(PathType.LAVA, 8.0F); + this.a(PathType.DANGER_FIRE, 0.0F); +@@ -17,19 +18,50 @@ public class EntityBlaze extends EntityMonster { + this.f = 10; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.blazeRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.blazeRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.blazeMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue(); ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, 1.0, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(4, new EntityBlaze.PathfinderGoalBlazeFireball(this)); + this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D)); + this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D, 0.0F)); + this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); +- this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a()); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // Purpur - decompile error + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + } + + public static AttributeProvider.Builder m() { +- return EntityMonster.eR().a(GenericAttributes.ATTACK_DAMAGE, 6.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.23000000417232513D).a(GenericAttributes.FOLLOW_RANGE, 48.0D); ++ return EntityMonster.eR().a(GenericAttributes.ATTACK_DAMAGE, 6.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.23000000417232513D).a(GenericAttributes.FOLLOW_RANGE, 48.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur + } + + @Override +@@ -84,6 +116,14 @@ public class EntityBlaze extends EntityMonster { + + @Override + protected void mobTick() { ++ // Purpur start ++ if (hasRider()) { ++ Vec3D mot = getMot(); ++ setMot(mot.x, getVertical() > 0 ? 0.07D : -0.07D, mot.z); ++ return; ++ } ++ // Purpur end ++ + --this.c; + if (this.c <= 0) { + this.c = 100; +diff --git a/src/main/java/net/minecraft/server/EntityCat.java b/src/main/java/net/minecraft/server/EntityCat.java +index 35f511c398795a0edeb5fe6d802f2a2bf754bf3a..700314b02abb355e6a600d744887a705cbcfb4e4 100644 +--- a/src/main/java/net/minecraft/server/EntityCat.java ++++ b/src/main/java/net/minecraft/server/EntityCat.java +@@ -41,6 +41,25 @@ public class EntityCat extends EntityTameableAnimal { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.catRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.catRidableInWater; ++ } ++ ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ setSitting(false); ++ setSleepingWithOwner(false); ++ setHeadDown(false); ++ } ++ // Purpur end ++ + public MinecraftKey eU() { + return (MinecraftKey) EntityCat.bq.getOrDefault(this.getCatType(), EntityCat.bq.get(0)); + } +@@ -48,7 +67,8 @@ public class EntityCat extends EntityTameableAnimal { + @Override + protected void initPathfinder() { + this.bx = new EntityCat.PathfinderGoalTemptChance(this, 0.6D, EntityCat.br, true); +- this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalSit(this)); + this.goalSelector.a(2, new EntityCat.b(this)); + this.goalSelector.a(3, this.bx); +@@ -60,6 +80,7 @@ public class EntityCat extends EntityTameableAnimal { + this.goalSelector.a(10, new PathfinderGoalBreed(this, 0.8D)); + this.goalSelector.a(11, new PathfinderGoalRandomStrollLand(this, 0.8D, 1.0000001E-5F)); + this.goalSelector.a(12, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 10.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalRandomTargetNonTamed<>(this, EntityRabbit.class, false, (Predicate) null)); + this.targetSelector.a(1, new PathfinderGoalRandomTargetNonTamed<>(this, EntityTurtle.class, false, EntityTurtle.bo)); + } +@@ -76,6 +97,7 @@ public class EntityCat extends EntityTameableAnimal { + this.datawatcher.set(EntityCat.bs, i); + } + ++ public void setSleepingWithOwner(boolean flag) { x(flag); } // Purpur - OBFHELPER + public void x(boolean flag) { + this.datawatcher.set(EntityCat.bt, flag); + } +@@ -84,6 +106,7 @@ public class EntityCat extends EntityTameableAnimal { + return (Boolean) this.datawatcher.get(EntityCat.bt); + } + ++ public void setHeadDown(boolean flag) { y(flag); } // Purpur - OBFHELPER + public void y(boolean flag) { + this.datawatcher.set(EntityCat.bu, flag); + } +@@ -302,6 +325,7 @@ public class EntityCat extends EntityTameableAnimal { + + @Override + public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) { ++ if (hasRider()) return EnumInteractionResult.PASS; // Purpur + ItemStack itemstack = entityhuman.b(enumhand); + Item item = itemstack.getItem(); + +@@ -399,6 +423,7 @@ public class EntityCat extends EntityTameableAnimal { + + public b(EntityCat entitycat) { + this.a = entitycat; ++ this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - lay on owner + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCaveSpider.java b/src/main/java/net/minecraft/server/EntityCaveSpider.java +index 776f3d25a6eeb5e97667dd06c062d1045d1afa81..2e1f2dec17e7761b6534f29bbec813d135250e4f 100644 +--- a/src/main/java/net/minecraft/server/EntityCaveSpider.java ++++ b/src/main/java/net/minecraft/server/EntityCaveSpider.java +@@ -8,6 +8,18 @@ public class EntityCaveSpider extends EntitySpider { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.caveSpiderRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.caveSpiderRidableInWater; ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder m() { + return EntitySpider.eK().a(GenericAttributes.MAX_HEALTH, 12.0D); + } +diff --git a/src/main/java/net/minecraft/server/EntityChicken.java b/src/main/java/net/minecraft/server/EntityChicken.java +index 8fb5d5c75e79a81ab46af3fbb96ebc41804113c4..ee59a9f272a9caebec8f2329e1e4b22ddd27a0f9 100644 +--- a/src/main/java/net/minecraft/server/EntityChicken.java ++++ b/src/main/java/net/minecraft/server/EntityChicken.java +@@ -18,6 +18,16 @@ public class EntityChicken extends EntityAnimal { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.chickenRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.chickenRidableInWater; ++ } ++ + @Override + protected void initAttributes() { + if (world.purpurConfig.chickenRetaliate) { +@@ -29,6 +39,7 @@ public class EntityChicken extends EntityAnimal { + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + //this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); // Purpur - moved down + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); + this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, EntityChicken.bv)); +@@ -39,6 +50,7 @@ public class EntityChicken extends EntityAnimal { + // Purpur start + if (world.purpurConfig.chickenRetaliate) { + this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, false)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this)); + } else { + this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); +diff --git a/src/main/java/net/minecraft/server/EntityCod.java b/src/main/java/net/minecraft/server/EntityCod.java +index 9a99af6e9cec9679af07ab6017f9beb427fa88ad..039fae4c29648afa85ea1b27d82cfe51c4165315 100644 +--- a/src/main/java/net/minecraft/server/EntityCod.java ++++ b/src/main/java/net/minecraft/server/EntityCod.java +@@ -6,6 +6,18 @@ public class EntityCod extends EntityFishSchool { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.codRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ // Purpur end ++ + @Override + protected ItemStack eK() { + return new ItemStack(Items.COD_BUCKET); +diff --git a/src/main/java/net/minecraft/server/EntityComplexPart.java b/src/main/java/net/minecraft/server/EntityComplexPart.java +index c1b1bd123e7a18d37fd55cacc29ebca32030e5f0..26ff230ef774999bfe37b3327a7cf711799cbf2f 100644 +--- a/src/main/java/net/minecraft/server/EntityComplexPart.java ++++ b/src/main/java/net/minecraft/server/EntityComplexPart.java +@@ -47,4 +47,11 @@ public class EntityComplexPart extends Entity { + public EntitySize a(EntityPose entitypose) { + return this.d; + } ++ ++ // Purpur start ++ @Override ++ public EnumInteractionResult a(EntityHuman entityhuman, EnumHand enumhand) { ++ return owner.isAlive() ? owner.tryRide(entityhuman, enumhand) : EnumInteractionResult.PASS; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java +index cfb009c811bd2908d38da1b0007cb7aaed4e42c3..1219b0aa9c62bc9a1bda45cc9e9a27f14a28fe2e 100644 +--- a/src/main/java/net/minecraft/server/EntityCow.java ++++ b/src/main/java/net/minecraft/server/EntityCow.java +@@ -11,9 +11,22 @@ public class EntityCow extends EntityAnimal { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.cowRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.cowRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalPanic(this, 2.0D)); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); + if (world.purpurConfig.cowFeedMushrooms > 0) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.25D, RecipeItemStack.a(Items.WHEAT, Blocks.RED_MUSHROOM.getItem(), Blocks.BROWN_MUSHROOM.getItem()), false)); else // Purpur +@@ -55,6 +68,7 @@ public class EntityCow extends EntityAnimal { + + @Override + public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) { ++ if (hasRider()) return EnumInteractionResult.PASS; // Purpur + ItemStack itemstack = entityhuman.b(enumhand); + + if (itemstack.getItem() == Items.BUCKET && !this.isBaby()) { +@@ -62,7 +76,7 @@ public class EntityCow extends EntityAnimal { + org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((WorldServer) entityhuman.world, entityhuman, this.getChunkCoordinates(), this.getChunkCoordinates(), null, itemstack, Items.MILK_BUCKET, enumhand); // Paper - add enumHand + + if (event.isCancelled()) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + // CraftBukkit end + +@@ -73,7 +87,7 @@ public class EntityCow extends EntityAnimal { + return EnumInteractionResult.a(this.world.isClientSide); + // Purpur start - feed mushroom to change to mooshroom + } else if (world.purpurConfig.cowFeedMushrooms > 0 && getEntityType() != EntityTypes.MOOSHROOM && isMushroom(itemstack)) { +- return feedMushroom(entityhuman, itemstack); ++ return feedMushroom(entityhuman, enumhand, itemstack); + // Purpur end + } else { + return super.b(entityhuman, enumhand); +@@ -96,7 +110,7 @@ public class EntityCow extends EntityAnimal { + } + } + +- private EnumInteractionResult feedMushroom(EntityHuman entityhuman, ItemStack itemstack) { ++ private EnumInteractionResult feedMushroom(EntityHuman entityhuman, EnumHand enumhand, ItemStack itemstack) { + world.broadcastEntityEffect(this, (byte) 18); // hearts + playSound(SoundEffects.ENTITY_COW_MILK, 1.0F, 1.0F); + if (incrementFeedCount(itemstack) < world.purpurConfig.cowFeedMushrooms) { +@@ -107,7 +121,7 @@ public class EntityCow extends EntityAnimal { + } + EntityMushroomCow mooshroom = EntityTypes.MOOSHROOM.create(world); + if (mooshroom == null) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); + } + if (itemstack.getItem() == Blocks.BROWN_MUSHROOM.getItem()) { + mooshroom.setVariant(EntityMushroomCow.Type.BROWN); +@@ -126,10 +140,10 @@ public class EntityCow extends EntityAnimal { + mooshroom.setCustomName(this.getCustomName()); + } + if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); + } + if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); + } + this.world.addEntity(mooshroom); + this.die(); +diff --git a/src/main/java/net/minecraft/server/EntityCreeper.java b/src/main/java/net/minecraft/server/EntityCreeper.java +index 2256b81624b12b6f6cb54250b24fa12ad6da621d..d186f257fa3bc613be7ec79cd6a6ff2e747cba78 100644 +--- a/src/main/java/net/minecraft/server/EntityCreeper.java ++++ b/src/main/java/net/minecraft/server/EntityCreeper.java +@@ -18,12 +18,27 @@ public class EntityCreeper extends EntityMonster { + public int maxFuseTicks = 30; + public int explosionRadius = 3; + private int bs; ++ // Purpur start ++ private int spacebarCharge = 0; ++ private int prevSpacebarCharge = 0; ++ private int powerToggleDelay = 0; ++ // Purpur end + + public EntityCreeper(EntityTypes entitytypes, World world) { + super(entitytypes, world); + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.creeperRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.creeperRidableInWater; ++ } ++ + @Override + public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @javax.annotation.Nullable GroupDataEntity groupdataentity, @javax.annotation.Nullable NBTTagCompound nbttagcompound) { + double chance = worldaccess.getMinecraftWorld().purpurConfig.creeperChargedChance; +@@ -32,18 +47,69 @@ public class EntityCreeper extends EntityMonster { + } + return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); + } ++ ++ @Override ++ protected void mobTick() { ++ if (powerToggleDelay > 0) { ++ powerToggleDelay--; ++ } ++ if (hasRider()) { ++ if (getRider().getForward() != 0 || getRider().getStrafe() != 0) { ++ spacebarCharge = 0; ++ setIgnited(false); ++ } ++ if (spacebarCharge == prevSpacebarCharge) { ++ spacebarCharge = 0; ++ } ++ prevSpacebarCharge = spacebarCharge; ++ } ++ super.mobTick(); ++ } ++ ++ @Override ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ setIgnited(false); ++ } ++ ++ @Override ++ public boolean onSpacebar() { ++ if (powerToggleDelay > 0) { ++ return true; // just toggled power, do not jump or ignite ++ } ++ spacebarCharge++; ++ if (spacebarCharge > maxFuseTicks - 2) { ++ spacebarCharge = 0; ++ if (getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) { ++ powerToggleDelay = 20; ++ setPowered(!isPowered()); ++ setIgnited(false); ++ return true; ++ } ++ } ++ if (!isIgnited()) { ++ if (hasRider() && getRider().getForward() == 0 && getRider().getStrafe() == 0 && ++ getRider().getBukkitEntity().hasPermission("allow.special.creeper")) { ++ setIgnited(true); ++ return true; ++ } ++ } ++ return getForward() == 0 && getStrafe() == 0; // do not jump if standing still ++ } + // Purpur end + + @Override + protected void initPathfinder() { + this.goalSelector.a(1, new PathfinderGoalFloat(this)); + this.goalSelector.a(2, new PathfinderGoalSwell(this)); ++ this.goalSelector.a(3, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityOcelot.class, 6.0F, 1.0D, 1.2D)); + this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityCat.class, 6.0F, 1.0D, 1.2D)); + this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, 1.0D, false)); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 0.8D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(6, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, new Class[0])); + } +@@ -174,6 +240,7 @@ public class EntityCreeper extends EntityMonster { + return (Integer) this.datawatcher.get(EntityCreeper.b); + } + ++ public void setSwellDirection(int i) { a(i); } // Purpur - OBFHELPER + public void a(int i) { + this.datawatcher.set(EntityCreeper.b, i); + } +@@ -274,6 +341,7 @@ public class EntityCreeper extends EntityMonster { + com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); + if (event.callEvent()) { + this.datawatcher.set(EntityCreeper.d, event.isIgnited()); ++ if (!event.isIgnited()) setSwellDirection(-1); // Purpur + } + } + // Paper end +diff --git a/src/main/java/net/minecraft/server/EntityDolphin.java b/src/main/java/net/minecraft/server/EntityDolphin.java +index 664f9693368852bfb06a7a3bd0862a10cbc81747..9d5d143a997b74e0777bb79bf14d341ad5340db9 100644 +--- a/src/main/java/net/minecraft/server/EntityDolphin.java ++++ b/src/main/java/net/minecraft/server/EntityDolphin.java +@@ -17,6 +17,7 @@ public class EntityDolphin extends EntityWaterAnimal { + public static final Predicate b = (entityitem) -> { + return !entityitem.p() && entityitem.isAlive() && entityitem.isInWater(); + }; ++ private int spitCooldown; // Purpur + + public EntityDolphin(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -25,6 +26,45 @@ public class EntityDolphin extends EntityWaterAnimal { + this.setCanPickupLoot(true); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.dolphinRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ ++ @Override ++ public boolean onSpacebar() { ++ if (spitCooldown == 0 && hasRider()) { ++ spitCooldown = world.purpurConfig.dolphinSpitCooldown; ++ if (!hasRider()) { ++ return false; ++ } ++ ++ org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity(); ++ if (!player.hasPermission("allow.special.dolphin")) { ++ return false; ++ } ++ ++ org.bukkit.Location loc = player.getEyeLocation(); ++ loc.setPitch(loc.getPitch() - 10); ++ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector()); ++ ++ net.pl3x.purpur.entity.DolphinSpit spit = new net.pl3x.purpur.entity.DolphinSpit(world, this); ++ spit.shoot(target.getX() - locX(), target.getY() - locY(), target.getZ() - locZ(), world.purpurConfig.dolphinSpitSpeed, 5.0F); ++ ++ world.addEntity(spit); ++ playSound(SoundEffects.ENTITY_DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F); ++ return true; ++ } ++ return false; ++ } ++ // Purpur end ++ + @Nullable + @Override + public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { +@@ -99,6 +139,7 @@ public class EntityDolphin extends EntityWaterAnimal { + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalBreath(this)); + this.goalSelector.a(0, new PathfinderGoalWater(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityDolphin.b(this)); + this.goalSelector.a(2, new EntityDolphin.c(this, 4.0D)); + this.goalSelector.a(4, new PathfinderGoalRandomSwim(this, 1.0D, 10)); +@@ -109,6 +150,7 @@ public class EntityDolphin extends EntityWaterAnimal { + this.goalSelector.a(8, new EntityDolphin.d()); + this.goalSelector.a(8, new PathfinderGoalFollowBoat(this)); + this.goalSelector.a(9, new PathfinderGoalAvoidTarget<>(this, EntityGuardian.class, 8.0F, 1.0D, 1.0D)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityGuardian.class})).a(new Class[0])); // CraftBukkit - decompile error + } + +@@ -160,7 +202,7 @@ public class EntityDolphin extends EntityWaterAnimal { + + @Override + protected boolean n(Entity entity) { +- return true; ++ return getRideCooldown() <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs + } + + @Override +@@ -195,6 +237,9 @@ public class EntityDolphin extends EntityWaterAnimal { + @Override + public void tick() { + super.tick(); ++ if (spitCooldown > 0) { ++ spitCooldown--; ++ } + if (this.isNoAI()) { + this.setAirTicks(this.bH()); + } else { +@@ -468,7 +513,7 @@ public class EntityDolphin extends EntityWaterAnimal { + + private int b; + +- private d() {} ++ private d() { this.a(java.util.EnumSet.of(PathfinderGoal.Type.MOVE)); } // Purpur - play with item + + @Override + public boolean a() { +@@ -536,7 +581,7 @@ public class EntityDolphin extends EntityWaterAnimal { + } + } + +- static class a extends ControllerMove { ++ static class a extends net.pl3x.purpur.controller.ControllerMoveWASDWater { // Purpur + + private final EntityDolphin i; + +@@ -546,7 +591,20 @@ public class EntityDolphin extends EntityWaterAnimal { + } + + @Override +- public void a() { ++ // Purpur start ++ public void tick(EntityHuman rider) { ++ if (this.i.getAirTicks() < 150) { ++ // if drowning override player WASD controls to find air ++ tick(); ++ } else { ++ super.tick(rider); ++ this.i.setMot(this.i.getMot().add(0.0D, 0.005D, 0.0D)); ++ } ++ } ++ ++ @Override ++ public void tick() { ++ // Purpur end + if (this.i.isInWater()) { + this.i.setMot(this.i.getMot().add(0.0D, 0.005D, 0.0D)); + } +diff --git a/src/main/java/net/minecraft/server/EntityDrowned.java b/src/main/java/net/minecraft/server/EntityDrowned.java +index 1a102816921fa3b40f6d364bb826db4459f68eb2..125eab60f2b4657e52a71eddf7586c574945252e 100644 +--- a/src/main/java/net/minecraft/server/EntityDrowned.java ++++ b/src/main/java/net/minecraft/server/EntityDrowned.java +@@ -22,6 +22,16 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.drownedRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.drownedRidableInWater; ++ } ++ + @Override + public boolean jockeyOnlyBaby() { + return world.purpurConfig.drownedJockeyOnlyBaby; +@@ -219,7 +229,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + this.d = flag; + } + +- static class d extends ControllerMove { ++ static class d extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur + + private final EntityDrowned i; + +@@ -229,7 +239,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + } + + @Override +- public void a() { ++ public void tick() { // Purpur + EntityLiving entityliving = this.i.getGoalTarget(); + + if (this.i.eW() && this.i.isInWater()) { +@@ -262,7 +272,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + this.i.setMot(this.i.getMot().add(0.0D, -0.008D, 0.0D)); + } + +- super.a(); ++ super.tick(); // Purpur + } + + } +@@ -401,6 +411,7 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + this.a = entitydrowned; + this.b = d0; + this.c = i; ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - swim up + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java +index a5a2182455542bb8fd62941bd0da2f38ba698f35..17a9217ed2d1677371d12b4ab0552378cd71652f 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java +@@ -46,6 +46,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + private final int[] bK = new int[24]; + private final Path bL = new Path(); + private Explosion explosionSource = new Explosion(null, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.Effect.DESTROY); // CraftBukkit - reusable source for CraftTNTPrimed.getSource() ++ private boolean hadRider; // Purpur + + public EntityEnderDragon(EntityTypes entitytypes, World world) { + super(EntityTypes.ENDER_DRAGON, world); +@@ -60,8 +61,44 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + } + + this.bG = new DragonControllerManager(this); ++ // Purpur start ++ this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlying(this) { ++ @Override ++ public void tick() { ++ // dragon doesn't use the controller. do nothing ++ } ++ }; ++ this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this) { ++ @Override ++ public void tick() { ++ // dragon doesn't use the controller. do nothing ++ } ++ ++ @Override ++ public void tick(EntityHuman rider) { ++ setYawPitch(rider.yaw - 180F, rider.pitch * 0.5F); ++ } ++ }; ++ // Purpur end ++ } ++ ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.enderDragonRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.enderDragonRidableInWater; + } + ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.enderDragonMaxY; ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder m() { + return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 200.0D); + } +@@ -94,6 +131,37 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + + @Override + public void movementTick() { ++ // Purpur start ++ boolean hasRider = getRider() != null; ++ if (hasRider) { ++ if (!hadRider) { ++ hadRider = true; ++ noclip = false; ++ this.size = EntitySize.b(4.0F, 2.0F); ++ } ++ ++ // dragon doesn't use controllers, so must tick manually ++ moveController.a(); ++ lookController.a(); ++ ++ moveRelative((float) getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * 0.1F, new Vec3D(-getStrafe(), getVertical(), -getForward())); ++ Vec3D mot = getMot(); ++ setMot(mot); ++ move(EnumMoveType.PLAYER, mot); ++ ++ mot = mot.multiply(0.9F, 0.9F, 0.9F); ++ setMot(mot); ++ ++ // control wing flap speed on client ++ getDragonControllerManager().setControllerPhase(mot.getX() * mot.getX() + mot.getZ() * mot.getZ() < 0.005F ? DragonControllerPhase.HOVER : DragonControllerPhase.HOLDING_PATTERN); ++ } else if (hadRider) { ++ hadRider = false; ++ noclip = true; ++ this.size = EntitySize.b(16.0F, 8.0F); ++ getDragonControllerManager().setControllerPhase(DragonControllerPhase.HOLDING_PATTERN); // HoldingPattern ++ } ++ // Purpur end ++ + float f; + float f1; + +@@ -115,6 +183,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + + this.bp = this.bq; + if (this.dl()) { ++ if (hasRider) ejectPassengers(); // Purpur + f = (this.random.nextFloat() - 0.5F) * 8.0F; + f1 = (this.random.nextFloat() - 0.5F) * 4.0F; + float f2 = (this.random.nextFloat() - 0.5F) * 8.0F; +@@ -126,9 +195,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + + f1 = 0.2F / (MathHelper.sqrt(c(vec3d)) * 10.0F + 1.0F); + f1 *= (float) Math.pow(2.0D, vec3d.y); +- if (this.bG.a().a()) { ++ if (!hasRider && this.bG.a().a()) { // Purpur + this.bq += 0.1F; +- } else if (this.br) { ++ } else if (!hasRider && this.br) { // Purpur + this.bq += f1 * 0.5F; + } else { + this.bq += f1; +@@ -172,7 +241,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + } + + this.bG.a().b(); +- } else { ++ } else if (!hasRider) { // Purpur + IDragonController idragoncontroller = this.bG.a(); + + idragoncontroller.c(); +@@ -239,7 +308,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + this.a(this.bz, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F)); + this.a(this.bD, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F)); + this.a(this.bE, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F)); +- if (!this.world.isClientSide && this.hurtTicks == 0) { ++ if (!hasRider && !this.world.isClientSide && this.hurtTicks == 0) { // Purpur + this.a(this.world.getEntities(this, this.bD.getBoundingBox().grow(4.0D, 2.0D, 4.0D).d(0.0D, -2.0D, 0.0D), IEntitySelector.e)); + this.a(this.world.getEntities(this, this.bE.getBoundingBox().grow(4.0D, 2.0D, 4.0D).d(0.0D, -2.0D, 0.0D), IEntitySelector.e)); + this.b(this.world.getEntities(this, this.bo.getBoundingBox().g(1.0D), IEntitySelector.e)); +@@ -282,7 +351,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + } + + if (!this.world.isClientSide) { +- this.br = this.b(this.bo.getBoundingBox()) | this.b(this.by.getBoundingBox()) | this.b(this.bz.getBoundingBox()); ++ this.br = !hasRider && this.b(this.bo.getBoundingBox()) | this.b(this.by.getBoundingBox()) | this.b(this.bz.getBoundingBox()); // Purpur + if (this.bF != null) { + this.bF.b(this); + } +@@ -949,7 +1018,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + + @Override + protected boolean n(Entity entity) { +- return false; ++ return getRideCooldown() <= 0; // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java +index e3364032669b473c799b759f5f89468b7584d9f5..995849212c25568d3aa28ada78babf8b8e669960 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderman.java ++++ b/src/main/java/net/minecraft/server/EntityEnderman.java +@@ -30,9 +30,22 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + this.a(PathType.WATER, -1.0F); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.endermanRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.endermanRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityEnderman.a(this)); + this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false)); + this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D, 0.0F)); +@@ -40,6 +53,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); + this.goalSelector.a(10, new EntityEnderman.PathfinderGoalEndermanPlaceBlock(this)); + this.goalSelector.a(11, new EntityEnderman.PathfinderGoalEndermanPickupBlock(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new EntityEnderman.PathfinderGoalPlayerWhoLookedAtTarget(this, this::a_)); + this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, new Class[0])); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityEndermite.class, 10, true, false, EntityEnderman.bq)); +@@ -220,7 +234,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + @Override + protected void mobTick() { +- if (this.world.isDay() && this.ticksLived >= this.bs + 600) { ++ if (!hasRider() && this.world.isDay() && this.ticksLived >= this.bs + 600) { // Purpur - no random teleporting + float f = this.aR(); + + if (f > 0.5F && this.world.e(this.getChunkCoordinates()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper +@@ -322,6 +336,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + if (this.isInvulnerable(damagesource)) { + return false; + } else if (net.pl3x.purpur.PurpurConfig.endermanShortHeight && damagesource == DamageSource.STUCK) { return false; // Purpur - no suffocation damage if short height ++ } else if (hasRider()) { return super.damageEntity(damagesource, f); // Purpur - no teleporting on damage + } else if (damagesource instanceof EntityDamageSourceIndirect) { + if (this.tryEscape(EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start + for (int i = 0; i < 64; ++i) { +@@ -366,6 +381,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + public PathfinderGoalEndermanPickupBlock(EntityEnderman entityenderman) { + this.enderman = entityenderman; ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur + } + + @Override +@@ -408,6 +424,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + public PathfinderGoalEndermanPlaceBlock(EntityEnderman entityenderman) { + this.a = entityenderman; ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEndermite.java b/src/main/java/net/minecraft/server/EntityEndermite.java +index fcebf36312c95df876652d18cf27bf1cc6589226..7eef7f523a17434b38492006526920a955fc9120 100644 +--- a/src/main/java/net/minecraft/server/EntityEndermite.java ++++ b/src/main/java/net/minecraft/server/EntityEndermite.java +@@ -12,14 +12,28 @@ public class EntityEndermite extends EntityMonster { + this.f = 3; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.endermiteRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.endermiteRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false)); + this.goalSelector.a(3, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); +- this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a()); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur ++ this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // Purpur - decompile error + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + } + +diff --git a/src/main/java/net/minecraft/server/EntityEvoker.java b/src/main/java/net/minecraft/server/EntityEvoker.java +index f40fbef32f2fc1f37f2065ad598f013766cf12f2..c75dc75611991028e9de6db7c57304e913251a6b 100644 +--- a/src/main/java/net/minecraft/server/EntityEvoker.java ++++ b/src/main/java/net/minecraft/server/EntityEvoker.java +@@ -12,10 +12,23 @@ public class EntityEvoker extends EntityIllagerWizard { + this.f = 10; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.evokerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.evokerRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityEvoker.b()); + this.goalSelector.a(2, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 0.6D, 1.0D)); + this.goalSelector.a(4, new EntityEvoker.c()); +@@ -24,6 +37,7 @@ public class EntityEvoker extends EntityIllagerWizard { + this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); + this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // Paper - decompile fix + this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)).a(300)); + this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)).a(300)); +@@ -104,6 +118,7 @@ public class EntityEvoker extends EntityIllagerWizard { + + public d() { + super(); ++ this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - wolololo spell + } + + @Override +@@ -182,6 +197,7 @@ public class EntityEvoker extends EntityIllagerWizard { + private c() { + super(); + this.e = (new PathfinderTargetCondition()).a(16.0D).c().e().a().b(); ++ this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - summon spell + } + + @Override +@@ -238,6 +254,7 @@ public class EntityEvoker extends EntityIllagerWizard { + + private a() { + super(); ++ this.a(java.util.EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - attack with spell + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityFish.java b/src/main/java/net/minecraft/server/EntityFish.java +index 9b84564401e30a7a2aafae023a1eb3b2eda3f112..668f9d61b72cf0d8e7bb0d1cd985ae653a587466 100644 +--- a/src/main/java/net/minecraft/server/EntityFish.java ++++ b/src/main/java/net/minecraft/server/EntityFish.java +@@ -70,13 +70,12 @@ public abstract class EntityFish extends EntityWaterAnimal { + @Override + protected void initPathfinder() { + super.initPathfinder(); +- this.goalSelector.a(0, new PathfinderGoalPanic(this, 1.25D)); +- PathfinderGoalSelector pathfindergoalselector = this.goalSelector; +- Predicate predicate = IEntitySelector.g; +- +- predicate.getClass(); +- pathfindergoalselector.a(2, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 1.6D, 1.4D, predicate::test)); +- this.goalSelector.a(4, new EntityFish.b(this)); ++ // Purpur start ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur ++ this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D)); ++ this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityHuman.class, 8.0F, 1.6D, 1.4D, IEntitySelector.g::test)); // Purpur - decompile error ++ this.goalSelector.a(5, new EntityFish.b(this)); ++ // Purpur end + } + + @Override +@@ -87,7 +86,7 @@ public abstract class EntityFish extends EntityWaterAnimal { + @Override + public void g(Vec3D vec3d) { + if (this.doAITick() && this.isInWater()) { +- this.a(0.01F, vec3d); ++ this.a(hasRider() ? getSpeed() : 0.01F, vec3d); // Purpur + this.move(EnumMoveType.SELF, this.getMot()); + this.setMot(this.getMot().a(0.9D)); + if (this.getGoalTarget() == null) { +@@ -161,9 +160,9 @@ public abstract class EntityFish extends EntityWaterAnimal { + @Override + protected void b(BlockPosition blockposition, IBlockData iblockdata) {} + +- static class a extends ControllerMove { ++ static class a extends net.pl3x.purpur.controller.ControllerMoveWASDWater { // Purpur + +- private final EntityFish i; ++ private final EntityFish i; public EntityFish getFish() { return i; } // Purpur - OBFHELPER + + a(EntityFish entityfish) { + super(entityfish); +@@ -171,7 +170,15 @@ public abstract class EntityFish extends EntityWaterAnimal { + } + + @Override +- public void a() { ++ // Purpur start ++ public void tick(EntityHuman rider) { ++ super.tick(rider); ++ getFish().setMot(getFish().getMot().add(0.0D, 0.005D, 0.0D)); ++ } ++ ++ @Override ++ public void tick() { ++ // Purpur end + if (this.i.a((Tag) TagsFluid.WATER)) { + this.i.setMot(this.i.getMot().add(0.0D, 0.005D, 0.0D)); + } +diff --git a/src/main/java/net/minecraft/server/EntityFishSchool.java b/src/main/java/net/minecraft/server/EntityFishSchool.java +index 08372b72262f2ef6e6c95f34e889e04faf731329..6a8e15f01606024aba43d57d270e09db307a9c3f 100644 +--- a/src/main/java/net/minecraft/server/EntityFishSchool.java ++++ b/src/main/java/net/minecraft/server/EntityFishSchool.java +@@ -16,7 +16,7 @@ public abstract class EntityFishSchool extends EntityFish { + @Override + protected void initPathfinder() { + super.initPathfinder(); +- this.goalSelector.a(5, new PathfinderGoalFishSchool(this)); ++ this.goalSelector.a(6, new PathfinderGoalFishSchool(this)); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityFox.java b/src/main/java/net/minecraft/server/EntityFox.java +index 8845afd83d47902d5192ec1a9146b6f20c5667d3..f5defe4713c6be7d32fb2116110516717460284e 100644 +--- a/src/main/java/net/minecraft/server/EntityFox.java ++++ b/src/main/java/net/minecraft/server/EntityFox.java +@@ -55,6 +55,39 @@ public class EntityFox extends EntityAnimal { + this.setCanPickupLoot(true); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.foxRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.foxRidableInWater; ++ } ++ ++ @Override ++ public float getJumpHeight() { ++ return !hasRider() ? super.getJumpHeight() : 0.5F; ++ } ++ ++ @Override ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ setCanPickupLoot(false); ++ stopActions(); ++ setChasing(false); ++ spit(getEquipment(EnumItemSlot.MAINHAND)); ++ setSlot(EnumItemSlot.MAINHAND, ItemStack.NULL_ITEM); ++ } ++ ++ @Override ++ public void onDismount(EntityHuman entityhuman) { ++ super.onDismount(entityhuman); ++ setCanPickupLoot(true); ++ } ++ // Purpur end ++ + @Override + protected void initDatawatcher() { + super.initDatawatcher(); +@@ -74,6 +107,7 @@ public class EntityFox extends EntityAnimal { + return entityliving instanceof EntityFishSchool; + }); + this.goalSelector.a(0, new EntityFox.g()); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityFox.b()); + this.goalSelector.a(2, new EntityFox.n(2.2D)); + this.goalSelector.a(3, new EntityFox.e(1.0D)); +@@ -99,6 +133,7 @@ public class EntityFox extends EntityAnimal { + this.goalSelector.a(11, new EntityFox.p()); + this.goalSelector.a(12, new EntityFox.j(this, EntityHuman.class, 24.0F)); + this.goalSelector.a(13, new EntityFox.r()); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(3, new EntityFox.a(EntityLiving.class, false, false, (entityliving) -> { + return EntityFox.bt.test(entityliving) && !this.c(entityliving.getUniqueID()); + })); +@@ -386,6 +421,7 @@ public class EntityFox extends EntityAnimal { + return itemstack1.isEmpty() || this.bD > 0 && item.isFood() && !itemstack1.getItem().isFood(); + } + ++ public void spit(ItemStack itemstack) { m(itemstack); } // Purpur - OBFHELPER + private void m(ItemStack itemstack) { + if (!itemstack.isEmpty() && !this.world.isClientSide) { + EntityItem entityitem = new EntityItem(this.world, this.locX() + this.getLookDirection().x, this.locY() + 1.0D, this.locZ() + this.getLookDirection().z, itemstack); +@@ -481,6 +517,7 @@ public class EntityFox extends EntityAnimal { + return this.t(16); + } + ++ public void setChasing(boolean flag) { u(flag); } // Purpur - OBFHELPER + public void u(boolean flag) { + this.d(16, flag); + } +@@ -523,6 +560,7 @@ public class EntityFox extends EntityAnimal { + this.setSleeping(false); + } + ++ public void stopActions() { fd(); } // Purpur - OBFHELPER + private void fd() { + this.w(false); + this.setCrouching(false); +@@ -688,16 +726,16 @@ public class EntityFox extends EntityAnimal { + } + } + +- public class k extends ControllerLook { ++ public class k extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur + + public k() { + super(EntityFox.this); + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (!EntityFox.this.isSleeping()) { +- super.a(); ++ super.tick(); // Purpur + } + + } +@@ -1367,16 +1405,16 @@ public class EntityFox extends EntityAnimal { + } + } + +- class m extends ControllerMove { ++ class m extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur + + public m() { + super(EntityFox.this); + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (EntityFox.this.fe()) { +- super.a(); ++ super.tick(); // Purpur + } + + } +diff --git a/src/main/java/net/minecraft/server/EntityGhast.java b/src/main/java/net/minecraft/server/EntityGhast.java +index a67611c4f9271c116a795ee598412f25396fee88..216506a7b1f97b776ecd4e24f5b2afaf5b79ec2d 100644 +--- a/src/main/java/net/minecraft/server/EntityGhast.java ++++ b/src/main/java/net/minecraft/server/EntityGhast.java +@@ -14,11 +14,42 @@ public class EntityGhast extends EntityFlying implements IMonster { + this.moveController = new EntityGhast.ControllerGhast(this); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.ghastRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.ghastRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.ghastMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue(); ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, 1.0, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(5, new EntityGhast.PathfinderGoalGhastIdleMove(this)); + this.goalSelector.a(7, new EntityGhast.PathfinderGoalGhastMoveTowardsTarget(this)); + this.goalSelector.a(7, new EntityGhast.PathfinderGoalGhastAttackTarget(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, (entityliving) -> { + return Math.abs(entityliving.locY() - this.locY()) <= 4.0D; + })); +@@ -56,7 +87,7 @@ public class EntityGhast extends EntityFlying implements IMonster { + } + + public static AttributeProvider.Builder eJ() { +- return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 10.0D).a(GenericAttributes.FOLLOW_RANGE, 100.0D); ++ return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 10.0D).a(GenericAttributes.FOLLOW_RANGE, 100.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur + } + + @Override +@@ -255,7 +286,7 @@ public class EntityGhast extends EntityFlying implements IMonster { + } + } + +- static class ControllerGhast extends ControllerMove { ++ static class ControllerGhast extends net.pl3x.purpur.controller.ControllerMoveWASDFlying { // Purpur + + private final EntityGhast i; + private int j; +@@ -266,7 +297,7 @@ public class EntityGhast extends EntityFlying implements IMonster { + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (this.h == ControllerMove.Operation.MOVE_TO) { + if (this.j-- <= 0) { + this.j += this.i.getRandom().nextInt(5) + 2; +diff --git a/src/main/java/net/minecraft/server/EntityGiantZombie.java b/src/main/java/net/minecraft/server/EntityGiantZombie.java +index 565c938d879940d8e12fe320ea8524d2cf679c1f..5e99767f45f7ba7db80f5b51810689e059b5cef5 100644 +--- a/src/main/java/net/minecraft/server/EntityGiantZombie.java ++++ b/src/main/java/net/minecraft/server/EntityGiantZombie.java +@@ -11,16 +11,28 @@ public class EntityGiantZombie extends EntityMonster { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.giantRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.giantRidableInWater; ++ } ++ + @Override + protected void initPathfinder() { + if (world.purpurConfig.giantHaveAI) { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 16.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); + this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D)); + if (world.purpurConfig.giantHaveHostileAI) { + this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this).a(EntityPigZombie.class)); + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillager.class, false)); +diff --git a/src/main/java/net/minecraft/server/EntityGuardian.java b/src/main/java/net/minecraft/server/EntityGuardian.java +index 27ec46c0b2e7df985ed4c68a973db18f81caac89..0a7f51a2a1cb10438c9364faf3e4adc6322fc787 100644 +--- a/src/main/java/net/minecraft/server/EntityGuardian.java ++++ b/src/main/java/net/minecraft/server/EntityGuardian.java +@@ -24,15 +24,36 @@ public class EntityGuardian extends EntityMonster { + this.f = 10; + this.a(PathType.WATER, 0.0F); + this.moveController = new EntityGuardian.ControllerMoveGuardian(this); ++ // Purpur start ++ this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this) { ++ @Override ++ public void setYawPitch(float yaw, float pitch) { ++ super.setYawPitch(yaw, pitch * 0.35F); ++ } ++ }; ++ // Purpur end + this.bo = this.random.nextFloat(); + this.bp = this.bo; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.guardianRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + PathfinderGoalMoveTowardsRestriction pathfindergoalmovetowardsrestriction = new PathfinderGoalMoveTowardsRestriction(this, 1.0D); + + this.goalRandomStroll = new PathfinderGoalRandomStroll(this, 1.0D, 80); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(4, new EntityGuardian.PathfinderGoalGuardianAttack(this)); + this.goalSelector.a(5, pathfindergoalmovetowardsrestriction); + this.goalSelector.a(7, this.goalRandomStroll); +@@ -41,6 +62,7 @@ public class EntityGuardian extends EntityMonster { + this.goalSelector.a(9, new PathfinderGoalRandomLookaround(this)); + this.goalRandomStroll.a(EnumSet.of(PathfinderGoal.Type.MOVE, PathfinderGoal.Type.LOOK)); + pathfindergoalmovetowardsrestriction.a(EnumSet.of(PathfinderGoal.Type.MOVE, PathfinderGoal.Type.LOOK)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityLiving.class, 10, true, false, new EntityGuardian.EntitySelectorGuardianTargetHumanSquid(this))); + } + +@@ -74,6 +96,7 @@ public class EntityGuardian extends EntityMonster { + return (Boolean) this.datawatcher.get(EntityGuardian.b); + } + ++ private void setMovingFlag(boolean movingFlag) { t(movingFlag); } // Purpur - OBFHELPER + private void t(boolean flag) { + this.datawatcher.set(EntityGuardian.b, flag); + } +@@ -288,7 +311,7 @@ public class EntityGuardian extends EntityMonster { + @Override + public void g(Vec3D vec3d) { + if (this.doAITick() && this.isInWater()) { +- this.a(0.1F, vec3d); ++ this.a(hasRider() ? getSpeed() : 0.1F, vec3d); // Purpur + this.move(EnumMoveType.SELF, this.getMot()); + this.setMot(this.getMot().a(0.9D)); + if (!this.eN() && this.getGoalTarget() == null) { +@@ -300,17 +323,26 @@ public class EntityGuardian extends EntityMonster { + + } + +- static class ControllerMoveGuardian extends ControllerMove { ++ static class ControllerMoveGuardian extends net.pl3x.purpur.controller.ControllerMoveWASDWater { // Purpur + +- private final EntityGuardian i; ++ private final EntityGuardian i; private EntityGuardian getGuardian() { return i; } // Purpur - OBFHELPER + + public ControllerMoveGuardian(EntityGuardian entityguardian) { + super(entityguardian); + this.i = entityguardian; + } + ++ // Purpur start ++ @Override ++ public void tick(EntityHuman rider) { ++ super.tick(rider); ++ getGuardian().setMot(getGuardian().getMot().add(0.0D, 0.005D, 0.0D)); ++ getGuardian().setMovingFlag(getGuardian().getForward() > 0.0F); // control tail speed ++ } ++ // Purpur end ++ + @Override +- public void a() { ++ public void tick() { // Purpur + if (this.h == ControllerMove.Operation.MOVE_TO && !this.i.getNavigation().m()) { + Vec3D vec3d = new Vec3D(this.b - this.i.locX(), this.c - this.i.locY(), this.d - this.i.locZ()); + double d0 = vec3d.f(); +diff --git a/src/main/java/net/minecraft/server/EntityGuardianElder.java b/src/main/java/net/minecraft/server/EntityGuardianElder.java +index b691e844953bcc2853a806a3bbf9cb7338e98266..f6f882746940c9e049106aa9b41591ba27a608ce 100644 +--- a/src/main/java/net/minecraft/server/EntityGuardianElder.java ++++ b/src/main/java/net/minecraft/server/EntityGuardianElder.java +@@ -16,6 +16,18 @@ public class EntityGuardianElder extends EntityGuardian { + + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.elderGuardianRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder m() { + return EntityGuardian.eM().a(GenericAttributes.MOVEMENT_SPEED, 0.30000001192092896D).a(GenericAttributes.ATTACK_DAMAGE, 8.0D).a(GenericAttributes.MAX_HEALTH, 80.0D); + } +diff --git a/src/main/java/net/minecraft/server/EntityHoglin.java b/src/main/java/net/minecraft/server/EntityHoglin.java +index f6797925365836b6c2d3d2c48c746a4d58e28bf3..548ff4449faca0abdf72487276fe49207bacfe17 100644 +--- a/src/main/java/net/minecraft/server/EntityHoglin.java ++++ b/src/main/java/net/minecraft/server/EntityHoglin.java +@@ -13,13 +13,25 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin { + public int conversionTicks = 0; + public boolean cannotBeHunted = false; + protected static final ImmutableList>> bo = ImmutableList.of(SensorType.c, SensorType.d, SensorType.n, SensorType.m); +- protected static final ImmutableList> bp = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, new MemoryModuleType[]{MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULY, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED}); ++ protected static final ImmutableList> bp = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.MOBS, MemoryModuleType.VISIBLE_MOBS, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULY, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED); // Purpur - decompile error + + public EntityHoglin(EntityTypes entitytypes, World world) { + super(entitytypes, world); + this.f = 5; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.hoglinRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.hoglinRidableInWater; ++ } ++ // Purpur end ++ + @Override + public boolean a(EntityHuman entityhuman) { + return !this.isLeashed(); +@@ -77,13 +89,14 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin { + + @Override + public BehaviorController getBehaviorController() { +- return super.getBehaviorController(); ++ return (BehaviorController) super.getBehaviorController(); // Purpur - decompile error + } + + @Override + protected void mobTick() { + this.world.getMethodProfiler().enter("hoglinBrain"); +- this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this); ++ if (getRider() == null) // Purpur - only use brain if no rider ++ this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error + this.world.getMethodProfiler().exit(); + HoglinAI.a(this); + if (this.isConverting()) { +@@ -259,7 +272,7 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin { + + @Override + protected SoundEffect getSoundAmbient() { +- return this.world.isClientSide ? null : (SoundEffect) HoglinAI.b(this).orElse((Object) null); ++ return this.world.isClientSide ? null : (SoundEffect) HoglinAI.b(this).orElse(null); // Purpur - decompile error + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorse.java b/src/main/java/net/minecraft/server/EntityHorse.java +index f958751b16abcc36910bb0b655ff2360459e2e4c..4ffc61acdff8c51dc9b111e3024c828fb5386118 100644 +--- a/src/main/java/net/minecraft/server/EntityHorse.java ++++ b/src/main/java/net/minecraft/server/EntityHorse.java +@@ -12,6 +12,13 @@ public class EntityHorse extends EntityHorseAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.horseRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void eK() { + this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue((double) this.fp()); +diff --git a/src/main/java/net/minecraft/server/EntityHorseAbstract.java b/src/main/java/net/minecraft/server/EntityHorseAbstract.java +index 7604fd83de9cfe93d427a9a1f6bbbee76aa861e8..7958b69b2d4bee87ff5b38b8f724d248f41dff66 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseAbstract.java ++++ b/src/main/java/net/minecraft/server/EntityHorseAbstract.java +@@ -39,12 +39,27 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + + protected EntityHorseAbstract(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ this.moveController = new ControllerMove(this); // Purpur - use vanilla controller ++ this.lookController = new ControllerLook(this); // Purpur - use vanilla controller + this.G = 1.0F; + this.loadChest(); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return false; // vanilla handles ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return false; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.2D)); + this.goalSelector.a(1, new PathfinderGoalTame(this, 1.2D)); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D, EntityHorseAbstract.class)); +@@ -52,6 +67,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + this.goalSelector.a(6, new PathfinderGoalRandomStrollLand(this, 0.7D)); + this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur + this.eV(); + } + +diff --git a/src/main/java/net/minecraft/server/EntityHorseDonkey.java b/src/main/java/net/minecraft/server/EntityHorseDonkey.java +index 767d18d5a0417813c514d0341b318d6321f6f7a5..cb8aee5691ff4ecaa6ae60f1637b1852d3b6c162 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseDonkey.java ++++ b/src/main/java/net/minecraft/server/EntityHorseDonkey.java +@@ -8,6 +8,13 @@ public class EntityHorseDonkey extends EntityHorseChestedAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.donkeyRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected SoundEffect getSoundAmbient() { + super.getSoundAmbient(); +diff --git a/src/main/java/net/minecraft/server/EntityHorseMule.java b/src/main/java/net/minecraft/server/EntityHorseMule.java +index 3dd4d4f0f35e2b12686e1a257250934e5678e397..243aeb736e350418e9476819bbfec0e7ab59f92f 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseMule.java ++++ b/src/main/java/net/minecraft/server/EntityHorseMule.java +@@ -8,6 +8,12 @@ public class EntityHorseMule extends EntityHorseChestedAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.muleRidableInWater; ++ } ++ // Purpur end + @Override + protected SoundEffect getSoundAmbient() { + super.getSoundAmbient(); +diff --git a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java +index a53d335f3af9df80bec3f94f81fb5ff0e0e5ebb5..e2c6a5807a4554a7eebb148e40f1f8a1d979df5e 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java ++++ b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java +@@ -12,6 +12,18 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.skeletonHorseRidableInWater; ++ } ++ ++ @Override ++ public boolean isTamed() { ++ return true; ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder eL() { + return fi().a(GenericAttributes.MAX_HEALTH, 15.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D); + } +@@ -22,7 +34,7 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + } + + @Override +- protected void eV() {} ++ protected void eV() { if (world.purpurConfig.skeletonHorseCanSwim) goalSelector.a(0, new PathfinderGoalFloat(this)); } // Purpur + + @Override + protected SoundEffect getSoundAmbient() { +@@ -117,7 +129,7 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + + @Override + public boolean bt() { +- return true; ++ return super.bt(); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorseZombie.java b/src/main/java/net/minecraft/server/EntityHorseZombie.java +index 0e98173607c810e0e74552a2ba8febf292357c39..559ba50977147b8e2a0e7c1e7dc281faabd7f292 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseZombie.java ++++ b/src/main/java/net/minecraft/server/EntityHorseZombie.java +@@ -8,6 +8,18 @@ public class EntityHorseZombie extends EntityHorseAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.zombieHorseRidableInWater; ++ } ++ ++ @Override ++ public boolean isTamed() { ++ return true; ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder eL() { + return fi().a(GenericAttributes.MAX_HEALTH, 15.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D); + } +@@ -79,5 +91,5 @@ public class EntityHorseZombie extends EntityHorseAbstract { + } + + @Override +- protected void eV() {} ++ protected void eV() { if (world.purpurConfig.zombieHorseCanSwim) goalSelector.a(0, new PathfinderGoalFloat(this)); } // Purpur + } +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index 0ef020972494181a87c86f95aec7a4302e243f16..aef1dc47f2025f09d650a04f6dfa867d5ea1b65a 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -2155,4 +2155,15 @@ public abstract class EntityHuman extends EntityLiving { + return this.g; + } + } ++ ++ // Purpur start ++ @Override ++ public boolean processClick(EnumHand hand) { ++ Entity vehicle = getRootVehicle(); ++ if (vehicle != null && vehicle.getRider() == this) { ++ return vehicle.onClick(hand); ++ } ++ return false; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java b/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java +index c57bf5091430709778dc21d70c8a32819c9d6639..b0a5c36d1132e2558a1fefbd9f8dd26448400086 100644 +--- a/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java ++++ b/src/main/java/net/minecraft/server/EntityIllagerIllusioner.java +@@ -20,6 +20,16 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.illusionerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.illusionerRidableInWater; ++ } ++ + @Override + protected void initAttributes() { + this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.illusionerMovementSpeed); +@@ -32,6 +42,7 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityIllagerWizard.b()); + this.goalSelector.a(4, new EntityIllagerIllusioner.b()); + this.goalSelector.a(5, new EntityIllagerIllusioner.a()); +@@ -39,6 +50,7 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan + this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); + this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)).a(300)); + this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)).a(300)); +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index bacb631e0fc59bbcca9ae1e116ba5b9ed1eccfca..8c36027dffca818ec5b23040640c344dff9037dc 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -29,7 +29,7 @@ public abstract class EntityInsentient extends EntityLiving { + protected int f; + protected ControllerLook lookController; + protected ControllerMove moveController; +- protected ControllerJump bi; ++ protected ControllerJump bi; public ControllerJump getJumpController() { return bi; } // Purpur - OBFHELPER + private final EntityAIBodyControl c; + protected NavigationAbstract navigation; + public PathfinderGoalSelector goalSelector; +@@ -67,8 +67,8 @@ public abstract class EntityInsentient extends EntityLiving { + this.bA = -1.0F; + this.goalSelector = new PathfinderGoalSelector(world.getMethodProfilerSupplier()); + this.targetSelector = new PathfinderGoalSelector(world.getMethodProfilerSupplier()); +- this.lookController = new ControllerLook(this); +- this.moveController = new ControllerMove(this); ++ this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this); // Purpur ++ this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASD(this); // Purpur + this.bi = new ControllerJump(this); + this.c = this.r(); + this.navigation = this.b(world); +@@ -257,10 +257,10 @@ public abstract class EntityInsentient extends EntityLiving { + // Purpur start + private void incrementTicksSinceLastInteraction() { + ++ticksSinceLastInteraction; +- //if (hasRider()) { +- // ticksSinceLastInteraction = 0; +- // return; +- //} ++ if (hasRider()) { ++ ticksSinceLastInteraction = 0; ++ return; ++ } + if (world.purpurConfig.entityLifeSpan <= 0) { + return; // feature disabled + } +@@ -555,14 +555,17 @@ public abstract class EntityInsentient extends EntityLiving { + return super.dp(); + } + ++ public void setForwardSpeed(float speed) { this.t(speed); } // Purpur - OBFHELPER + public void t(float f) { + this.aT = f; + } + ++ public void setVerticalSpeed(float speed) { this.u(speed); } // Purpur - OBFHELPER + public void u(float f) { + this.aS = f; + } + ++ public void setStrafeSpeed(float speed) { this.v(speed); } // Purpur - OBFHELPER + public void v(float f) { + this.aR = f; + } +@@ -1249,7 +1252,7 @@ public abstract class EntityInsentient extends EntityLiving { + protected void a(EntityHuman entityhuman, EntityInsentient entityinsentient) {} + + protected EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + + public boolean ev() { +@@ -1621,4 +1624,54 @@ public abstract class EntityInsentient extends EntityLiving { + this.world.getServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit + this.unleash(true, false); + } ++ ++ // Purpur start ++ public double getMaxY() { ++ return world.getHeight(); ++ } ++ ++ public EnumInteractionResult tryRide(EntityHuman entityhuman, EnumHand enumhand) { ++ if (!isRidable()) { ++ return EnumInteractionResult.PASS; ++ } ++ if (enumhand != EnumHand.MAIN_HAND) { ++ return EnumInteractionResult.PASS; ++ } ++ if (entityhuman.isSneaking()) { ++ return EnumInteractionResult.PASS; ++ } ++ if (!entityhuman.getItemInHand(enumhand).isEmpty()) { ++ return EnumInteractionResult.PASS; ++ } ++ if (!passengers.isEmpty() || entityhuman.isPassenger()) { ++ return EnumInteractionResult.PASS; ++ } ++ if (this instanceof EntityTameableAnimal) { ++ EntityTameableAnimal tameable = (EntityTameableAnimal) this; ++ if (tameable.isTamed() && !tameable.isOwner(entityhuman)) { ++ return EnumInteractionResult.PASS; ++ } ++ if (!tameable.isTamed() && !world.purpurConfig.untamedTamablesAreRidable) { ++ return EnumInteractionResult.PASS; ++ } ++ } ++ if (this instanceof EntityAgeable) { ++ EntityAgeable ageable = (EntityAgeable) this; ++ if (ageable.isBaby() && !world.purpurConfig.babiesAreRidable) { ++ return EnumInteractionResult.PASS; ++ } ++ } ++ if (!entityhuman.getBukkitEntity().hasPermission("allow.ride." + getEntityType().getName())) { ++ entityhuman.sendMessage(net.pl3x.purpur.PurpurConfig.cannotRideMob); ++ return EnumInteractionResult.PASS; ++ } ++ entityhuman.yaw = this.yaw; ++ entityhuman.pitch = this.pitch; ++ if (entityhuman.startRiding(this)) { ++ return EnumInteractionResult.SUCCESS; ++ } else { ++ return EnumInteractionResult.PASS; ++ } ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityIronGolem.java b/src/main/java/net/minecraft/server/EntityIronGolem.java +index bdff2368836dca230a6622a205d5772834afc6ee..9ee03b233b71d1b4b85a9a5e1f0ea9feb55dfe43 100644 +--- a/src/main/java/net/minecraft/server/EntityIronGolem.java ++++ b/src/main/java/net/minecraft/server/EntityIronGolem.java +@@ -22,9 +22,22 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable { + this.G = 1.0F; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.ironGolemRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.ironGolemRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + if (world.purpurConfig.ironGolemCanSwim) this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, true)); + this.goalSelector.a(2, new PathfinderGoalMoveTowardsTarget(this, 0.9D, 32.0F)); + this.goalSelector.a(2, new PathfinderGoalStrollVillage(this, 0.6D, false)); +@@ -32,6 +45,7 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable { + this.goalSelector.a(5, new PathfinderGoalOfferFlower(this)); + this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalDefendVillage(this)); + this.targetSelector.a(2, new PathfinderGoalHurtByTarget(this, new Class[0])); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::a_)); +@@ -201,13 +215,13 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable { + Item item = itemstack.getItem(); + + if (item != Items.IRON_INGOT) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } else { + float f = this.getHealth(); + + this.heal(25.0F); + if (this.getHealth() == f) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } else { + float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index c22fa35197ae23526a29bcbf69f3022ffc0701e1..618739799bc5dc2550198465b1e46e494f473ad9 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -462,7 +462,7 @@ public abstract class EntityLiving extends Entity { + + @Override + public boolean bt() { +- return false; ++ return isRidableInWater(); // Purpur + } + + protected void cU() { +@@ -2194,7 +2194,7 @@ public abstract class EntityLiving extends Entity { + return 0.42F * this.getBlockJumpFactor(); + } + +- protected void jump() { ++ public void jump() { // Purpur - protected -> public + float f = this.dJ(); + + if (this.hasEffect(MobEffects.JUMP)) { +@@ -2443,10 +2443,12 @@ public abstract class EntityLiving extends Entity { + return this.onGround ? this.dN() * (0.21600002F / (f * f * f)) : this.aE; + } + ++ public float getSpeed() { return dN(); } // Purpur - OBFHELPER + public float dN() { + return this.bu; + } + ++ public void setSpeed(float speed) { q(speed); } // Purpur - OBFHELPER + public void q(float f) { + this.bu = f; + } +@@ -2846,6 +2848,20 @@ public abstract class EntityLiving extends Entity { + } + } + // Purpur end ++ // Purpur start ++ if (hasRider() && ((WorldServer) world).hasRidableMoveEvent && this instanceof EntityInsentient) { ++ if (lastX != locX() || lastY != locY() || lastZ != locZ() || lastYaw != yaw || lastPitch != pitch) { ++ Location from = new Location(world.getWorld(), lastX, lastY, lastZ, lastYaw, lastPitch); ++ Location to = new Location (world.getWorld(), locX(), locY(), locZ(), yaw, pitch); ++ net.pl3x.purpur.event.entity.RidableMoveEvent event = new net.pl3x.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ setLocation(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ setLocation(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); ++ } ++ } ++ } ++ // Purpur end + if (!this.world.isClientSide && this.dO() && this.aG()) { + this.damageEntity(DamageSource.DROWN, 1.0F); + } +diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java +index e61f53816cbf09e775762403d97e9c591fb405a6..1099277868f92fdaf4b0ec3a982f26f20ead7369 100644 +--- a/src/main/java/net/minecraft/server/EntityLlama.java ++++ b/src/main/java/net/minecraft/server/EntityLlama.java +@@ -18,7 +18,46 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + + public EntityLlama(EntityTypes entitytypes, World world) { + super(entitytypes, world); ++ // Purpur start ++ this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASD(this) { ++ @Override ++ public void a() { // tick ++ if (entity.hasRider() && hasSaddle()) { ++ tick(entity.getRider()); ++ } else { ++ tick(); ++ } ++ } ++ }; ++ this.lookController = new net.pl3x.purpur.controller.ControllerLookWASD(this) { ++ @Override ++ public void a() { // tick ++ if (entity.hasRider() && hasSaddle()) { ++ tick(entity.getRider()); ++ } else { ++ tick(); ++ } ++ } ++ }; ++ // Purpur end ++ } ++ ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.llamaRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.llamaRidableInWater; ++ } ++ ++ @Override ++ public boolean hasSaddle() { ++ return super.hasSaddle() || (isTamed() && getColor() != null); + } ++ // Purpur end + + public void setStrength(int i) { + this.datawatcher.set(EntityLlama.bx, Math.max(1, Math.min(5, i))); +@@ -66,6 +105,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalTame(this, 1.2D)); + this.goalSelector.a(2, new PathfinderGoalLlamaFollow(this, 2.0999999046325684D)); + this.goalSelector.a(3, new PathfinderGoalArrowAttack(this, 1.25D, 40, 20.0F)); +@@ -75,6 +115,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + this.goalSelector.a(6, new PathfinderGoalRandomStrollLand(this, 0.7D)); + this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHorseHasRider(this)); // Purpur + this.targetSelector.a(1, new EntityLlama.c(this)); + this.targetSelector.a(2, new EntityLlama.a(this)); + } +@@ -312,7 +353,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + } + + @Nullable +- public EnumColor fy() { ++ public EnumColor fy() { return getColor(); } public EnumColor getColor() { // Purpur - OBFHELPER + int i = (Integer) this.datawatcher.get(EntityLlama.by); + + return i == -1 ? null : EnumColor.fromColorIndex(i); +diff --git a/src/main/java/net/minecraft/server/EntityLlamaTrader.java b/src/main/java/net/minecraft/server/EntityLlamaTrader.java +index 7d30e5c2378a2b8c540d1a1a13ec0ae97367bb9a..b6aae5cdee1f8bb842ab8e06c47fb497576b464f 100644 +--- a/src/main/java/net/minecraft/server/EntityLlamaTrader.java ++++ b/src/main/java/net/minecraft/server/EntityLlamaTrader.java +@@ -11,6 +11,23 @@ public class EntityLlamaTrader extends EntityLlama { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.llamaTraderRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.llamaTraderRidableInWater; ++ } ++ ++ @Override ++ public boolean hasSaddle() { ++ return super.hasSaddle() || isTamed(); ++ } ++ // Purpur end ++ + @Override + protected EntityLlama fz() { + return (EntityLlama) EntityTypes.TRADER_LLAMA.a(this.world); +diff --git a/src/main/java/net/minecraft/server/EntityMagmaCube.java b/src/main/java/net/minecraft/server/EntityMagmaCube.java +index 874dd39825b41f4e2b366446359920989961f084..cd28463e2bf944d94c121c8f8d6e37221754c168 100644 +--- a/src/main/java/net/minecraft/server/EntityMagmaCube.java ++++ b/src/main/java/net/minecraft/server/EntityMagmaCube.java +@@ -8,6 +8,23 @@ public class EntityMagmaCube extends EntitySlime { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.magmaCubeRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.magmaCubeRidableInWater; ++ } ++ ++ @Override ++ public float getJumpHeight() { ++ return 0.42F * this.getBlockJumpFactor(); // from EntityLiving ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder m() { + return EntityMonster.eR().a(GenericAttributes.MOVEMENT_SPEED, 0.20000000298023224D); + } +@@ -58,11 +75,12 @@ public class EntityMagmaCube extends EntitySlime { + } + + @Override +- protected void jump() { ++ public void jump() { // Purpur - protected -> public + Vec3D vec3d = this.getMot(); + + this.setMot(vec3d.x, (double) (this.dJ() + (float) this.getSize() * 0.1F), vec3d.z); + this.impulse = true; ++ this.actualJump = false; // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityMushroomCow.java b/src/main/java/net/minecraft/server/EntityMushroomCow.java +index 38df17bd206c908582ece2c4105235feaf0f2227..7966b34f8d202d2260a35baa4cd594e4def89257 100644 +--- a/src/main/java/net/minecraft/server/EntityMushroomCow.java ++++ b/src/main/java/net/minecraft/server/EntityMushroomCow.java +@@ -20,6 +20,18 @@ public class EntityMushroomCow extends EntityCow implements IShearable { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.mooshroomRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.mooshroomRidableInWater; ++ } ++ // Purpur end ++ + @Override + public float a(BlockPosition blockposition, IWorldReader iworldreader) { + return iworldreader.getType(blockposition.down()).a(Blocks.MYCELIUM) ? 10.0F : iworldreader.y(blockposition) - 0.5F; +@@ -81,7 +93,7 @@ public class EntityMushroomCow extends EntityCow implements IShearable { + } else if (itemstack.getItem() == Items.SHEARS && this.canShear()) { + // CraftBukkit start + if (!CraftEventFactory.handlePlayerShearEntityEvent(entityhuman, this, itemstack, enumhand)) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + // CraftBukkit end + this.shear(SoundCategory.PLAYERS); +@@ -101,7 +113,7 @@ public class EntityMushroomCow extends EntityCow implements IShearable { + Optional> optional = this.l(itemstack); + + if (!optional.isPresent()) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + + Pair pair = (Pair) optional.get(); +diff --git a/src/main/java/net/minecraft/server/EntityOcelot.java b/src/main/java/net/minecraft/server/EntityOcelot.java +index 7bd3fea6e0e478337c7f6400a941675eebba517b..2f8275cd6b3cde0d3f949219f67ba7f0e0031dc3 100644 +--- a/src/main/java/net/minecraft/server/EntityOcelot.java ++++ b/src/main/java/net/minecraft/server/EntityOcelot.java +@@ -16,6 +16,18 @@ public class EntityOcelot extends EntityAnimal { + this.eL(); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.ocelotRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.ocelotRidableInWater; ++ } ++ // Purpur end ++ + private boolean isTrusting() { + return (Boolean) this.datawatcher.get(EntityOcelot.bp); + } +@@ -47,12 +59,14 @@ public class EntityOcelot extends EntityAnimal { + protected void initPathfinder() { + this.br = new EntityOcelot.b(this, 0.6D, EntityOcelot.bo, true); + this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(3, this.br); + this.goalSelector.a(7, new PathfinderGoalLeapAtTarget(this, 0.3F)); + this.goalSelector.a(8, new PathfinderGoalOcelotAttack(this)); + this.goalSelector.a(9, new PathfinderGoalBreed(this, 0.8D)); + this.goalSelector.a(10, new PathfinderGoalRandomStrollLand(this, 0.8D, 1.0000001E-5F)); + this.goalSelector.a(11, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 10.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityChicken.class, false)); + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, false, false, EntityTurtle.bo)); + } +diff --git a/src/main/java/net/minecraft/server/EntityPanda.java b/src/main/java/net/minecraft/server/EntityPanda.java +index b36cc95168c6e6fbf668ebe15c1fa10b54e570d3..eafae5516b9b5d51aa943796557926cf61476d2b 100644 +--- a/src/main/java/net/minecraft/server/EntityPanda.java ++++ b/src/main/java/net/minecraft/server/EntityPanda.java +@@ -46,6 +46,27 @@ public class EntityPanda extends EntityAnimal { + + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.pandaRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.pandaRidableInWater; ++ } ++ ++ @Override ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ this.setForwardSpeed(0.0F); ++ this.setScared(false); ++ this.setEating(false); ++ this.setLayingOnBack(false); ++ } ++ // Purpur end ++ + @Override + public boolean e(ItemStack itemstack) { + EnumItemSlot enumitemslot = EntityInsentient.j(itemstack); +@@ -69,6 +90,7 @@ public class EntityPanda extends EntityAnimal { + return this.w(8); + } + ++ public void setScared(boolean scared) { this.t(scared); } // Purpur - OBFHELPER + public void t(boolean flag) { + this.d(8, flag); + } +@@ -77,6 +99,7 @@ public class EntityPanda extends EntityAnimal { + return this.w(16); + } + ++ public void setLayingOnBack(boolean layingOnBack) { this.u(layingOnBack); } // Purpur - OBFHELPER + public void u(boolean flag) { + this.d(16, flag); + } +@@ -93,6 +116,7 @@ public class EntityPanda extends EntityAnimal { + return (Integer) this.datawatcher.get(EntityPanda.br); + } + ++ public void setEating(boolean eating) { this.v(eating); } // Purpur - OBFHELPER + private void v(int i) { + this.datawatcher.set(EntityPanda.br, i); + } +@@ -201,6 +225,7 @@ public class EntityPanda extends EntityAnimal { + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new EntityPanda.i(this, 2.0D)); + this.goalSelector.a(2, new EntityPanda.d(this, 1.0D)); + this.goalSelector.a(3, new EntityPanda.b(this, 1.2000000476837158D, true)); +@@ -216,6 +241,7 @@ public class EntityPanda extends EntityAnimal { + this.goalSelector.a(12, new EntityPanda.j(this)); + this.goalSelector.a(13, new PathfinderGoalFollowParent(this, 1.25D)); + this.goalSelector.a(14, new PathfinderGoalRandomStrollLand(this, 1.0D)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new EntityPanda.e(this, new Class[0])).a(new Class[0])); + } + +@@ -536,7 +562,7 @@ public class EntityPanda extends EntityAnimal { + ItemStack itemstack = entityhuman.b(enumhand); + + if (this.ff()) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } else if (this.eN()) { + this.u(false); + return EnumInteractionResult.a(this.world.isClientSide); +@@ -553,7 +579,7 @@ public class EntityPanda extends EntityAnimal { + this.g(entityhuman); + } else { + if (this.world.isClientSide || this.eM() || this.isInWater()) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + + this.ft(); +@@ -570,7 +596,7 @@ public class EntityPanda extends EntityAnimal { + + return EnumInteractionResult.SUCCESS; + } else { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + } + +@@ -683,6 +709,7 @@ public class EntityPanda extends EntityAnimal { + + public f(EntityPanda entitypanda) { + this.a = entitypanda; ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - lay on back + } + + @Override +@@ -846,6 +873,7 @@ public class EntityPanda extends EntityAnimal { + + public l(EntityPanda entitypanda) { + this.a = entitypanda; ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur - sneeze + } + + @Override +@@ -975,7 +1003,7 @@ public class EntityPanda extends EntityAnimal { + } + } + +- static class h extends ControllerMove { ++ static class h extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur + + private final EntityPanda i; + +@@ -985,9 +1013,9 @@ public class EntityPanda extends EntityAnimal { + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (this.i.fh()) { +- super.a(); ++ super.tick(); // Purpur + } + } + } +diff --git a/src/main/java/net/minecraft/server/EntityParrot.java b/src/main/java/net/minecraft/server/EntityParrot.java +index 0af6c9395b5d98e6bfa162f651d0e8cb89035afd..e402d4a77b57b8b12b7575a9793c30d7acfa7fb0 100644 +--- a/src/main/java/net/minecraft/server/EntityParrot.java ++++ b/src/main/java/net/minecraft/server/EntityParrot.java +@@ -65,12 +65,58 @@ public class EntityParrot extends EntityPerchable implements EntityBird { + + public EntityParrot(EntityTypes entitytypes, World world) { + super(entitytypes, world); +- this.moveController = new ControllerMoveFlying(this, 10, false); ++ // Purpur start ++ final net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar flyingController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.3F); ++ this.moveController = new ControllerMoveFlying(this, 10, false) { ++ @Override ++ public void a() { // tick ++ if (getEntity().hasRider()) { ++ flyingController.tick(getEntity().getRider()); ++ } else { ++ tick(); ++ } ++ } ++ ++ @Override ++ public boolean b() { // isUpdating ++ return getEntity().hasRider() ? getForward() != 0 || getStrafe() != 0 : super.b(); ++ } ++ }; ++ // Purpur end + this.a(PathType.DANGER_FIRE, -1.0F); + this.a(PathType.DAMAGE_FIRE, -1.0F); + this.a(PathType.COCOA, -1.0F); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.parrotRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.parrotRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.parrotMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 2; ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, 0.25, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ // Purpur end ++ + @Nullable + @Override + public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { +@@ -89,8 +135,10 @@ public class EntityParrot extends EntityPerchable implements EntityBird { + + @Override + protected void initPathfinder() { +- this.goalSelector.a(0, new PathfinderGoalPanic(this, 1.25D)); ++ // this.goalSelector.a(0, new PathfinderGoalPanic(this, 1.25D)); // Purpur - move down + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur ++ this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D)); // Purpur + this.goalSelector.a(1, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(2, new PathfinderGoalSit(this)); + this.goalSelector.a(2, new PathfinderGoalFollowOwner(this, 1.0D, 5.0F, 1.0F, true)); +diff --git a/src/main/java/net/minecraft/server/EntityPhantom.java b/src/main/java/net/minecraft/server/EntityPhantom.java +index bdfe073dcd255a7359127f9ae3a962642be5526d..f8c3480045e86a18501db223c1b2254cf3298a42 100644 +--- a/src/main/java/net/minecraft/server/EntityPhantom.java ++++ b/src/main/java/net/minecraft/server/EntityPhantom.java +@@ -25,6 +25,58 @@ public class EntityPhantom extends EntityFlying implements IMonster { + this.lookController = new EntityPhantom.f(this); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.phantomRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.phantomRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.phantomMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue(); ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, speed, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ ++ public static AttributeProvider.Builder defaultAttributes() { ++ return EntityMonster.eR().a(GenericAttributes.FLYING_SPEED, 3.0D); ++ } ++ ++ @Override ++ public boolean onSpacebar() { ++ if (hasRider() && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) { ++ shoot(); ++ } ++ return false; ++ } ++ ++ public boolean shoot() { ++ org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation(); ++ loc.setPitch(-loc.getPitch()); ++ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector()); ++ ++ net.pl3x.purpur.entity.PhantomFlames flames = new net.pl3x.purpur.entity.PhantomFlames(world, this); ++ flames.shoot(target.getX() - locX(), target.getY() - locY(), target.getZ() - locZ(), 1.0F, 5.0F); ++ world.addEntity(flames); ++ return true; ++ } ++ // Purpur end ++ + @Override + protected EntityAIBodyControl r() { + return new EntityPhantom.d(this); +@@ -33,6 +85,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + @Override + protected void initPathfinder() { + // Purpur start ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); + if (world.purpurConfig.phantomOrbitCrystalRadius > 0) { + this.goalSelector.a(1, new FindCrystalGoal(this)); + this.goalSelector.a(2, new OrbitCrystalGoal(this)); +@@ -40,6 +93,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + this.goalSelector.a(3, new EntityPhantom.c()); // PickAttackGoal + this.goalSelector.a(4, new EntityPhantom.i()); // SweepAttackGoal + this.goalSelector.a(5, new EntityPhantom.e()); // OrbitPointGoal ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); + this.targetSelector.a(1, new EntityPhantom.b()); // AttackPlayer Goal + // Purpur end + } +@@ -126,7 +180,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public void movementTick() { +- if (this.isAlive() && ((world.purpurConfig.phantomBurnInDaylight && this.isInDaylight()) || (world.purpurConfig.phantomBurnInLight > 0 && world.getLightLevel(new BlockPosition(this)) >= world.purpurConfig.phantomBurnInLight))) { // Purpur ++ if (this.isAlive() && !hasRider() && ((world.purpurConfig.phantomBurnInDaylight && this.isInDaylight()) || (world.purpurConfig.phantomBurnInLight > 0 && world.getLightLevel(new BlockPosition(this)) >= world.purpurConfig.phantomBurnInLight))) { // Purpur + this.setOnFire(8); + } + +@@ -368,7 +422,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public boolean a() { +- if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag ++ if (getRider() != null || isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag + if (this.c > 0) { + --this.c; + return false; +@@ -397,7 +451,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public boolean b() { +- if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag ++ if (getRider() != null || isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag + EntityLiving entityliving = EntityPhantom.this.getGoalTarget(); + + return entityliving != null ? EntityPhantom.this.a(entityliving, PathfinderTargetCondition.a) : false; +@@ -412,7 +466,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + + @Override + public boolean a() { +- if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag ++ if (getRider() != null || isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag + EntityLiving entityliving = EntityPhantom.this.getGoalTarget(); + + return entityliving != null ? EntityPhantom.this.a(EntityPhantom.this.getGoalTarget(), PathfinderTargetCondition.a) : false; +@@ -610,14 +664,23 @@ public class EntityPhantom extends EntityFlying implements IMonster { + } + } + +- class f extends ControllerLook { ++ class f extends net.pl3x.purpur.controller.ControllerLookWASD { // Purpur + + public f(EntityInsentient entityinsentient) { + super(entityinsentient); + } + + @Override +- public void a() {} ++ // Purpur start ++ public void tick(EntityHuman rider) { ++ setYawPitch(rider.yaw, -rider.pitch * 0.75F); ++ } ++ ++ @Override ++ public void tick() { ++ // do nothing ++ } ++ // Purpur end + } + + class d extends EntityAIBodyControl { +@@ -633,7 +696,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + } + } + +- class g extends ControllerMove { ++ class g extends net.pl3x.purpur.controller.ControllerMoveWASDFlying { // Purpur + + private float j = 0.1F; + +@@ -642,7 +705,19 @@ public class EntityPhantom extends EntityFlying implements IMonster { + } + + @Override +- public void a() { ++ // Purpur start ++ public void tick(EntityHuman rider) { ++ if (!EntityPhantom.this.onGround) { ++ // phantom is always in motion when flying ++ // TODO - FIX THIS ++ // rider.setForward(1.0F); ++ } ++ super.tick(rider); ++ } ++ ++ @Override ++ public void tick() { ++ // Purpur end + if (EntityPhantom.this.positionChanged) { + EntityPhantom.this.yaw += 180.0F; + this.j = 0.1F; +diff --git a/src/main/java/net/minecraft/server/EntityPig.java b/src/main/java/net/minecraft/server/EntityPig.java +index 7f52c39234e69b612b89993ce4503c20690064ed..dade0bb29422ebd68fae0edb74cbbf6d3ab89d64 100644 +--- a/src/main/java/net/minecraft/server/EntityPig.java ++++ b/src/main/java/net/minecraft/server/EntityPig.java +@@ -19,9 +19,22 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable { + this.saddleStorage = new SaddleStorage(this.datawatcher, EntityPig.bp, EntityPig.bo); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.pigRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.pigRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D)); + this.goalSelector.a(3, new PathfinderGoalBreed(this, 1.0D)); + this.goalSelector.a(4, new PathfinderGoalTempt(this, 1.2D, RecipeItemStack.a(Items.CARROT_ON_A_STICK), false)); +diff --git a/src/main/java/net/minecraft/server/EntityPigZombie.java b/src/main/java/net/minecraft/server/EntityPigZombie.java +index 32b75f710b12efbcecec2c8d72d4d8cb725870fe..3327dbbf87d8f43cbc7cd728df2f4c6a33dae40d 100644 +--- a/src/main/java/net/minecraft/server/EntityPigZombie.java ++++ b/src/main/java/net/minecraft/server/EntityPigZombie.java +@@ -22,6 +22,16 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.zombifiedPiglinRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.zombifiedPiglinRidableInWater; ++ } ++ + @Override + public boolean jockeyOnlyBaby() { + return world.purpurConfig.zombifiedPiglinJockeyOnlyBaby; +diff --git a/src/main/java/net/minecraft/server/EntityPiglin.java b/src/main/java/net/minecraft/server/EntityPiglin.java +index ca7f9dc54ed2e58f521613b5d8027494bd20edd2..334e0f73e67ef2db7e680874faf0646995d9de8a 100644 +--- a/src/main/java/net/minecraft/server/EntityPiglin.java ++++ b/src/main/java/net/minecraft/server/EntityPiglin.java +@@ -25,6 +25,18 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow { + this.f = 5; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.piglinRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.piglinRidableInWater; ++ } ++ // Purpur end ++ + @Override + public void saveData(NBTTagCompound nbttagcompound) { + super.saveData(nbttagcompound); +@@ -143,7 +155,7 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow { + + @Override + public BehaviorController getBehaviorController() { +- return super.getBehaviorController(); ++ return (BehaviorController) super.getBehaviorController(); // Purpur - decompile error + } + + @Override +@@ -202,7 +214,8 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow { + @Override + protected void mobTick() { + this.world.getMethodProfiler().enter("piglinBrain"); +- this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this); ++ if (getRider() == null) // Purpur - only use brain if no rider ++ this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error + this.world.getMethodProfiler().exit(); + PiglinAI.b(this); + super.mobTick(); +@@ -341,7 +354,7 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow { + + @Override + protected SoundEffect getSoundAmbient() { +- return this.world.isClientSide ? null : (SoundEffect) PiglinAI.d(this).orElse((Object) null); ++ return this.world.isClientSide ? null : (SoundEffect) PiglinAI.d(this).orElse(null); // Purpur - decompile error + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPiglinBrute.java b/src/main/java/net/minecraft/server/EntityPiglinBrute.java +index 3e9a4af09d0a4af89584ce26428c38f59ff1ff11..f6b170811159544dc10b91226e4e54b349472c46 100644 +--- a/src/main/java/net/minecraft/server/EntityPiglinBrute.java ++++ b/src/main/java/net/minecraft/server/EntityPiglinBrute.java +@@ -15,6 +15,18 @@ public class EntityPiglinBrute extends EntityPiglinAbstract { + this.f = 20; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.piglinBruteRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.piglinBruteRidableInWater; ++ } ++ // Purpur end ++ + public static AttributeProvider.Builder eS() { + return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 50.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.3499999940395355D).a(GenericAttributes.ATTACK_DAMAGE, 7.0D); + } +@@ -44,7 +56,7 @@ public class EntityPiglinBrute extends EntityPiglinAbstract { + + @Override + public BehaviorController getBehaviorController() { +- return super.getBehaviorController(); ++ return (BehaviorController) super.getBehaviorController(); // Purpur - decompile error + } + + @Override +@@ -60,7 +72,8 @@ public class EntityPiglinBrute extends EntityPiglinAbstract { + @Override + protected void mobTick() { + this.world.getMethodProfiler().enter("piglinBruteBrain"); +- this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this); ++ if (getRider() == null) // Purpur - only use brain if no rider ++ this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error + this.world.getMethodProfiler().exit(); + PiglinBruteAI.b(this); + PiglinBruteAI.c(this); +diff --git a/src/main/java/net/minecraft/server/EntityPillager.java b/src/main/java/net/minecraft/server/EntityPillager.java +index a3a428da99574c485fcf2b8c7944e0d8354146ee..cf7de0127166f6175a6246062c8664e64959edeb 100644 +--- a/src/main/java/net/minecraft/server/EntityPillager.java ++++ b/src/main/java/net/minecraft/server/EntityPillager.java +@@ -13,15 +13,29 @@ public class EntityPillager extends EntityIllagerAbstract implements ICrossbow { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.pillagerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.pillagerRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new EntityRaider.a(this, 10.0F)); + this.goalSelector.a(3, new PathfinderGoalCrossbowAttack<>(this, 1.0D, 8.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); + this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 15.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 15.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index aba0647c9b297d532a5e7100eb95337e6a0db776..9e3c85d3dd70bccdbff9663e86dd0bb449701085 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -508,6 +508,15 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + this.advancementDataPlayer.b(this); ++ ++ // Purpur start ++ if (this.world.purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && world.getTime() % 100 == 0) { // 5 seconds ++ MobEffect nightVision = this.getEffect(MobEffects.NIGHT_VISION); ++ if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds ++ this.addEffect(new MobEffect(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds ++ } ++ } ++ // Purpur end + } + + public void playerTick() { +diff --git a/src/main/java/net/minecraft/server/EntityPolarBear.java b/src/main/java/net/minecraft/server/EntityPolarBear.java +index 99f0bd8f82520778d469ec51745034e6ebd3238a..3d649843f565d2c8820b525c199bd2b9f9120cc7 100644 +--- a/src/main/java/net/minecraft/server/EntityPolarBear.java ++++ b/src/main/java/net/minecraft/server/EntityPolarBear.java +@@ -18,12 +18,34 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + private static final IntRange bs = TimeRange.a(20, 39); + private int bt; + private UUID bu; ++ private int standTimer = 0; // Purpur + + public EntityPolarBear(EntityTypes entitytypes, World world) { + super(entitytypes, world); + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.polarBearRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.polarBearRidableInWater; ++ } ++ ++ @Override ++ public boolean onSpacebar() { ++ if (!isStanding()) { ++ if (hasRider() && getRider().getForward() == 0 && getRider().getStrafe() == 0) { ++ setStanding(true); ++ playSound(SoundEffects.ENTITY_POLAR_BEAR_WARNING, 1.0F, 1.0F); ++ } ++ } ++ return false; ++ } ++ + @Override + public boolean mate(EntityAnimal entityanimal) { + if (entityanimal == this) { +@@ -61,6 +83,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityPolarBear.c()); + this.goalSelector.a(1, new EntityPolarBear.d()); + // Purpur start +@@ -73,6 +96,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + this.goalSelector.a(5, new PathfinderGoalRandomStroll(this, 1.0D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new EntityPolarBear.b()); + this.targetSelector.a(2, new EntityPolarBear.a()); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::a_)); +@@ -185,6 +209,11 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + this.a((WorldServer) this.world, true); + } + ++ // Purpur start ++ if (isStanding() && --standTimer <= 0) { ++ setStanding(false); ++ } ++ // Purpur end + } + + @Override +@@ -218,6 +247,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + public void setStanding(boolean standing) { t(standing); } // Purpur - OBFHELPER + public void t(boolean flag) { + this.datawatcher.set(EntityPolarBear.bo, flag); ++ standTimer = flag ? 20 : -1; // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPufferFish.java b/src/main/java/net/minecraft/server/EntityPufferFish.java +index 330ec38d09636232250d97baad0cef7a9305d614..16a4df27c557e2d4a0fd4f48317307b884c2688c 100644 +--- a/src/main/java/net/minecraft/server/EntityPufferFish.java ++++ b/src/main/java/net/minecraft/server/EntityPufferFish.java +@@ -17,6 +17,18 @@ public class EntityPufferFish extends EntityFish { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.pufferfishRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ // Purpur end ++ + @Override + protected void initDatawatcher() { + super.initDatawatcher(); +@@ -60,7 +72,7 @@ public class EntityPufferFish extends EntityFish { + @Override + protected void initPathfinder() { + super.initPathfinder(); +- this.goalSelector.a(1, new EntityPufferFish.a(this)); ++ this.goalSelector.a(2, new EntityPufferFish.a(this)); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java +index f17608730fca96af4f9779863a8c25723a3bd5cf..95f4592944a53aab0ff9843ae8e7c9b9cd0201c7 100644 +--- a/src/main/java/net/minecraft/server/EntityRabbit.java ++++ b/src/main/java/net/minecraft/server/EntityRabbit.java +@@ -20,6 +20,18 @@ public class EntityRabbit extends EntityAnimal { + this.initializePathFinderGoals(); // CraftBukkit - moved code + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.rabbitRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.rabbitRidableInWater; ++ } ++ // Purpur end ++ + // CraftBukkit start - code from constructor + public void initializePathFinderGoals(){ + this.i(0.0D); +@@ -28,7 +40,8 @@ public class EntityRabbit extends EntityAnimal { + + @Override + public void initPathfinder() { +- this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityRabbit.PathfinderGoalRabbitPanic(this, 2.2D)); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 0.8D)); + this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, RecipeItemStack.a(Items.CARROT, Items.GOLDEN_CARROT, Blocks.DANDELION), false)); +@@ -41,7 +54,15 @@ public class EntityRabbit extends EntityAnimal { + } + + @Override +- protected float dJ() { ++ // Purpur start ++ public float getJumpHeight() { ++ if (hasRider()) { ++ if (getForward() < 0) { ++ setSpeed(getForward() * 2F); ++ } ++ return actualJump ? 0.5F : 0.3F; ++ } ++ // Purpur end + if (!this.positionChanged && (!this.moveController.b() || this.moveController.e() <= this.locY() + 0.5D)) { + PathEntity pathentity = this.navigation.k(); + +@@ -60,7 +81,7 @@ public class EntityRabbit extends EntityAnimal { + } + + @Override +- protected void jump() { ++ public void jump() { // Purpur - protected -> public + super.jump(); + double d0 = this.moveController.c(); + +@@ -92,6 +113,7 @@ public class EntityRabbit extends EntityAnimal { + + } + ++ public void startJumping() { eK(); } // Purpur - OBFHELPER + public void eK() { + this.setJumping(true); + this.br = 10; +@@ -106,6 +128,13 @@ public class EntityRabbit extends EntityAnimal { + + @Override + public void mobTick() { ++ // Purpur start ++ if (hasRider()) { ++ handleJumping(); ++ return; ++ } ++ // Purpur end ++ + if (this.bt > 0) { + --this.bt; + } +@@ -156,6 +185,39 @@ public class EntityRabbit extends EntityAnimal { + this.bs = this.onGround; + } + ++ // Purpur start ++ private boolean wasOnGround; ++ private boolean actualJump; ++ ++ private void handleJumping() { ++ if (onGround) { ++ ControllerJumpRabbit jumpController = (ControllerJumpRabbit) getJumpController(); ++ if (!wasOnGround) { ++ setJumping(false); ++ jumpController.setCanJump(false); ++ } ++ if (!jumpController.isJumping()) { ++ if (moveController.b()) { // isUpdating ++ startJumping(); ++ } ++ } else if (!jumpController.canJump()) { ++ jumpController.setCanJump(true); ++ } ++ } ++ wasOnGround = onGround; ++ } ++ ++ @Override ++ public boolean onSpacebar() { ++ if (onGround) { ++ actualJump = true; ++ jump(); ++ actualJump = false; ++ } ++ return true; ++ } ++ // Purpur end ++ + @Override + public boolean aO() { + return false; +@@ -485,7 +547,7 @@ public class EntityRabbit extends EntityAnimal { + } + } + +- static class ControllerMoveRabbit extends ControllerMove { ++ static class ControllerMoveRabbit extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur + + private final EntityRabbit i; + private double j; +@@ -496,14 +558,14 @@ public class EntityRabbit extends EntityAnimal { + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (this.i.onGround && !this.i.jumping && !((EntityRabbit.ControllerJumpRabbit) this.i.bi).c()) { + this.i.i(0.0D); + } else if (this.b()) { + this.i.i(this.j); + } + +- super.a(); ++ super.tick(); // Purpur + } + + @Override +@@ -530,14 +592,17 @@ public class EntityRabbit extends EntityAnimal { + this.c = entityrabbit; + } + ++ public boolean isJumping() { return c(); } // Purpur - OBFHELPER + public boolean c() { + return this.a; + } + ++ public boolean canJump() { return d(); } // Purpur - OBFHELPER + public boolean d() { + return this.d; + } + ++ public void setCanJump(boolean canJump) { a(canJump); } // Purpur - OBFHELPER + public void a(boolean flag) { + this.d = flag; + } +diff --git a/src/main/java/net/minecraft/server/EntityRavager.java b/src/main/java/net/minecraft/server/EntityRavager.java +index fd1ac7df68a0caebe35290cdf7a9c37519342b61..a9021458814d84a3a82088f91956db73562c3b10 100644 +--- a/src/main/java/net/minecraft/server/EntityRavager.java ++++ b/src/main/java/net/minecraft/server/EntityRavager.java +@@ -20,14 +20,37 @@ public class EntityRavager extends EntityRaider { + this.f = 20; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.ravagerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.ravagerRidableInWater; ++ } ++ ++ @Override ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ getNavigation().stopPathfinding(); ++ ++ double speed = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getBaseValue(); ++ getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(speed); ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(4, new EntityRavager.a()); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 0.4D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(2, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(4, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, true)); +@@ -99,7 +122,7 @@ public class EntityRavager extends EntityRaider { + @Override + public void movementTick() { + super.movementTick(); +- if (this.isAlive()) { ++ if (this.isAlive() && !hasRider()) { + if (this.isFrozen()) { + this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.0D); + } else { +diff --git a/src/main/java/net/minecraft/server/EntitySalmon.java b/src/main/java/net/minecraft/server/EntitySalmon.java +index 51ec63413306cf9dcd685870ccbd4e5440ab2e7a..6dfcee2c9b658c2c9ee1179e412389934c066d48 100644 +--- a/src/main/java/net/minecraft/server/EntitySalmon.java ++++ b/src/main/java/net/minecraft/server/EntitySalmon.java +@@ -6,6 +6,18 @@ public class EntitySalmon extends EntityFishSchool { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.salmonRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ // Purpur end ++ + @Override + public int eN() { + return 5; +diff --git a/src/main/java/net/minecraft/server/EntitySheep.java b/src/main/java/net/minecraft/server/EntitySheep.java +index 2908e9cc47947daad19391d38da3c2a300f67fe5..a151d4295c02930687a23212647de60cce5405ca 100644 +--- a/src/main/java/net/minecraft/server/EntitySheep.java ++++ b/src/main/java/net/minecraft/server/EntitySheep.java +@@ -56,10 +56,23 @@ public class EntitySheep extends EntityAnimal implements IShearable { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.sheepRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.sheepRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.bs = new PathfinderGoalEatTile(this); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.25D)); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); + this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.1D, RecipeItemStack.a(Items.WHEAT), false)); +diff --git a/src/main/java/net/minecraft/server/EntityShulker.java b/src/main/java/net/minecraft/server/EntityShulker.java +index 7bedaf02c4a56067f55a5d15cb18c002df87a404..30e0e14162cce0c0d228139d4c537243a400ef13 100644 +--- a/src/main/java/net/minecraft/server/EntityShulker.java ++++ b/src/main/java/net/minecraft/server/EntityShulker.java +@@ -29,12 +29,26 @@ public class EntityShulker extends EntityGolem implements IMonster { + this.f = 5; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.shulkerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.shulkerRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(4, new EntityShulker.a()); + this.goalSelector.a(7, new EntityShulker.e()); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(2, new EntityShulker.d(this)); + this.targetSelector.a(3, new EntityShulker.c(this)); +@@ -518,7 +532,7 @@ public class EntityShulker extends EntityGolem implements IMonster { + + private int b; + +- private e() {} ++ private e() { this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); } // Purpur - peek + + @Override + public boolean a() { +diff --git a/src/main/java/net/minecraft/server/EntitySilverfish.java b/src/main/java/net/minecraft/server/EntitySilverfish.java +index 28b490cc14f5881eb83acfbd6f30f9163ffe1926..ad428e090089a461283445022b33313520585ac5 100644 +--- a/src/main/java/net/minecraft/server/EntitySilverfish.java ++++ b/src/main/java/net/minecraft/server/EntitySilverfish.java +@@ -11,13 +11,27 @@ public class EntitySilverfish extends EntityMonster { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.silverfishRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.silverfishRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.b = new EntitySilverfish.PathfinderGoalSilverfishWakeOthers(this); + this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(3, this.b); + this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, 1.0D, false)); + this.goalSelector.a(5, new EntitySilverfish.PathfinderGoalSilverfishHideInBlock(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + } +@@ -177,6 +191,7 @@ public class EntitySilverfish extends EntityMonster { + + public PathfinderGoalSilverfishWakeOthers(EntitySilverfish entitysilverfish) { + this.silverfish = entitysilverfish; ++ this.a(EnumSet.of(PathfinderGoal.Type.UNKNOWN_BEHAVIOR)); // Purpur + } + + public void g() { +diff --git a/src/main/java/net/minecraft/server/EntitySkeleton.java b/src/main/java/net/minecraft/server/EntitySkeleton.java +index 7c39bec8314a3db63a90ccc9f040b82d754705e2..3f130e03bf8b235360385fd169d4886ffcfa626a 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeleton.java ++++ b/src/main/java/net/minecraft/server/EntitySkeleton.java +@@ -6,6 +6,18 @@ public class EntitySkeleton extends EntitySkeletonAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.skeletonRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.skeletonRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected SoundEffect getSoundAmbient() { + return SoundEffects.ENTITY_SKELETON_AMBIENT; +diff --git a/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java b/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java +index f73304240a626f3f7d9355e6e5f2963a06c4bb7d..ee4c26de15a5c304889f38f49f4584e8d4ccc5fe 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java ++++ b/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java +@@ -28,12 +28,14 @@ public abstract class EntitySkeletonAbstract extends EntityMonster implements IR + + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new PathfinderGoalRestrictSun(this)); + this.goalSelector.a(3, new PathfinderGoalFleeSun(this, 1.0D)); + this.goalSelector.a(3, new PathfinderGoalAvoidTarget<>(this, EntityWolf.class, 6.0F, 1.0D, 1.2D)); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(6, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0])); + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); +diff --git a/src/main/java/net/minecraft/server/EntitySkeletonStray.java b/src/main/java/net/minecraft/server/EntitySkeletonStray.java +index f985caada082eff6183d7bc9868b1782f9529eaf..d123fb82b635d5271bea9b238554a3011858eeae 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeletonStray.java ++++ b/src/main/java/net/minecraft/server/EntitySkeletonStray.java +@@ -8,6 +8,18 @@ public class EntitySkeletonStray extends EntitySkeletonAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.strayRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.strayRidableInWater; ++ } ++ // Purpur end ++ + public static boolean a(EntityTypes entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { + return b(entitytypes, worldaccess, enummobspawn, blockposition, random) && (enummobspawn == EnumMobSpawn.SPAWNER || worldaccess.e(blockposition)); + } +diff --git a/src/main/java/net/minecraft/server/EntitySkeletonWither.java b/src/main/java/net/minecraft/server/EntitySkeletonWither.java +index c872be77a6cd767520d5412b38ec4ed4fa87ac2f..96cb080d940db22330598a8806726088b79a53c1 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeletonWither.java ++++ b/src/main/java/net/minecraft/server/EntitySkeletonWither.java +@@ -9,6 +9,18 @@ public class EntitySkeletonWither extends EntitySkeletonAbstract { + this.a(PathType.LAVA, 8.0F); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.witherSkeletonRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.witherSkeletonRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityPiglinAbstract.class, true)); +diff --git a/src/main/java/net/minecraft/server/EntitySlime.java b/src/main/java/net/minecraft/server/EntitySlime.java +index 8a1ff579ddf2fef191bc370dc51dd2e6404d9a22..3c14ed27e5b487451846f6811f189e6fba05d32d 100644 +--- a/src/main/java/net/minecraft/server/EntitySlime.java ++++ b/src/main/java/net/minecraft/server/EntitySlime.java +@@ -34,12 +34,45 @@ public class EntitySlime extends EntityInsentient implements IMonster { + this.moveController = new EntitySlime.ControllerMoveSlime(this); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.slimeRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.slimeRidableInWater; ++ } ++ ++ protected boolean actualJump; ++ ++ @Override ++ public float getJumpHeight() { ++ float height = super.getJumpHeight(); ++ return hasRider() && actualJump ? height * 1.5F : height; ++ } ++ ++ @Override ++ public boolean onSpacebar() { ++ if (onGround && hasRider()) { ++ actualJump = true; ++ if (getRider().getForward() == 0 || getRider().getStrafe() == 0) { ++ jump(); // jump() here if not moving ++ } ++ } ++ return true; // do not jump() in wasd controller, let vanilla controller handle ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntitySlime.PathfinderGoalSlimeRandomJump(this)); + this.goalSelector.a(2, new EntitySlime.PathfinderGoalSlimeNearestPlayer(this)); + this.goalSelector.a(3, new EntitySlime.PathfinderGoalSlimeRandomDirection(this)); + this.goalSelector.a(5, new EntitySlime.PathfinderGoalSlimeIdle(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, (entityliving) -> { + return Math.abs(entityliving.locY() - this.locY()) <= 4.0D; + })); +@@ -325,11 +358,12 @@ public class EntitySlime extends EntityInsentient implements IMonster { + } + + @Override +- protected void jump() { ++ public void jump() { // Purpur - protected -> public + Vec3D vec3d = this.getMot(); + + this.setMot(vec3d.x, (double) this.dJ(), vec3d.z); + this.impulse = true; ++ this.actualJump = false; // Purpur + } + + @Nullable +@@ -498,10 +532,10 @@ public class EntitySlime extends EntityInsentient implements IMonster { + // Paper end + } + +- static class ControllerMoveSlime extends ControllerMove { ++ static class ControllerMoveSlime extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur + + private float i; +- private int j; ++ private int j; private int getJumpDelay() { return j; } private void setJumpDelay(int delay) { j = delay; } // Purpur - OBFHELPER + private final EntitySlime k; + private boolean l; + +@@ -523,15 +557,27 @@ public class EntitySlime extends EntityInsentient implements IMonster { + + @Override + public void a() { ++ // Purpur start ++ if (entity.hasRider()) { ++ tick(entity.getRider()); ++ if (entity.getForward() != 0 || entity.getStrafe() != 0) { ++ if (getJumpDelay() > 10) { ++ setJumpDelay(6); ++ } ++ } else { ++ setJumpDelay(20); ++ } ++ } else { ++ // Purpur end + this.a.yaw = this.a(this.a.yaw, this.i, 90.0F); + this.a.aC = this.a.yaw; + this.a.aA = this.a.yaw; +- if (this.h != ControllerMove.Operation.MOVE_TO) { ++ } if (!entity.hasRider() && this.h != ControllerMove.Operation.MOVE_TO) { // Purpur + this.a.t(0.0F); + } else { + this.h = ControllerMove.Operation.WAIT; + if (this.a.isOnGround()) { +- this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED))); ++ this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED) * (entity.hasRider() && (entity.getRider().getForward() != 0 || entity.getRider().getStrafe() != 0) ? 2.0D : 1.0D))); // Purpur + if (this.j-- <= 0) { + this.j = this.k.eJ(); + if (this.l) { +@@ -548,7 +594,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + this.a.q(0.0F); + } + } else { +- this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED))); ++ this.a.q((float) (this.e * this.a.b(GenericAttributes.MOVEMENT_SPEED) * (entity.hasRider() && (entity.getRider().getForward() != 0 || entity.getRider().getStrafe() != 0) ? 2.0D : 1.0D))); // Purpur + } + + } +diff --git a/src/main/java/net/minecraft/server/EntitySnowman.java b/src/main/java/net/minecraft/server/EntitySnowman.java +index 95ee716fc9b79b5fcb8508118b3876c51f3f6987..e980da14cf4f34c87a88ffd2b908223808404966 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowman.java ++++ b/src/main/java/net/minecraft/server/EntitySnowman.java +@@ -14,12 +14,26 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.snowGolemRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.snowGolemRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new PathfinderGoalArrowAttack(this, 1.25D, 20, 10.0F)); + this.goalSelector.a(2, new PathfinderGoalRandomStrollLand(this, 1.0D, 1.0000001E-5F)); + this.goalSelector.a(3, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(4, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalNearestAttackableTarget<>(this, EntityInsentient.class, 10, true, false, (entityliving) -> { + return entityliving instanceof IMonster; + })); +@@ -71,6 +85,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + return; + } + ++ if (hasRider() && !world.purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden + IBlockData iblockdata = Blocks.SNOW.getBlockData(); + + for (int l = 0; l < 4; ++l) { +@@ -113,7 +128,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + if (itemstack.getItem() == Items.SHEARS && this.canShear()) { + // CraftBukkit start + if (!CraftEventFactory.handlePlayerShearEntityEvent(entityhuman, this, itemstack, enumhand)) { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + // CraftBukkit end + this.shear(SoundCategory.PLAYERS); +@@ -141,7 +156,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + return EnumInteractionResult.SUCCESS; + // Purpur end + } else { +- return EnumInteractionResult.PASS; ++ return tryRide(entityhuman, enumhand); // Purpur + } + } + +diff --git a/src/main/java/net/minecraft/server/EntitySpider.java b/src/main/java/net/minecraft/server/EntitySpider.java +index bf68efd52f607ae353d6f84d4896926e16740523..92d74137877d096970bf9d1b4fc91beabb862b9d 100644 +--- a/src/main/java/net/minecraft/server/EntitySpider.java ++++ b/src/main/java/net/minecraft/server/EntitySpider.java +@@ -11,14 +11,28 @@ public class EntitySpider extends EntityMonster { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.spiderRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.spiderRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(3, new PathfinderGoalLeapAtTarget(this, 0.4F)); + this.goalSelector.a(4, new EntitySpider.PathfinderGoalSpiderMeleeAttack(this)); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 0.8D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(6, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0])); + this.targetSelector.a(2, new EntitySpider.PathfinderGoalSpiderNearestAttackableTarget<>(this, EntityHuman.class)); + this.targetSelector.a(3, new EntitySpider.PathfinderGoalSpiderNearestAttackableTarget<>(this, EntityIronGolem.class)); +diff --git a/src/main/java/net/minecraft/server/EntitySquid.java b/src/main/java/net/minecraft/server/EntitySquid.java +index 148e4b158734f136832e5c17bdc69634c0f294aa..70b952f10a2af547f58069977ee135469d02f84d 100644 +--- a/src/main/java/net/minecraft/server/EntitySquid.java ++++ b/src/main/java/net/minecraft/server/EntitySquid.java +@@ -26,17 +26,38 @@ public class EntitySquid extends EntityWaterAnimal { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.squidRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ + @Override + public AxisAlignedBB getAxisForFluidCheck() { + // Stops squids from floating just over the water + return this.getBoundingBox().shrink(0.001D).offsetY(world.purpurConfig.squidOffsetWaterCheck); + } ++ ++ private void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) { ++ double rad = Math.toRadians(degrees); ++ double cos = Math.cos(rad); ++ double sine = Math.sin(rad); ++ double x = vector.getX(); ++ double z = vector.getZ(); ++ vector.setX(cos * x - sine * z); ++ vector.setZ(sine * x + cos * z); ++ } + // Purpur end + + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new EntitySquid.PathfinderGoalSquid(this)); +- this.goalSelector.a(1, new EntitySquid.a()); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur ++ this.goalSelector.a(2, new EntitySquid.a()); // Purpur + } + + public static AttributeProvider.Builder m() { +@@ -181,6 +202,7 @@ public class EntitySquid extends EntityWaterAnimal { + return blockposition.getY() > generatoraccess.getMinecraftWorld().spigotConfig.squidSpawnRangeMin && blockposition.getY() < maxHeight; // Spigot // Paper + } + ++ public void setMovementVector(float x, float y, float z) { a(x, y, z); } // Purpur - OBFHELPER + public void a(float f, float f1, float f2) { + this.bw = f; + this.bx = f1; +@@ -252,7 +274,7 @@ public class EntitySquid extends EntityWaterAnimal { + + class PathfinderGoalSquid extends PathfinderGoal { + +- private final EntitySquid b; ++ private final EntitySquid b; public EntitySquid getSquid() { return b; } // Purpur - OBFHELPER + + public PathfinderGoalSquid(EntitySquid entitysquid) { + this.b = entitysquid; +@@ -265,6 +287,38 @@ public class EntitySquid extends EntityWaterAnimal { + + @Override + public void e() { ++ // Purpur start ++ EntitySquid squid = getSquid(); ++ EntityHuman rider = squid.getRider(); ++ if (rider != null) { ++ if (rider.jumping) { ++ squid.onSpacebar(); ++ } ++ float forward = rider.getForward(); ++ float strafe = rider.getStrafe(); ++ float speed = (float) squid.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * 10F; ++ if (forward < 0.0F) { ++ speed *= -0.5; ++ } ++ org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F); ++ if (strafe != 0.0F) { ++ if (forward == 0.0F) { ++ dir.setY(0); ++ rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90); ++ } else if (forward < 0.0F) { ++ rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45); ++ } else { ++ rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45); ++ } ++ } ++ if (forward != 0.0F || strafe != 0.0F) { ++ squid.setMovementVector((float) dir.getX(), (float) dir.getY(), (float) dir.getZ()); ++ } else { ++ squid.setMovementVector(0.0F, 0.0F, 0.0F); ++ } ++ return; ++ } ++ // Purpur end + int i = this.b.dd(); + + if (i > 100) { +diff --git a/src/main/java/net/minecraft/server/EntityStrider.java b/src/main/java/net/minecraft/server/EntityStrider.java +index 6d4c6a8f1cbb3e938dcc7c594e93c71680758dab..172867f50d0dba45a296b029c8fa85f1a19a49dc 100644 +--- a/src/main/java/net/minecraft/server/EntityStrider.java ++++ b/src/main/java/net/minecraft/server/EntityStrider.java +@@ -28,6 +28,18 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab + this.a(PathType.DAMAGE_FIRE, 0.0F); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.striderRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.striderRidableInWater; ++ } ++ // Purpur end ++ + public static boolean c(EntityTypes entitytypes, GeneratorAccess generatoraccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = blockposition.i(); + +@@ -89,6 +101,7 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab + @Override + protected void initPathfinder() { + this.bv = new PathfinderGoalPanic(this, 1.65D); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, this.bv); + this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D)); + this.bu = new PathfinderGoalTempt(this, 1.4D, false, EntityStrider.bp); +@@ -363,7 +376,7 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab + if (!enuminteractionresult.a()) { + ItemStack itemstack = entityhuman.b(enumhand); + +- return itemstack.getItem() == Items.SADDLE ? itemstack.a(entityhuman, (EntityLiving) this, enumhand) : EnumInteractionResult.PASS; ++ return itemstack.getItem() == Items.SADDLE ? itemstack.a(entityhuman, (EntityLiving) this, enumhand) : tryRide(entityhuman, enumhand); // Purpur + } else { + if (flag && !this.isSilent()) { + this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_STRIDER_EAT, this.getSoundCategory(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); +diff --git a/src/main/java/net/minecraft/server/EntityTameableAnimal.java b/src/main/java/net/minecraft/server/EntityTameableAnimal.java +index 9e008d56bb88550f399008095734436a5ab768c7..8f227678dc4ab1b7369a0c76173b3f695a570620 100644 +--- a/src/main/java/net/minecraft/server/EntityTameableAnimal.java ++++ b/src/main/java/net/minecraft/server/EntityTameableAnimal.java +@@ -130,6 +130,7 @@ public abstract class EntityTameableAnimal extends EntityAnimal { + return this.i(entityliving) ? false : super.c(entityliving); + } + ++ public boolean isOwner(EntityLiving entityLiving) { return i(entityLiving); } // Purpur - OBFHELPER + public boolean i(EntityLiving entityliving) { + return entityliving == this.getOwner(); + } +diff --git a/src/main/java/net/minecraft/server/EntityTropicalFish.java b/src/main/java/net/minecraft/server/EntityTropicalFish.java +index 495c28ccb86a5645459c9265c90dfffb6972d604..2c9df356e685ea6f71653023fadcf7e287dcd46e 100644 +--- a/src/main/java/net/minecraft/server/EntityTropicalFish.java ++++ b/src/main/java/net/minecraft/server/EntityTropicalFish.java +@@ -19,6 +19,18 @@ public class EntityTropicalFish extends EntityFishSchool { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.tropicalFishRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return true; ++ } ++ // Purpur end ++ + @Override + protected void initDatawatcher() { + super.initDatawatcher(); +diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java +index 4ad393bc99881d813e2b349fb929fc8e69631723..2b34e6cf3b86319bd2875d92b63902889fec32a8 100644 +--- a/src/main/java/net/minecraft/server/EntityTurtle.java ++++ b/src/main/java/net/minecraft/server/EntityTurtle.java +@@ -27,6 +27,18 @@ public class EntityTurtle extends EntityAnimal { + this.G = 1.0F; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.turtleRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.turtleRidableInWater; ++ } ++ // Purpur end ++ + public void setHomePos(BlockPosition blockposition) { + this.datawatcher.set(EntityTurtle.bp, blockposition.immutableCopy()); // Paper - called with mutablepos... + } +@@ -135,12 +147,13 @@ public class EntityTurtle extends EntityAnimal { + + @Override + protected void initPathfinder() { +- this.goalSelector.a(0, new EntityTurtle.f(this, 1.2D)); +- this.goalSelector.a(1, new EntityTurtle.a(this, 1.0D)); +- this.goalSelector.a(1, new EntityTurtle.d(this, 1.0D)); +- this.goalSelector.a(2, new EntityTurtle.i(this, 1.1D, Blocks.SEAGRASS.getItem())); +- this.goalSelector.a(3, new EntityTurtle.c(this, 1.0D)); +- this.goalSelector.a(4, new EntityTurtle.b(this, 1.0D)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur ++ this.goalSelector.a(1, new EntityTurtle.f(this, 1.2D)); // Purpur ++ this.goalSelector.a(2, new EntityTurtle.a(this, 1.0D)); // Purpur ++ this.goalSelector.a(2, new EntityTurtle.d(this, 1.0D)); // Purpur ++ this.goalSelector.a(3, new EntityTurtle.i(this, 1.1D, Blocks.SEAGRASS.getItem())); // Purpur ++ this.goalSelector.a(4, new EntityTurtle.c(this, 1.0D)); // Purpur ++ this.goalSelector.a(5, new EntityTurtle.b(this, 1.0D)); // Purpur + this.goalSelector.a(7, new EntityTurtle.j(this, 1.0D)); + this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(9, new EntityTurtle.h(this, 1.0D, 100)); +@@ -323,13 +336,15 @@ public class EntityTurtle extends EntityAnimal { + } + } + +- static class e extends ControllerMove { ++ static class e extends net.pl3x.purpur.controller.ControllerMoveWASD { // Purpur + +- private final EntityTurtle i; ++ private final EntityTurtle i; public EntityTurtle getTurtle() { return i; } // Purpur - OBFHELPER ++ private final net.pl3x.purpur.controller.ControllerMoveWASDWater waterController; // Purpur + + e(EntityTurtle entityturtle) { +- super(entityturtle); ++ super(entityturtle, 0.1D); // Purpur + this.i = entityturtle; ++ waterController = new net.pl3x.purpur.controller.ControllerMoveWASDWater(entityturtle, 0.25D); // Purpur + } + + private void g() { +@@ -349,7 +364,18 @@ public class EntityTurtle extends EntityAnimal { + } + + @Override +- public void a() { ++ // Purpur start ++ public void tick(EntityHuman rider) { ++ if (getTurtle().isInWater()) { ++ waterController.tick(rider); ++ } else { ++ super.tick(rider); ++ } ++ } ++ ++ @Override ++ public void tick() { ++ // Purpur end + this.g(); + if (this.h == ControllerMove.Operation.MOVE_TO && !this.i.getNavigation().m()) { + double d0 = this.b - this.i.locX(); +diff --git a/src/main/java/net/minecraft/server/EntityVex.java b/src/main/java/net/minecraft/server/EntityVex.java +index ed6a47ad2fd973695fbb151d1a44000ec3639e54..ac75ed3e2e0e0cd8f91de9ff188e173591443b72 100644 +--- a/src/main/java/net/minecraft/server/EntityVex.java ++++ b/src/main/java/net/minecraft/server/EntityVex.java +@@ -19,6 +19,45 @@ public class EntityVex extends EntityMonster { + this.f = 3; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.vexRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.vexRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.vexMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider()) { ++ float speed; ++ if (onGround) { ++ speed = (float) getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * 0.1F; ++ } else { ++ speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue(); ++ } ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, 1.0, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ ++ @Override ++ public boolean b(float f, float f1) { ++ return false; // no fall damage please ++ } ++ // Purpur end ++ + @Override + public void move(EnumMoveType enummovetype, Vec3D vec3d) { + super.move(enummovetype, vec3d); +@@ -27,7 +66,7 @@ public class EntityVex extends EntityMonster { + + @Override + public void tick() { +- this.noclip = true; ++ this.noclip = !hasRider(); // Purpur + super.tick(); + this.noclip = false; + this.setNoGravity(true); +@@ -42,17 +81,19 @@ public class EntityVex extends EntityMonster { + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(4, new EntityVex.a()); + this.goalSelector.a(8, new EntityVex.d()); + this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(2, new EntityVex.b(this)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + } + + public static AttributeProvider.Builder m() { +- return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 14.0D).a(GenericAttributes.ATTACK_DAMAGE, 4.0D); ++ return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 14.0D).a(GenericAttributes.ATTACK_DAMAGE, 4.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur + } + + @Override +@@ -284,14 +325,14 @@ public class EntityVex extends EntityMonster { + } + } + +- class c extends ControllerMove { ++ class c extends net.pl3x.purpur.controller.ControllerMoveWASDFlying { // Purpur + + public c(EntityVex entityvex) { + super(entityvex); + } + + @Override +- public void a() { ++ public void tick() { // Purpur + if (this.h == ControllerMove.Operation.MOVE_TO) { + Vec3D vec3d = new Vec3D(this.b - EntityVex.this.locX(), this.c - EntityVex.this.locY(), this.d - EntityVex.this.locZ()); + double d0 = vec3d.f(); +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index a0bfef54c853d57c9a5c6d3f9f19591649295357..548a993a1de939396d075f9176e0d60eebc7b010 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -75,8 +75,19 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.villagerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.villagerRidableInWater; ++ } ++ + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + if (world.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS)); + } + +@@ -241,7 +252,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + if (itemstack.getItem() != Items.VILLAGER_SPAWN_EGG && this.isAlive() && !this.eN() && !this.isSleeping()) { + if (this.isBaby()) { + this.fk(); +- return EnumInteractionResult.a(this.world.isClientSide); ++ return super.b(entityhuman, enumhand); // Purpur + } else { + boolean flag = this.getOffers().isEmpty(); + +@@ -254,8 +265,9 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + } + + if (flag) { +- return EnumInteractionResult.a(this.world.isClientSide); ++ return tryRide(entityhuman, enumhand); // Purpur + } else { ++ if (world.purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(entityhuman, enumhand); // Purpur + if (!this.world.isClientSide && !this.trades.isEmpty()) { + this.h(entityhuman); + } +diff --git a/src/main/java/net/minecraft/server/EntityVillagerTrader.java b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +index 96dda6a14fd17509e9bcb72cc7e9c8532c6a036b..3ea66955df304fd13aac2cf9bb93ea156558ae57 100644 +--- a/src/main/java/net/minecraft/server/EntityVillagerTrader.java ++++ b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +@@ -23,6 +23,7 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(0, new PathfinderGoalUseItem<>(this, PotionUtil.a(new ItemStack(Items.POTION), Potions.INVISIBILITY), SoundEffects.ENTITY_WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { + return this.world.isNight() && !entityvillagertrader.isInvisible(); + })); +@@ -48,6 +49,16 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + } + + // Purpur - start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.villagerTraderRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.villagerTraderRidableInWater; ++ } ++ + @Override + public boolean a(EntityHuman entityhuman) { + return world.purpurConfig.villagerTraderCanBeLeashed && !this.isLeashed(); +@@ -75,8 +86,9 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + } + + if (this.getOffers().isEmpty()) { +- return EnumInteractionResult.a(this.world.isClientSide); ++ return tryRide(entityhuman, enumhand); // Purpur + } else { ++ if (world.purpurConfig.villagerTraderRidable && itemstack.isEmpty()) return tryRide(entityhuman, enumhand); // Purpur + if (!this.world.isClientSide) { + this.setTradingPlayer(entityhuman); + this.openTrade(entityhuman, this.getScoreboardDisplayName(), 1); +diff --git a/src/main/java/net/minecraft/server/EntityVindicator.java b/src/main/java/net/minecraft/server/EntityVindicator.java +index 23b350f793539672b6990327ed52e9bb3bdbf54e..f1bc6a4199d788215c2e7d5a835211d16603a6d9 100644 +--- a/src/main/java/net/minecraft/server/EntityVindicator.java ++++ b/src/main/java/net/minecraft/server/EntityVindicator.java +@@ -18,14 +18,28 @@ public class EntityVindicator extends EntityIllagerAbstract { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.vindicatorRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.vindicatorRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + super.initPathfinder(); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(1, new EntityVindicator.a(this)); + this.goalSelector.a(2, new EntityIllagerAbstract.b(this)); + this.goalSelector.a(3, new EntityRaider.a(this, 10.0F)); + this.goalSelector.a(4, new EntityVindicator.c(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); // Paper - decompile fix + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, true)); +diff --git a/src/main/java/net/minecraft/server/EntityWitch.java b/src/main/java/net/minecraft/server/EntityWitch.java +index ca3c5150bcfe2a92b49ad5a27c23dd37a7054fbb..323d79a99402b0f6952b4fb873170069f3428953 100644 +--- a/src/main/java/net/minecraft/server/EntityWitch.java ++++ b/src/main/java/net/minecraft/server/EntityWitch.java +@@ -24,6 +24,18 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + super(entitytypes, world); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.witchRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.witchRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + super.initPathfinder(); +@@ -32,10 +44,12 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + }); + this.bs = new PathfinderGoalNearestAttackableTargetWitch<>(this, EntityHuman.class, 10, true, false, (Predicate) null); + this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new PathfinderGoalArrowAttack(this, 1.0D, 60, 10.0F)); + this.goalSelector.a(2, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.goalSelector.a(3, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(3, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})); + this.targetSelector.a(2, this.br); + this.targetSelector.a(3, this.bs); +diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java +index 2e623ef9be036ea467e9e41817c2eced018f8f93..0abfa34a8fa0088c2089645f54f85d4490a04bc9 100644 +--- a/src/main/java/net/minecraft/server/EntityWither.java ++++ b/src/main/java/net/minecraft/server/EntityWither.java +@@ -26,6 +26,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + private final float[] bt = new float[2]; + private final int[] bu = new int[2]; + private final int[] bv = new int[2]; ++ private int shootCooldown = 0; // Purpur + private int bw; + public final BossBattleServer bossBattle; + private static final Predicate by = (entityliving) -> { +@@ -44,15 +45,122 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + this.setHealth(this.getMaxHealth()); + this.getNavigation().d(true); + this.f = 50; ++ this.moveController = new net.pl3x.purpur.controller.ControllerMoveWASDFlyingWithSpacebar(this, 0.1F); // Purpur + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.witherRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.witherRidableInWater; ++ } ++ ++ @Override ++ public double getMaxY() { ++ return world.purpurConfig.witherMaxY; ++ } ++ ++ @Override ++ public void g(Vec3D vec3d) { ++ super.g(vec3d); ++ if (hasRider() && !onGround) { ++ float speed = (float) getAttributeInstance(GenericAttributes.FLYING_SPEED).getValue() * 5F; ++ setSpeed(speed); ++ Vec3D mot = getMot(); ++ move(EnumMoveType.SELF, mot.multiply(speed, 0.5, speed)); ++ setMot(mot.a(0.9D)); ++ } ++ } ++ ++ @Override ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ this.datawatcher.set(bo.get(0), 0); ++ this.datawatcher.set(bo.get(1), 0); ++ this.datawatcher.set(bo.get(2), 0); ++ getNavigation().stopPathfinding(); ++ shootCooldown = 20; ++ } ++ ++ @Override ++ public boolean onClick(EnumHand hand) { ++ return shoot(getRider(), hand == EnumHand.MAIN_HAND ? new int[]{1} : new int[]{2}); ++ } ++ ++ public boolean shoot(EntityHuman rider, int[] heads) { ++ if (shootCooldown > 0) { ++ return false; ++ } ++ ++ shootCooldown = 20; ++ if (rider == null) { ++ return false; ++ } ++ ++ org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity(); ++ if (!player.hasPermission("allow.special.wither")) { ++ return false; ++ } ++ ++ MovingObjectPosition rayTrace = getRayTrace(120, RayTrace.FluidCollisionOption.NONE); ++ if (rayTrace == null) { ++ return false; ++ } ++ ++ Vec3D loc; ++ if (rayTrace.getType() == MovingObjectPosition.EnumMovingObjectType.BLOCK) { ++ BlockPosition pos = ((MovingObjectPositionBlock) rayTrace).getBlockPosition(); ++ loc = new Vec3D(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D); ++ } else if (rayTrace.getType() == MovingObjectPosition.EnumMovingObjectType.ENTITY) { ++ Entity target = ((MovingObjectPositionEntity) rayTrace).getEntity(); ++ loc = new Vec3D(target.locX(), target.locY() + (target.getHeadHeight() / 2), target.locZ()); ++ } else { ++ org.bukkit.block.Block block = player.getTargetBlock(null, 120); ++ loc = new Vec3D(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D); ++ } ++ ++ for (int head : heads) { ++ shoot(head, loc.getX(), loc.getY(), loc.getZ(), rider); ++ } ++ ++ return true; // handled ++ } ++ ++ public void shoot(int head, double x, double y, double z, EntityHuman rider) { ++ world.playEvent(null, 1024, getChunkCoordinates(), 0); ++ double headX = getHeadX(head); ++ double headY = getHeadY(head); ++ double headZ = getHeadZ(head); ++ EntityWitherSkull skull = new EntityWitherSkull(world, this, x - headX, y - headY, z - headZ) { ++ @Override ++ public boolean canSaveToDisk() { ++ return false; ++ } ++ ++ @Override ++ public boolean hitPredicate(Entity target) { ++ // do not hit rider ++ return target != rider && super.hitPredicate(target); ++ } ++ }; ++ skull.setPositionRaw(headX, headY, headZ); ++ world.addEntity(skull); ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { +- this.goalSelector.a(0, new EntityWither.a()); ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur ++ this.goalSelector.a(1, new EntityWither.a()); // Purpur + this.goalSelector.a(2, new PathfinderGoalArrowAttack(this, 1.0D, 40, 20.0F)); + this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, new Class[0])); + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityInsentient.class, 0, false, false, EntityWither.by)); + } +@@ -194,6 +302,16 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + + @Override + protected void mobTick() { ++ // Purpur start ++ if (hasRider()) { ++ Vec3D mot = getMot(); ++ setMot(mot.x, mot.y + (getVertical() > 0 ? 0.07D : 0.0D), mot.z); ++ } ++ if (shootCooldown > 0) { ++ shootCooldown--; ++ } ++ // Purpur end ++ + int i; + + if (this.getInvul() > 0) { +@@ -377,7 +495,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + this.bossBattle.removePlayer(entityplayer); + } + +- private double u(int i) { ++ private double u(int i) { return getHeadX(i); } private double getHeadX(int i) { // Purpur - OBFHELPER + if (i <= 0) { + return this.locX(); + } else { +@@ -388,11 +506,11 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + } + } + +- private double v(int i) { ++ private double v(int i) { return getHeadY(i); } private double getHeadY(int i) { // Purpur - OBFHELPER + return i <= 0 ? this.locY() + 3.0D : this.locY() + 2.2D; + } + +- private double w(int i) { ++ private double w(int i) { return getHeadZ(i); } private double getHeadZ(int i) { // Purpur - OBFHELPER + if (i <= 0) { + return this.locZ(); + } else { +@@ -516,7 +634,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + } + + public static AttributeProvider.Builder eK() { +- return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 300.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.6000000238418579D).a(GenericAttributes.FOLLOW_RANGE, 40.0D).a(GenericAttributes.ARMOR, 4.0D); ++ return EntityMonster.eR().a(GenericAttributes.MAX_HEALTH, 300.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.6000000238418579D).a(GenericAttributes.FOLLOW_RANGE, 40.0D).a(GenericAttributes.ARMOR, 4.0D).a(GenericAttributes.FLYING_SPEED, 0.6D); // Purpur + } + + public int getInvul() { +@@ -528,11 +646,11 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + } + + public int getHeadTarget(int i) { +- return (Integer) this.datawatcher.get((DataWatcherObject) EntityWither.bo.get(i)); ++ return hasRider() ? 0 : this.datawatcher.get(EntityWither.bo.get(i)); // Purpur + } + + public void setHeadTarget(int i, int j) { +- this.datawatcher.set((DataWatcherObject) EntityWither.bo.get(i), j); ++ if (!hasRider()) this.datawatcher.set(EntityWither.bo.get(i), j); // Purpur + } + + public final boolean isPowered() { return this.S_(); } // Paper - OBFHELPER +@@ -547,7 +665,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + + @Override + protected boolean n(Entity entity) { +- return false; ++ return getRideCooldown() <= 0; // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java +index dcbb34313fedb21e180a0b76610a787e6419d404..9ae7168595dd66860e09ef87f946b18b010e54b1 100644 +--- a/src/main/java/net/minecraft/server/EntityWolf.java ++++ b/src/main/java/net/minecraft/server/EntityWolf.java +@@ -33,9 +33,27 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + this.setTamed(false); + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.wolfRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.wolfRidableInWater; ++ } ++ ++ public void onMount(EntityHuman entityhuman) { ++ super.onMount(entityhuman); ++ setSitting(false); ++ } ++ // Purpur end ++ + @Override + protected void initPathfinder() { + this.goalSelector.a(1, new PathfinderGoalFloat(this)); ++ this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new PathfinderGoalSit(this)); + this.goalSelector.a(3, new EntityWolf.a<>(this, EntityLlama.class, 24.0F, 1.5D, 1.5D)); + this.goalSelector.a(4, new PathfinderGoalLeapAtTarget(this, 0.4F)); +@@ -46,6 +64,7 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + this.goalSelector.a(9, new PathfinderGoalBeg(this, 8.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(10, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.targetSelector.a(1, new PathfinderGoalOwnerHurtByTarget(this)); + this.targetSelector.a(2, new PathfinderGoalOwnerHurtTarget(this)); + this.targetSelector.a(3, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/server/EntityZoglin.java b/src/main/java/net/minecraft/server/EntityZoglin.java +index e76e6ebde73b93dc06e76b71cdf6371c3654160a..d92fe8013fb3b43cb7eabdf1c624291b7e881889 100644 +--- a/src/main/java/net/minecraft/server/EntityZoglin.java ++++ b/src/main/java/net/minecraft/server/EntityZoglin.java +@@ -21,6 +21,18 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin { + this.f = 5; + } + ++ // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.zoglinRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.zoglinRidableInWater; ++ } ++ // Purpur end ++ + @Override + protected BehaviorController.b cK() { + return BehaviorController.a((Collection) EntityZoglin.c, (Collection) EntityZoglin.b); +@@ -52,10 +64,10 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin { + } + + private Optional eO() { +- return ((List) this.getBehaviorController().getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(ImmutableList.of())).stream().filter(EntityZoglin::i).findFirst(); ++ return (this.getBehaviorController().getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(ImmutableList.of())).stream().filter(EntityZoglin::predicate).findFirst(); // Purpur - decompile error + } + +- private static boolean i(EntityLiving entityliving) { ++ private static boolean predicate(EntityLiving entityliving) { // Purpur - decompile error + EntityTypes entitytypes = entityliving.getEntityType(); + + return entitytypes != EntityTypes.ZOGLIN && entitytypes != EntityTypes.CREEPER && IEntitySelector.f.test(entityliving); +@@ -140,14 +152,14 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin { + + @Override + public BehaviorController getBehaviorController() { +- return super.getBehaviorController(); ++ return (BehaviorController) super.getBehaviorController(); // Purpur - decompile error + } + + protected void eL() { +- Activity activity = (Activity) this.bg.f().orElse((Object) null); ++ Activity activity = (Activity) this.bg.f().orElse(null); // Purpur - decompile error + + this.bg.a((List) ImmutableList.of(Activity.FLIGHT, Activity.IDLE)); +- Activity activity1 = (Activity) this.bg.f().orElse((Object) null); ++ Activity activity1 = (Activity) this.bg.f().orElse(null); // Purpur - decompile error + + if (activity1 == Activity.FLIGHT && activity != Activity.FLIGHT) { + this.eN(); +@@ -159,7 +171,8 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin { + @Override + protected void mobTick() { + this.world.getMethodProfiler().enter("zoglinBrain"); +- this.getBehaviorController().a((WorldServer) this.world, (EntityLiving) this); ++ if (getRider() == null) // Purpur - only use brain if no rider ++ this.getBehaviorController().a((WorldServer) this.world, this); // Purpur - decompile error + this.world.getMethodProfiler().exit(); + this.eL(); + } +diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java +index 03263b94aaeeb8667e0f82c832e4743f4c63108e..d835ce3fe7c71333efeed5b9cf2a827bebae97f2 100644 +--- a/src/main/java/net/minecraft/server/EntityZombie.java ++++ b/src/main/java/net/minecraft/server/EntityZombie.java +@@ -47,6 +47,16 @@ public class EntityZombie extends EntityMonster { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.zombieRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.zombieRidableInWater; ++ } ++ + public boolean jockeyOnlyBaby() { + return world.purpurConfig.zombieJockeyOnlyBaby; + } +@@ -62,9 +72,11 @@ public class EntityZombie extends EntityMonster { + + @Override + protected void initPathfinder() { ++ this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + if (world.paperConfig.zombiesTargetTurtleEggs) this.goalSelector.a(4, new EntityZombie.a(this, 1.0D, 3)); // Paper + this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this)); ++ this.targetSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + this.m(); + } + +diff --git a/src/main/java/net/minecraft/server/EntityZombieHusk.java b/src/main/java/net/minecraft/server/EntityZombieHusk.java +index ce6d79780197eb9300130036a8ed84648a17f9cf..02b0ae550a0ed33b5b43beedf3b1405985c58c13 100644 +--- a/src/main/java/net/minecraft/server/EntityZombieHusk.java ++++ b/src/main/java/net/minecraft/server/EntityZombieHusk.java +@@ -9,6 +9,16 @@ public class EntityZombieHusk extends EntityZombie { + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.huskRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.huskRidableInWater; ++ } ++ + @Override + public boolean jockeyOnlyBaby() { + return world.purpurConfig.huskJockeyOnlyBaby; +diff --git a/src/main/java/net/minecraft/server/EntityZombieVillager.java b/src/main/java/net/minecraft/server/EntityZombieVillager.java +index 505c83f3e3ad61c2d4d40c4df017e1f7a9a3ad8c..0c47477b416980d2e932321730525bf5a8feda4f 100644 +--- a/src/main/java/net/minecraft/server/EntityZombieVillager.java ++++ b/src/main/java/net/minecraft/server/EntityZombieVillager.java +@@ -29,6 +29,16 @@ public class EntityZombieVillager extends EntityZombie implements VillagerDataHo + } + + // Purpur start ++ @Override ++ public boolean isRidable() { ++ return world.purpurConfig.zombieVillagerRidable; ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return world.purpurConfig.zombieVillagerRidableInWater; ++ } ++ + @Override + public boolean jockeyOnlyBaby() { + return world.purpurConfig.zombieVillagerJockeyOnlyBaby; +diff --git a/src/main/java/net/minecraft/server/GeneratorAccess.java b/src/main/java/net/minecraft/server/GeneratorAccess.java +index cbc0b8bc854ab1b0ba95fa0a2041385f440718d9..89d64ea0d1e61dfce622df026209af129efd5773 100644 +--- a/src/main/java/net/minecraft/server/GeneratorAccess.java ++++ b/src/main/java/net/minecraft/server/GeneratorAccess.java +@@ -37,6 +37,7 @@ public interface GeneratorAccess extends ICombinedAccess, IWorldTime { + + void addParticle(ParticleParam particleparam, double d0, double d1, double d2, double d3, double d4, double d5); + ++ default void playEvent(@Nullable EntityHuman entityhuman, int i, BlockPosition blockposition, int j) { a(entityhuman, i, blockposition, j); } // Purpur - OBFHELPER + void a(@Nullable EntityHuman entityhuman, int i, BlockPosition blockposition, int j); + + default int getHeight() { +diff --git a/src/main/java/net/minecraft/server/IProjectile.java b/src/main/java/net/minecraft/server/IProjectile.java +index dc9f2a1a132b3a7925bd62aa1da0512afd90b8b1..b7e540dfeeabb13227596ecfc6eddabf3cfde537 100644 +--- a/src/main/java/net/minecraft/server/IProjectile.java ++++ b/src/main/java/net/minecraft/server/IProjectile.java +@@ -12,7 +12,7 @@ public abstract class IProjectile extends Entity { + + private UUID shooter; + private int c; +- private boolean d; ++ private boolean d; public boolean leftOwner() { return d; } public void setLeftOwner(boolean leftOwner) { this.d = leftOwner; } // Purpur - OBFHELPER + + IProjectile(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -74,6 +74,7 @@ public abstract class IProjectile extends Entity { + super.tick(); + } + ++ public boolean checkIfLeftOwner() { return this.h(); } // Purpur - OBFHELPER + private boolean h() { + Entity entity = this.getShooter(); + +@@ -137,7 +138,7 @@ public abstract class IProjectile extends Entity { + iblockdata.a(this.world, iblockdata, movingobjectpositionblock, this); + } + +- protected boolean a(Entity entity) { ++ protected boolean a(Entity entity) { return hitPredicate(entity); } public boolean hitPredicate(Entity entity) { // Purpur - OBFHELPER + if (!entity.isSpectator() && entity.isAlive() && entity.isInteractable()) { + Entity entity1 = this.getShooter(); + // Paper start - Cancel hit for vanished players +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e16f120544ad46995dcf695849d3634c575d65a5..98961e20642e61239a6ad89445f97245aa821919 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1410,6 +1410,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper + worldserver.hasEntityMoveEvent = net.pl3x.purpur.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur ++ worldserver.hasRidableMoveEvent = net.pl3x.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur + TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.methodProfiler.a(() -> { +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalHasRider.java b/src/main/java/net/minecraft/server/PathfinderGoalHasRider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..da18e1235eed1988052fbd761c11f77efd7afc5e +--- /dev/null ++++ b/src/main/java/net/minecraft/server/PathfinderGoalHasRider.java +@@ -0,0 +1,18 @@ ++package net.minecraft.server; ++ ++import java.util.EnumSet; ++ ++public class PathfinderGoalHasRider extends PathfinderGoal { ++ public final EntityInsentient entity; ++ ++ public PathfinderGoalHasRider(EntityInsentient entity) { ++ this.entity = entity; ++ setTypes(EnumSet.of(Type.JUMP, Type.MOVE, Type.LOOK, Type.TARGET, Type.UNKNOWN_BEHAVIOR)); ++ } ++ ++ // shouldExecute ++ @Override ++ public boolean a() { ++ return entity.hasRider(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalHorseHasRider.java b/src/main/java/net/minecraft/server/PathfinderGoalHorseHasRider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..44929182dfd7ad847d9657c324f440cbf29abadf +--- /dev/null ++++ b/src/main/java/net/minecraft/server/PathfinderGoalHorseHasRider.java +@@ -0,0 +1,16 @@ ++package net.minecraft.server; ++ ++public class PathfinderGoalHorseHasRider extends PathfinderGoalHasRider { ++ private final EntityHorseAbstract horse; ++ ++ public PathfinderGoalHorseHasRider(EntityHorseAbstract entity) { ++ super(entity); ++ this.horse = entity; ++ } ++ ++ // shouldExecute ++ @Override ++ public boolean a() { ++ return super.a() && horse.hasSaddle(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index eff58bba46e5cb4bd412fcb65e293d5b9eb58aba..e9485684b7d5ddde72fc388d51cfef679178bad3 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -2288,6 +2288,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + this.server.getPluginManager().callEvent(event); + ++ player.processClick(enumhand); // Purpur ++ + // Fish bucket - SPIGOT-4048 + if ((entity instanceof EntityFish && origItem != null && origItem.getItem() == Items.WATER_BUCKET) && (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != origItem)) { + this.sendPacket(new PacketPlayOutSpawnEntityLiving((EntityFish) entity)); +diff --git a/src/main/java/net/minecraft/server/ProjectileHelper.java b/src/main/java/net/minecraft/server/ProjectileHelper.java +index b2c64b31440389db5abe2322f7e31b328f590f6c..515ba50aec81497d27297e4b6c642e86b7de53ca 100644 +--- a/src/main/java/net/minecraft/server/ProjectileHelper.java ++++ b/src/main/java/net/minecraft/server/ProjectileHelper.java +@@ -7,6 +7,7 @@ import javax.annotation.Nullable; + + public final class ProjectileHelper { + ++ public static MovingObjectPosition getHitResult(Entity entity, Predicate predicate) { return a(entity, predicate); } // Purpur - OBFHELPER + public static MovingObjectPosition a(Entity entity, Predicate predicate) { + Vec3D vec3d = entity.getMot(); + World world = entity.world; +diff --git a/src/main/java/net/minecraft/server/Vec3D.java b/src/main/java/net/minecraft/server/Vec3D.java +index 5af554870bcf36e47aef43b966b141b9eda6c4d5..c59305ef7dd7847e204d4c4ed79758bf9d66e91e 100644 +--- a/src/main/java/net/minecraft/server/Vec3D.java ++++ b/src/main/java/net/minecraft/server/Vec3D.java +@@ -39,6 +39,7 @@ public class Vec3D implements IPosition { + return new Vec3D(vec3d.x - this.x, vec3d.y - this.y, vec3d.z - this.z); + } + ++ public Vec3D normalize() { return d(); } // Purpur - OBFHELPER + public Vec3D d() { + double d0 = (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + +@@ -98,6 +99,7 @@ public class Vec3D implements IPosition { + return d3 * d3 + d4 * d4 + d5 * d5; + } + ++ public Vec3D scale(double scale) { return a(scale); } // Purpur - OBFHELPER + public Vec3D a(double d0) { + return this.d(d0, d0, d0); + } +@@ -106,6 +108,7 @@ public class Vec3D implements IPosition { + return this.d(vec3d.x, vec3d.y, vec3d.z); + } + ++ public Vec3D multiply (double x, double y, double z) { return d(x, y, z); } // Purpur - OBFHELPER + public Vec3D d(double d0, double d1, double d2) { + return new Vec3D(this.x * d0, this.y * d1, this.z * d2); + } +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index f260d01aad4db512952e5a53bf5bc01023bbd43d..cf1f4fe5832781df7d0bdd5eb24eff8539691c30 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1651,5 +1651,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + public boolean isTheEnd() { + return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END; + } ++ ++ // Purpur start ++ public void playEffect(@Nullable EntityHuman entityhuman, int i, BlockPosition blockposition, int j) { ++ this.a(entityhuman, i, blockposition, j); ++ } + // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index d5007d00da9a10e99d7fa46a4368d41fb421ac31..79ae47ffafae09059774e45b48a36818e104b036 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -102,6 +102,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + public final UUID uuid; + boolean hasPhysicsEvent = true; // Paper + boolean hasEntityMoveEvent = false; // Purpur ++ boolean hasRidableMoveEvent = false; // Purpur + private static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index c314a8c9a921a95cea43b748e2037521d948e1e7..59f3122aab9940cb3c3c1efb2664ab0835656d48 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -136,11 +136,13 @@ public class PurpurConfig { + public static String afkBroadcastBack = "§e§o%s is no longer AFK"; + public static String afkTabListPrefix = "[AFK] "; + public static String pingCommandOutput = "§a%s's ping is %sms"; ++ public static String cannotRideMob = "§cYou cannot mount that mob"; + private static void messages() { + afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); + afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); + afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix); + pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); ++ cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); + } + + public static int dungeonSeed = -1; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 2a997302e1df398f9ca546a1acfa3a9cf70c7d54..2644fbcaa8be55b0c09b79baa0727d04d87a70e2 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -42,11 +42,6 @@ public class PurpurWorldConfig { + } + } + +- public double vindicatorJohnnySpawnChance = 0D; +- private void vindicatorSettings() { +- vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); +- } +- + private ConfigurationSection getConfigurationSection(String path) { + ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); + return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); +@@ -315,63 +310,188 @@ public class PurpurWorldConfig { + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + } + ++ public boolean babiesAreRidable = true; ++ public boolean untamedTamablesAreRidable = true; ++ public boolean useNightVisionWhenRiding = false; ++ private void ridableSettings() { ++ babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable); ++ untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable); ++ useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding); ++ } ++ ++ public boolean batRidable = false; ++ public boolean batRidableInWater = false; ++ public double batMaxY = 256D; ++ private void batSettings() { ++ batRidable = getBoolean("mobs.bat.ridable", batRidable); ++ batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater); ++ batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY); ++ } ++ ++ public boolean beeRidable = false; ++ public boolean beeRidableInWater = false; ++ public double beeMaxY = 256D; ++ private void beeSettings() { ++ beeRidable = getBoolean("mobs.bee.ridable", beeRidable); ++ beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); ++ beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); ++ } ++ ++ public boolean blazeRidable = false; ++ public boolean blazeRidableInWater = false; ++ public double blazeMaxY = 256D; ++ private void blazeSettings() { ++ blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); ++ blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); ++ blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY); ++ } ++ ++ public boolean catRidable = false; ++ public boolean catRidableInWater = false; + public int catSpawnDelay = 1200; + public int catSpawnSwampHutScanRange = 16; + public int catSpawnVillageScanRange = 48; + private void catSettings() { ++ catRidable = getBoolean("mobs.cat.ridable", catRidable); ++ catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); + catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); + catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); + catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); + } + ++ public boolean caveSpiderRidable = false; ++ public boolean caveSpiderRidableInWater = false; ++ private void caveSpiderSettings() { ++ caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable); ++ caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater); ++ } ++ ++ public boolean chickenRidable = false; ++ public boolean chickenRidableInWater = false; + public boolean chickenRetaliate = false; + private void chickenSettings() { ++ chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); ++ chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); + } + ++ public boolean codRidable = false; ++ private void codSettings() { ++ codRidable = getBoolean("mobs.cod.ridable", codRidable); ++ } ++ ++ public boolean cowRidable = false; ++ public boolean cowRidableInWater = false; + public int cowFeedMushrooms = 0; + private void cowSettings() { ++ cowRidable = getBoolean("mobs.cow.ridable", cowRidable); ++ cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); + } + ++ public boolean creeperRidable = false; ++ public boolean creeperRidableInWater = false; + public boolean creeperAllowGriefing = true; + public double creeperChargedChance = 0.0D; + private void creeperSettings() { ++ creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); ++ creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); + creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + } + ++ public boolean dolphinRidable = false; ++ public int dolphinSpitCooldown = 20; ++ public float dolphinSpitSpeed = 1.0F; ++ public float dolphinSpitDamage = 2.0F; + public boolean dolphinDisableTreasureSearching = false; + private void dolphinSettings() { ++ dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); ++ dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); ++ dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed); ++ dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage); + dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); + } + ++ public boolean donkeyRidableInWater = false; ++ private void donkeySettings() { ++ donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); ++ } ++ ++ public boolean drownedRidable = false; ++ public boolean drownedRidableInWater = false; + public boolean drownedJockeyOnlyBaby = true; + public double drownedJockeyChance = 0.05D; + public boolean drownedJockeyTryExistingChickens = true; + private void drownedSettings() { ++ drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); ++ drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); + drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); + drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); + drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); + } + ++ public boolean elderGuardianRidable = false; ++ private void elderGuardianSettings() { ++ elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); ++ } ++ ++ public boolean enderDragonRidable = false; ++ public boolean enderDragonRidableInWater = false; ++ public double enderDragonMaxY = 256D; + public boolean enderDragonAlwaysDropsEggBlock = false; + public boolean enderDragonAlwaysDropsFullExp = false; + private void enderDragonSettings() { ++ enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); ++ enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); ++ enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); + enderDragonAlwaysDropsEggBlock = getBoolean("mobs.ender_dragon.always-drop-egg-block", enderDragonAlwaysDropsEggBlock); + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); + } + ++ public boolean endermanRidable = false; ++ public boolean endermanRidableInWater = false; + public boolean endermanAllowGriefing = true; + private void endermanSettings() { ++ endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); ++ endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); + } + ++ public boolean endermiteRidable = false; ++ public boolean endermiteRidableInWater = false; ++ private void endermiteSettings() { ++ endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); ++ endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); ++ } ++ ++ public boolean evokerRidable = false; ++ public boolean evokerRidableInWater = false; ++ private void evokerSettings() { ++ evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); ++ evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); ++ } ++ ++ public boolean foxRidable = false; ++ public boolean foxRidableInWater = false; + public boolean foxTypeChangesWithTulips = false; + private void foxSettings() { ++ foxRidable = getBoolean("mobs.fox.ridable", foxRidable); ++ foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); + } + ++ public boolean ghastRidable = false; ++ public boolean ghastRidableInWater = false; ++ public double ghastMaxY = 256D; ++ private void ghastSettings() { ++ ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable); ++ ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater); ++ ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY); ++ } ++ ++ public boolean giantRidable = false; ++ public boolean giantRidableInWater = false; + public float giantStepHeight = 2.0F; + public float giantJumpHeight = 1.0F; + public double giantMovementSpeed = 0.5D; +@@ -380,6 +500,8 @@ public class PurpurWorldConfig { + public boolean giantHaveHostileAI = false; + public double giantMaxHealth = 100.0D; + private void giantSettings() { ++ giantRidable = getBoolean("mobs.giant.ridable", giantRidable); ++ giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); + giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight); + giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight); + giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed); +@@ -394,19 +516,44 @@ public class PurpurWorldConfig { + giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); + } + ++ public boolean guardianRidable = false; ++ private void guardianSettings() { ++ guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); ++ } ++ ++ public boolean hoglinRidable = false; ++ public boolean hoglinRidableInWater = false; ++ private void hoglinSettings() { ++ hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); ++ hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); ++ } ++ ++ public boolean horseRidableInWater = false; ++ private void horseSettings() { ++ horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); ++ } ++ ++ public boolean huskRidable = false; ++ public boolean huskRidableInWater = false; + public boolean huskJockeyOnlyBaby = true; + public double huskJockeyChance = 0.05D; + public boolean huskJockeyTryExistingChickens = true; + private void huskSettings() { ++ huskRidable = getBoolean("mobs.husk.ridable", huskRidable); ++ huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); + huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); + huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); + huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); + } + ++ public boolean illusionerRidable = false; ++ public boolean illusionerRidableInWater = false; + public double illusionerMovementSpeed = 0.5D; + public double illusionerFollowRange = 18.0D; + public double illusionerMaxHealth = 32.0D; + private void illusionerSettings() { ++ illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable); ++ illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater); + illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed); + illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); + if (PurpurConfig.version < 8) { +@@ -417,11 +564,76 @@ public class PurpurWorldConfig { + illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); + } + ++ public boolean ironGolemRidable = false; ++ public boolean ironGolemRidableInWater = false; + public boolean ironGolemCanSwim = false; + private void ironGolemSettings() { ++ ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); ++ ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + } + ++ public boolean llamaRidable = false; ++ public boolean llamaRidableInWater = false; ++ private void llamaSettings() { ++ llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); ++ llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); ++ } ++ ++ public boolean llamaTraderRidable = false; ++ public boolean llamaTraderRidableInWater = false; ++ private void llamaTraderSettings() { ++ llamaTraderRidable = getBoolean("mobs.trader_llama.ridable", llamaTraderRidable); ++ llamaTraderRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", llamaTraderRidableInWater); ++ } ++ ++ public boolean magmaCubeRidable = false; ++ public boolean magmaCubeRidableInWater = false; ++ private void magmaCubeSettings() { ++ magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable); ++ magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater); ++ } ++ ++ public boolean mooshroomRidable = false; ++ public boolean mooshroomRidableInWater = false; ++ private void mooshroomSettings() { ++ mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); ++ mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); ++ } ++ ++ public boolean muleRidableInWater = false; ++ private void muleSettings() { ++ muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); ++ } ++ ++ public boolean ocelotRidable = false; ++ public boolean ocelotRidableInWater = false; ++ private void ocelotSettings() { ++ ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); ++ ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); ++ } ++ ++ public boolean pandaRidable = false; ++ public boolean pandaRidableInWater = false; ++ private void pandaSettings() { ++ pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); ++ pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); ++ } ++ ++ public boolean parrotRidable = false; ++ public boolean parrotRidableInWater = false; ++ public double parrotMaxY = 256D; ++ private void parrotSettings() { ++ parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); ++ parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); ++ parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY); ++ } ++ ++ public boolean phantomRidable = false; ++ public boolean phantomRidableInWater = false; ++ public double phantomMaxY = 256D; ++ public float phantomFlameDamage = 1.0F; ++ public int phantomFlameFireTime = 8; + public double phantomAttackedByCrystalRadius = 0.0D; + public float phantomAttackedByCrystalDamage = 1.0F; + public double phantomOrbitCrystalRadius = 0.0D; +@@ -441,6 +653,11 @@ public class PurpurWorldConfig { + public boolean phantomIgnorePlayersWithTorch = false; + public boolean phantomBurnInDaylight = true; + private void phantomSettings() { ++ phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); ++ phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); ++ phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY); ++ phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); ++ phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); + phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); + phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); + phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); +@@ -461,40 +678,184 @@ public class PurpurWorldConfig { + phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); + } + ++ public boolean pigRidable = false; ++ public boolean pigRidableInWater = false; + public boolean pigGiveSaddleBack = false; + private void pigSettings() { ++ pigRidable = getBoolean("mobs.pig.ridable", pigRidable); ++ pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); + } + ++ public boolean piglinRidable = false; ++ public boolean piglinRidableInWater = false; ++ private void piglinSettings() { ++ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); ++ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); ++ } ++ ++ public boolean piglinBruteRidable = false; ++ public boolean piglinBruteRidableInWater = false; ++ private void piglinBruteSettings() { ++ piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable); ++ piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater); ++ } ++ ++ public boolean pillagerRidable = false; ++ public boolean pillagerRidableInWater = false; ++ private void pillagerSettings() { ++ pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); ++ pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); ++ } ++ ++ public boolean polarBearRidable = false; ++ public boolean polarBearRidableInWater = false; + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; + private void polarBearSettings() { ++ polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); ++ polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); + polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); + Item item = IRegistry.ITEM.get(new MinecraftKey(polarBearBreedableItemString)); + if (item != Items.AIR) polarBearBreedableItem = item; + } + ++ public boolean pufferfishRidable = false; ++ private void pufferfishSettings() { ++ pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); ++ } ++ ++ public boolean rabbitRidable = false; ++ public boolean rabbitRidableInWater = false; + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + private void rabbitSettings() { ++ rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); ++ rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + } + ++ public boolean ravagerRidable = false; ++ public boolean ravagerRidableInWater = false; ++ private void ravagerSettings() { ++ ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); ++ ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); ++ } ++ ++ public boolean salmonRidable = false; ++ private void salmonSettings() { ++ salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); ++ } ++ ++ public boolean sheepRidable = false; ++ public boolean sheepRidableInWater = false; ++ private void sheepSettings() { ++ sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); ++ sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); ++ } ++ ++ public boolean shulkerRidable = false; ++ public boolean shulkerRidableInWater = false; ++ private void shulkerSettings() { ++ shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); ++ shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); ++ } ++ ++ public boolean silverfishRidable = false; ++ public boolean silverfishRidableInWater = false; ++ private void silverfishSettings() { ++ silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); ++ silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); ++ } ++ ++ public boolean skeletonRidable = false; ++ public boolean skeletonRidableInWater = false; ++ private void skeletonSettings() { ++ skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); ++ skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); ++ } ++ ++ public boolean skeletonHorseCanSwim = false; ++ public boolean skeletonHorseRidableInWater = true; ++ private void skeletonHorseSettings() { ++ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); ++ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); ++ } ++ ++ public boolean slimeRidable = false; ++ public boolean slimeRidableInWater = false; ++ private void slimeSettings() { ++ slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); ++ slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); ++ } ++ ++ public boolean snowGolemRidable = false; ++ public boolean snowGolemRidableInWater = false; ++ public boolean snowGolemLeaveTrailWhenRidden = false; + public boolean snowGolemDropsPumpkin = false; + public boolean snowGolemPutPumpkinBack = false; + private void snowGolemSettings() { ++ snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); ++ snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); ++ snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden); + snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); + snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); + } + ++ public boolean squidRidable = false; + public boolean squidImmuneToEAR = true; + public double squidOffsetWaterCheck = 0.0D; + private void squidSettings() { ++ squidRidable = getBoolean("mobs.squid.ridable", squidRidable); + squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); + squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); + } + ++ public boolean spiderRidable = false; ++ public boolean spiderRidableInWater = false; ++ private void spiderSettings() { ++ spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable); ++ spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater); ++ } ++ ++ public boolean strayRidable = false; ++ public boolean strayRidableInWater = false; ++ private void straySettings() { ++ strayRidable = getBoolean("mobs.stray.ridable", strayRidable); ++ strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater); ++ } ++ ++ public boolean striderRidable = false; ++ public boolean striderRidableInWater = false; ++ private void striderSettings() { ++ striderRidable = getBoolean("mobs.strider.ridable", striderRidable); ++ striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); ++ } ++ ++ public boolean tropicalFishRidable = false; ++ private void tropicalFishSettings() { ++ tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable); ++ } ++ ++ public boolean turtleRidable = false; ++ public boolean turtleRidableInWater = false; ++ private void turtleSettings() { ++ turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); ++ turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); ++ } ++ ++ public boolean vexRidable = false; ++ public boolean vexRidableInWater = false; ++ public double vexMaxY = 256D; ++ private void vexSettings() { ++ vexRidable = getBoolean("mobs.vex.ridable", vexRidable); ++ vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater); ++ vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY); ++ } ++ ++ public boolean villagerRidable = false; ++ public boolean villagerRidableInWater = false; + public int villagerBrainTicks = 1; + public boolean villagerUseBrainTicksOnlyWhenLagging = true; + public boolean villagerCanBeLeashed = false; +@@ -504,6 +865,8 @@ public class PurpurWorldConfig { + public int villagerSpawnIronGolemLimit = 0; + public boolean villagerCanBreed = true; + private void villagerSettings() { ++ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); ++ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); + villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); + villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); + villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); +@@ -514,45 +877,108 @@ public class PurpurWorldConfig { + villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); + } + ++ public boolean villagerTraderRidable = false; ++ public boolean villagerTraderRidableInWater = false; + public boolean villagerTraderCanBeLeashed = false; + public boolean villagerTraderFollowEmeraldBlock = false; + private void villagerTraderSettings() { ++ villagerTraderRidable = getBoolean("mobs.wandering_trader.ridable", villagerTraderRidable); ++ villagerTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", villagerTraderRidableInWater); + villagerTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", villagerTraderCanBeLeashed); + villagerTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", villagerTraderFollowEmeraldBlock); + } + ++ public boolean vindicatorRidable = false; ++ public boolean vindicatorRidableInWater = false; ++ public double vindicatorJohnnySpawnChance = 0D; ++ private void vindicatorSettings() { ++ vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); ++ vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); ++ vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); ++ } ++ ++ public boolean witchRidable = false; ++ public boolean witchRidableInWater = false; ++ private void witchSettings() { ++ witchRidable = getBoolean("mobs.witch.ridable", witchRidable); ++ witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater); ++ } ++ ++ public boolean witherRidable = false; ++ public boolean witherRidableInWater = false; ++ public double witherMaxY = 256D; ++ private void witherSettings() { ++ witherRidable = getBoolean("mobs.wither.ridable", witherRidable); ++ witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); ++ witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY); ++ } ++ ++ public boolean witherSkeletonRidable = false; ++ public boolean witherSkeletonRidableInWater = false; + public boolean witherSkeletonTakesWitherDamage = false; + private void witherSkeletonSettings() { ++ witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable); ++ witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater); + witherSkeletonTakesWitherDamage = getBoolean("mobs.wither_skeleton.takes-wither-damage", witherSkeletonTakesWitherDamage); + } + ++ public boolean wolfRidable = false; ++ public boolean wolfRidableInWater = false; ++ private void wolfSettings() { ++ wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); ++ wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); ++ } ++ ++ public boolean zoglinRidable = false; ++ public boolean zoglinRidableInWater = false; ++ private void zoglinSettings() { ++ zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable); ++ zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater); ++ } ++ ++ public boolean zombieRidable = false; ++ public boolean zombieRidableInWater = false; + public boolean zombieJockeyOnlyBaby = true; + public double zombieJockeyChance = 0.05D; + public boolean zombieJockeyTryExistingChickens = true; + private void zombieSettings() { ++ zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); ++ zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); + zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); + zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); + zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); + } + ++ public boolean zombieHorseCanSwim = false; ++ public boolean zombieHorseRidableInWater = false; + public double zombieHorseSpawnChance = 0.0D; + private void zombieHorseSettings() { ++ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); ++ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); + } + ++ public boolean zombifiedPiglinRidable = false; ++ public boolean zombifiedPiglinRidableInWater = false; + public boolean zombifiedPiglinJockeyOnlyBaby = true; + public double zombifiedPiglinJockeyChance = 0.05D; + public boolean zombifiedPiglinJockeyTryExistingChickens = true; + private void zombifiedPiglinSettings() { ++ zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); ++ zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); + zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); + zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); + zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); + } + ++ public boolean zombieVillagerRidable = false; ++ public boolean zombieVillagerRidableInWater = false; + public boolean zombieVillagerJockeyOnlyBaby = true; + public double zombieVillagerJockeyChance = 0.05D; + public boolean zombieVillagerJockeyTryExistingChickens = true; + private void zombieVillagerSettings() { ++ zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); ++ zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); + zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); + zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); + zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); +diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b16a7b6345ff42ea0f42ca79155a50e2fe4926b +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/controller/ControllerLookWASD.java +@@ -0,0 +1,76 @@ ++package net.pl3x.purpur.controller; ++ ++import net.minecraft.server.ControllerLook; ++import net.minecraft.server.EntityHuman; ++import net.minecraft.server.EntityInsentient; ++import net.minecraft.server.MathHelper; ++import net.minecraft.server.PacketPlayOutEntity; ++ ++public class ControllerLookWASD extends ControllerLook { ++ protected final EntityInsentient entity; ++ private float yawOffset = 0; ++ private float pitchOffset = 0; ++ ++ public ControllerLookWASD(EntityInsentient entity) { ++ super(entity); ++ this.entity = entity; ++ } ++ ++ // tick ++ @Override ++ public void a() { ++ if (entity.hasRider()) { ++ tick(entity.getRider()); ++ } else { ++ tick(); ++ } ++ } ++ ++ protected void tick() { ++ super.a(); // tick ++ } ++ ++ protected void tick(EntityHuman rider) { ++ setYawPitch(rider.yaw, rider.pitch); ++ } ++ ++ public void setYawPitch(float yaw, float pitch) { ++ entity.yaw = normalizeYaw(yaw + yawOffset); ++ entity.lastYaw = entity.yaw; ++ entity.setBodyYaw(entity.yaw); ++ entity.setRenderYawOffset(entity.yaw); ++ entity.setHeadRotation(entity.yaw); ++ entity.pitch = normalizePitch(pitch + pitchOffset); ++ ++ entity.getTracker().broadcast(new PacketPlayOutEntity ++ .PacketPlayOutRelEntityMoveLook(entity.getId(), ++ (short) 0, (short) 0, (short) 0, ++ (byte) MathHelper.d(entity.yaw * 256.0F / 360.0F), ++ (byte) MathHelper.d(entity.pitch * 256.0F / 360.0F), ++ entity.onGround)); ++ } ++ ++ public void setOffsets(float yaw, float pitch) { ++ yawOffset = yaw; ++ pitchOffset = pitch; ++ } ++ ++ public float normalizeYaw(float yaw) { ++ yaw %= 360.0f; ++ if (yaw >= 180.0f) { ++ yaw -= 360.0f; ++ } else if (yaw < -180.0f) { ++ yaw += 360.0f; ++ } ++ return yaw; ++ } ++ ++ public float normalizePitch(float pitch) { ++ if (pitch > 90.0f) { ++ pitch = 90.0f; ++ } else if (pitch < -90.0f) { ++ pitch = -90.0f; ++ } ++ return pitch; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASD.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASD.java +new file mode 100644 +index 0000000000000000000000000000000000000000..426688b3a6dc197b41ddc4d1efed0405614f0f3b +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASD.java +@@ -0,0 +1,92 @@ ++package net.pl3x.purpur.controller; ++ ++import net.minecraft.server.ControllerMove; ++import net.minecraft.server.Entity; ++import net.minecraft.server.EntityHuman; ++import net.minecraft.server.EntityInsentient; ++import net.minecraft.server.GenericAttributes; ++import net.pl3x.purpur.event.entity.RidableSpacebarEvent; ++ ++public class ControllerMoveWASD extends ControllerMove { ++ protected final EntityInsentient entity; ++ private final double speedModifier; ++ ++ public ControllerMoveWASD(EntityInsentient entity) { ++ this(entity, 1.0D); ++ } ++ ++ public ControllerMoveWASD(EntityInsentient entity, double speedModifier) { ++ super(entity); ++ this.entity = entity; ++ this.speedModifier = speedModifier; ++ } ++ ++ // isUpdating ++ @Override ++ public boolean b() { ++ return entity.hasRider() ? getForward() != 0 || getStrafe() != 0 : super.b(); ++ } ++ ++ // tick ++ @Override ++ public void a() { ++ if (entity.hasRider()) { ++ tick(entity.getRider()); ++ } else { ++ tick(); ++ } ++ } ++ ++ public void tick() { ++ super.a(); // tick ++ } ++ ++ public void tick(EntityHuman rider) { ++ float forward = rider.getForward() * 0.5F; ++ float strafe = rider.getStrafe() * 0.25F; ++ ++ if (forward <= 0.0F) { ++ forward *= 0.5F; ++ } ++ ++ float yawOffset = 0; ++ if (strafe != 0) { ++ if (forward == 0) { ++ yawOffset += strafe > 0 ? -90 : 90; ++ forward = Math.abs(strafe * 2); ++ } else { ++ yawOffset += strafe > 0 ? -30 : 30; ++ strafe /= 2; ++ if (forward < 0) { ++ yawOffset += strafe > 0 ? -110 : 110; ++ forward *= -1; ++ } ++ } ++ } else if (forward < 0) { ++ yawOffset -= 180; ++ forward *= -1; ++ } ++ ++ ((ControllerLookWASD) entity.getControllerLook()).setOffsets(yawOffset, 0); ++ ++ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) { ++ entity.jump(); ++ } ++ ++ setSpeed(entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * speedModifier); ++ ++ entity.setSpeed((float) getSpeed()); ++ entity.setForward(forward); ++ ++ setForward(entity.getForward()); ++ setStrafe(entity.getStrafe()); ++ } ++ ++ public static boolean spacebarEvent(Entity entity) { ++ if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent(); ++ } else { ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlying.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlying.java +new file mode 100644 +index 0000000000000000000000000000000000000000..33c51460ab9556e5574c99232e9e3ff843c6ccc9 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlying.java +@@ -0,0 +1,61 @@ ++package net.pl3x.purpur.controller; ++ ++import net.minecraft.server.EntityHuman; ++import net.minecraft.server.EntityInsentient; ++import net.minecraft.server.GenericAttributes; ++ ++public class ControllerMoveWASDFlying extends ControllerMoveWASD { ++ protected final float groundSpeedModifier; ++ protected int tooHighCooldown = 0; ++ protected boolean setGravityFlag = true; ++ ++ public ControllerMoveWASDFlying(EntityInsentient entity) { ++ this(entity, 1.0F); ++ } ++ ++ public ControllerMoveWASDFlying(EntityInsentient entity, float groundSpeedModifier) { ++ this(entity, groundSpeedModifier, true); ++ } ++ ++ public ControllerMoveWASDFlying(EntityInsentient entity, float groundSpeedModifier, boolean setGravityFlag) { ++ super(entity); ++ this.groundSpeedModifier = groundSpeedModifier; ++ this.setGravityFlag = setGravityFlag; ++ } ++ ++ @Override ++ public void tick(EntityHuman rider) { ++ float forward = Math.max(0.0F, rider.getForward()); ++ float vertical = forward == 0.0F ? 0.0F : -(rider.pitch / 45.0F); ++ float strafe = rider.getStrafe(); ++ ++ if (rider.jumping && spacebarEvent(entity)) { ++ entity.onSpacebar(); ++ } ++ ++ if (entity.locY() >= entity.getMaxY() || --tooHighCooldown > 0) { ++ tooHighCooldown = 60; ++ entity.setMot(entity.getMot().add(0.0D, -0.05D, 0.0D)); ++ vertical = 0.0F; ++ } ++ ++ setSpeed(entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue()); ++ float speed = (float) getSpeed(); ++ ++ if (entity.onGround) { ++ speed *= groundSpeedModifier; // TODO = fix this! ++ } ++ ++ if (setGravityFlag) { ++ entity.setNoGravity(forward > 0); ++ } ++ ++ entity.setSpeed(speed); ++ entity.setVertical(vertical); ++ entity.setStrafe(strafe); ++ entity.setForward(forward); ++ ++ setForward(entity.getForward()); ++ setStrafe(entity.getStrafe()); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlyingWithSpacebar.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlyingWithSpacebar.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f7537593619a68eb3898f28034192fab8548655c +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDFlyingWithSpacebar.java +@@ -0,0 +1,61 @@ ++package net.pl3x.purpur.controller; ++ ++import net.minecraft.server.EntityHuman; ++import net.minecraft.server.EntityInsentient; ++import net.minecraft.server.GenericAttributes; ++import net.minecraft.server.Vec3D; ++ ++public class ControllerMoveWASDFlyingWithSpacebar extends ControllerMoveWASDFlying { ++ public ControllerMoveWASDFlyingWithSpacebar(EntityInsentient entity) { ++ super(entity); ++ } ++ ++ public ControllerMoveWASDFlyingWithSpacebar(EntityInsentient entity, float groundSpeedModifier) { ++ super(entity, groundSpeedModifier); ++ } ++ ++ @Override ++ public void tick(EntityHuman rider) { ++ float forward = rider.getForward(); ++ float strafe = rider.getStrafe() * 0.5F; ++ float vertical = 0; ++ ++ if (forward < 0.0F) { ++ forward *= 0.5F; ++ strafe *= 0.5F; ++ } ++ ++ float speed = (float) entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue(); ++ ++ if (entity.onGround) { ++ speed *= groundSpeedModifier; ++ } ++ ++ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar()) { ++ entity.setNoGravity(true); ++ vertical = 1.0F; ++ } else { ++ entity.setNoGravity(false); ++ } ++ ++ if (entity.locY() >= entity.getMaxY() || --tooHighCooldown > 0) { ++ tooHighCooldown = 60; ++ entity.setMot(entity.getMot().add(0.0D, -0.2D, 0.0D)); ++ vertical = 0.0F; ++ } ++ ++ setSpeed(speed); ++ entity.setSpeed((float) getSpeed()); ++ entity.setVertical(vertical); ++ entity.setStrafe(strafe); ++ entity.setForward(forward); ++ ++ setForward(entity.getForward()); ++ setStrafe(entity.getStrafe()); ++ ++ Vec3D mot = entity.getMot(); ++ if (mot.y > 0.2D) { ++ entity.setMot(mot.x, 0.2D, mot.z); ++ } ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDWater.java b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDWater.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1b08a0905b296b989e0ef0ea3b15169759b5f2fe +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/controller/ControllerMoveWASDWater.java +@@ -0,0 +1,50 @@ ++package net.pl3x.purpur.controller; ++ ++import net.minecraft.server.EntityHuman; ++import net.minecraft.server.EntityInsentient; ++import net.minecraft.server.GenericAttributes; ++ ++public class ControllerMoveWASDWater extends ControllerMoveWASD { ++ private final double speedModifier; ++ ++ public ControllerMoveWASDWater(EntityInsentient entity) { ++ this(entity, 1.0D); ++ } ++ ++ public ControllerMoveWASDWater(EntityInsentient entity, double speedModifier) { ++ super(entity); ++ this.speedModifier = speedModifier; ++ } ++ ++ @Override ++ public void tick(EntityHuman rider) { ++ float forward = rider.getForward(); ++ float strafe = rider.getStrafe() * 0.5F; // strafe slower by default ++ float vertical = -(rider.pitch / 90); ++ ++ if (forward == 0.0F) { ++ // strafe slower if not moving forward ++ strafe *= 0.5F; ++ // do not move vertically if not moving forward ++ vertical = 0.0F; ++ } else if (forward < 0.0F) { ++ // water animals can't swim backwards ++ forward = 0.0F; ++ vertical = 0.0F; ++ } ++ ++ if (rider.jumping && spacebarEvent(entity)) { ++ entity.onSpacebar(); ++ } ++ ++ setSpeed(entity.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getValue() * speedModifier); ++ entity.setSpeed((float) getSpeed() * 0.1F); ++ ++ entity.setForward(forward * (float) speedModifier); ++ entity.setStrafe(strafe * (float) speedModifier); ++ entity.setVertical(vertical * (float) speedModifier); ++ ++ setForward(entity.getForward()); ++ setStrafe(entity.getStrafe()); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/entity/DolphinSpit.java b/src/main/java/net/pl3x/purpur/entity/DolphinSpit.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a01524c6abaec13d7249d7aba6da9e4bc39f8b99 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/entity/DolphinSpit.java +@@ -0,0 +1,120 @@ ++package net.pl3x.purpur.entity; ++ ++import net.minecraft.server.BlockBase; ++import net.minecraft.server.DamageSource; ++import net.minecraft.server.Entity; ++import net.minecraft.server.EntityDolphin; ++import net.minecraft.server.EntityLiving; ++import net.minecraft.server.EntityLlamaSpit; ++import net.minecraft.server.EntityTypes; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.MathHelper; ++import net.minecraft.server.MovingObjectPosition; ++import net.minecraft.server.MovingObjectPositionBlock; ++import net.minecraft.server.MovingObjectPositionEntity; ++import net.minecraft.server.Particles; ++import net.minecraft.server.ProjectileHelper; ++import net.minecraft.server.Vec3D; ++import net.minecraft.server.World; ++import net.minecraft.server.WorldServer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++ ++public class DolphinSpit extends EntityLlamaSpit { ++ public EntityLiving dolphin; ++ public int ticksLived; ++ ++ public DolphinSpit(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ public DolphinSpit(World world, EntityDolphin dolphin) { ++ this(EntityTypes.LLAMA_SPIT, world); ++ setShooter(dolphin.hasRider() ? dolphin.getRider() : dolphin); ++ this.dolphin = dolphin; ++ this.setPosition( ++ dolphin.locX() - (double) (dolphin.getWidth() + 1.0F) * 0.5D * (double) MathHelper.sin(dolphin.getRenderYawOffset() * 0.017453292F), ++ dolphin.getHeadY() - 0.10000000149011612D, ++ dolphin.locZ() + (double) (dolphin.getWidth() + 1.0F) * 0.5D * (double) MathHelper.cos(dolphin.getRenderYawOffset() * 0.017453292F)); ++ } ++ ++ @Override ++ public boolean canSaveToDisk() { ++ return false; ++ } ++ ++ public void tick() { ++ if (dead || !valid) { ++ return; ++ } ++ ++ if (!leftOwner()) { ++ setLeftOwner(checkIfLeftOwner()); ++ } ++ ++ setFlag(6, isGlowing()); ++ entityBaseTick(); ++ ++ Vec3D mot = getMot(); ++ ++ MovingObjectPosition hitResult = ProjectileHelper.getHitResult(this, this::hitPredicate); ++ if (hitResult != null) { ++ onHit(hitResult); ++ } ++ ++ double x = this.locX() + mot.x; ++ double y = this.locY() + mot.y; ++ double z = this.locZ() + mot.z; ++ setMot(mot.scale(0.99D)); ++ ++ Vec3D motDouble = mot.scale(2.0); ++ for (int i = 0; i < 5; i++) { ++ ((WorldServer) world).sendParticles(null, Particles.BUBBLE, ++ locX() + random.nextFloat() / 2 - 0.25F, ++ locY() + random.nextFloat() / 2 - 0.25F, ++ locZ() + random.nextFloat() / 2 - 0.25F, ++ 0, motDouble.getX(), motDouble.getY(), motDouble.getZ(), 0.1, true); ++ } ++ ++ if (++ticksLived > 20) { ++ die(); ++ } else { ++ setMot(mot.scale(0.99D)); ++ if (!isNoGravity()) { ++ setMot(getMot().add(0.0D, -0.06D, 0.0D)); ++ } ++ setPosition(x, y, z); ++ } ++ } ++ ++ @Override ++ public void shoot(double x, double y, double z, float speed, float inaccuracy) { ++ setMot(new Vec3D(x, y, z).normalize().add( ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) ++ .scale(speed)); ++ } ++ ++ public void onHit(MovingObjectPosition rayTrace) { ++ CraftEventFactory.callProjectileHitEvent(this, rayTrace); ++ MovingObjectPosition.EnumMovingObjectType type = rayTrace.getType(); ++ if (type == MovingObjectPosition.EnumMovingObjectType.ENTITY) { ++ onHit((MovingObjectPositionEntity) rayTrace); ++ } else if (type == MovingObjectPosition.EnumMovingObjectType.BLOCK) { ++ onHit((MovingObjectPositionBlock) rayTrace); ++ } ++ } ++ ++ protected void onHit(MovingObjectPositionEntity rayTrace) { ++ Entity shooter = getShooter(); ++ if (shooter instanceof EntityLiving) { ++ rayTrace.getEntity().damageEntity(DamageSource.indirectMobAttack(this, (EntityLiving) shooter).setProjectile(), world.purpurConfig.dolphinSpitDamage); ++ } ++ } ++ ++ protected void onHit(MovingObjectPositionBlock rayTrace) { ++ IBlockData iblockdata = world.getType(rayTrace.getBlockPosition()); ++ iblockdata.a(world, iblockdata, rayTrace, this); ++ die(); ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/entity/PhantomFlames.java b/src/main/java/net/pl3x/purpur/entity/PhantomFlames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3059078c37deb35fcd20e27767f9b79503802cf4 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/entity/PhantomFlames.java +@@ -0,0 +1,128 @@ ++package net.pl3x.purpur.entity; ++ ++import net.minecraft.server.BlockBase; ++import net.minecraft.server.DamageSource; ++import net.minecraft.server.Entity; ++import net.minecraft.server.EntityLiving; ++import net.minecraft.server.EntityLlamaSpit; ++import net.minecraft.server.EntityPhantom; ++import net.minecraft.server.EntityTypes; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.MathHelper; ++import net.minecraft.server.MovingObjectPosition; ++import net.minecraft.server.MovingObjectPositionBlock; ++import net.minecraft.server.MovingObjectPositionEntity; ++import net.minecraft.server.Particles; ++import net.minecraft.server.ProjectileHelper; ++import net.minecraft.server.Vec3D; ++import net.minecraft.server.World; ++import net.minecraft.server.WorldServer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++ ++public class PhantomFlames extends EntityLlamaSpit { ++ public EntityPhantom phantom; ++ public int ticksLived; ++ ++ public PhantomFlames(EntityTypes entitytypes, World world) { ++ super(entitytypes, world); ++ } ++ ++ public PhantomFlames(World world, EntityPhantom phantom) { ++ this(EntityTypes.LLAMA_SPIT, world); ++ setShooter(phantom.hasRider() ? phantom.getRider() : phantom); ++ this.phantom = phantom; ++ this.setPosition( ++ phantom.locX() - (double) (phantom.getWidth() + 1.0F) * 0.5D * (double) MathHelper.sin(phantom.getRenderYawOffset() * 0.017453292F), ++ phantom.getHeadY() - 0.10000000149011612D, ++ phantom.locZ() + (double) (phantom.getWidth() + 1.0F) * 0.5D * (double) MathHelper.cos(phantom.getRenderYawOffset() * 0.017453292F)); ++ } ++ ++ @Override ++ public boolean canSaveToDisk() { ++ return false; ++ } ++ ++ @Override ++ public void tick() { ++ if (dead || !valid) { ++ return; ++ } ++ ++ if (!leftOwner()) { ++ setLeftOwner(checkIfLeftOwner()); ++ } ++ ++ setFlag(6, isGlowing()); ++ entityBaseTick(); ++ ++ Vec3D mot = getMot(); ++ ++ MovingObjectPosition hitResult = ProjectileHelper.getHitResult(this, this::hitPredicate); ++ if (hitResult != null) { ++ onHit(hitResult); ++ } ++ ++ double x = this.locX() + mot.x; ++ double y = this.locY() + mot.y; ++ double z = this.locZ() + mot.z; ++ setMot(mot.scale(0.99D)); ++ ++ Vec3D motDouble = mot.scale(2.0); ++ for (int i = 0; i < 5; i++) { ++ ((WorldServer) world).sendParticles(null, Particles.FLAME, ++ locX() + random.nextFloat() / 2 - 0.25F, ++ locY() + random.nextFloat() / 2 - 0.25F, ++ locZ() + random.nextFloat() / 2 - 0.25F, ++ 0, motDouble.getX(), motDouble.getY(), motDouble.getZ(), 0.1, true); ++ } ++ ++ if (world.a(getBoundingBox()).noneMatch(BlockBase.BlockData::isAir)) { ++ die(); ++ } else if (isInWaterOrBubbleColumn()) { ++ die(); ++ } else if (++ticksLived > 20) { ++ die(); ++ } else { ++ setMot(mot.scale(0.99D)); ++ if (!isNoGravity()) { ++ setMot(getMot().add(0.0D, -0.06D, 0.0D)); ++ } ++ setPosition(x, y, z); ++ } ++ } ++ ++ @Override ++ public void shoot(double x, double y, double z, float speed, float inaccuracy) { ++ setMot(new Vec3D(x, y, z).normalize().add( ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, ++ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) ++ .scale(speed)); ++ } ++ ++ public void onHit(MovingObjectPosition rayTrace) { ++ CraftEventFactory.callProjectileHitEvent(this, rayTrace); ++ MovingObjectPosition.EnumMovingObjectType type = rayTrace.getType(); ++ if (type == MovingObjectPosition.EnumMovingObjectType.ENTITY) { ++ onHit((MovingObjectPositionEntity) rayTrace); ++ } else if (type == MovingObjectPosition.EnumMovingObjectType.BLOCK) { ++ onHit((MovingObjectPositionBlock) rayTrace); ++ } ++ } ++ ++ protected void onHit(MovingObjectPositionEntity rayTrace) { ++ Entity shooter = getShooter(); ++ if (shooter instanceof EntityLiving) { ++ rayTrace.getEntity().damageEntity(DamageSource.indirectMobAttack(this, (EntityLiving) shooter).setProjectile(), world.purpurConfig.phantomFlameDamage); ++ if (world.purpurConfig.phantomFlameFireTime > 0) { ++ rayTrace.getEntity().setOnFire(world.purpurConfig.phantomFlameFireTime); ++ } ++ } ++ } ++ ++ protected void onHit(MovingObjectPositionBlock rayTrace) { ++ IBlockData iblockdata = world.getType(rayTrace.getBlockPosition()); ++ iblockdata.a(world, iblockdata, rayTrace, this); ++ die(); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index a6d849facba1526ae2a2b7f3fb9a140d0b50289c..b56ca054b37f5887e13b481baad8132f1d28638b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1160,4 +1160,26 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().isTicking(); + } + // Paper end ++ ++ // Purpur start ++ @Override ++ public org.bukkit.entity.Player getRider() { ++ return hasRider() ? (org.bukkit.entity.Player) getHandle().getRider().getBukkitEntity() : null; ++ } ++ ++ @Override ++ public boolean hasRider() { ++ return getHandle().hasRider(); ++ } ++ ++ @Override ++ public boolean isRidable() { ++ return getHandle().isRidable(); ++ } ++ ++ @Override ++ public boolean isRidableInWater() { ++ return getHandle().isRidableInWater(); ++ } ++ // Purpur end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 696ded96e6018bc5289cc20f72d6dc9395d3b6a6..55ab5e078d28de962ec450c575c7f5bb4fa7ce6f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -510,6 +510,18 @@ public class CraftEventFactory { + } + craftServer.getPluginManager().callEvent(event); + ++ // Purpur start ++ switch (action) { ++ case LEFT_CLICK_BLOCK: ++ case LEFT_CLICK_AIR: ++ who.processClick(EnumHand.MAIN_HAND); ++ break; ++ case RIGHT_CLICK_BLOCK: ++ case RIGHT_CLICK_AIR: ++ who.processClick(EnumHand.OFF_HAND); ++ } ++ // Purpur end ++ + return event; + } + +@@ -910,6 +922,7 @@ public class CraftEventFactory { + damageCause = DamageCause.ENTITY_EXPLOSION; + } + event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions); ++ damager.processClick(EnumHand.MAIN_HAND); // Purpur + } + event.setCancelled(cancelled); + +@@ -994,6 +1007,7 @@ public class CraftEventFactory { + if (!event.isCancelled()) { + event.getEntity().setLastDamageCause(event); + } ++ damager.getHandle().processClick(EnumHand.MAIN_HAND); // Purpur + return event; + } + +@@ -1043,6 +1057,7 @@ public class CraftEventFactory { + EntityDamageEvent event; + if (damager != null) { + event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); ++ damager.processClick(EnumHand.MAIN_HAND); // Purpur + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); + } diff --git a/patches/Purpur/patches/server/0108-Use-configured-height-for-nether-surface-builders.patch b/patches/Purpur/patches/server/0108-Use-configured-height-for-nether-surface-builders.patch new file mode 100644 index 00000000..fb5a5fb7 --- /dev/null +++ b/patches/Purpur/patches/server/0108-Use-configured-height-for-nether-surface-builders.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 15 Aug 2020 06:51:46 -0500 +Subject: [PATCH] Use configured height for nether surface builders + + +diff --git a/src/main/java/net/minecraft/server/WorldGenSurfaceNetherAbstract.java b/src/main/java/net/minecraft/server/WorldGenSurfaceNetherAbstract.java +index 462f0b2baea4207d7d82f2d4e043a5a7999a1f42..f418a71ca8ec5e1417778d33c9020dc002a611cc 100644 +--- a/src/main/java/net/minecraft/server/WorldGenSurfaceNetherAbstract.java ++++ b/src/main/java/net/minecraft/server/WorldGenSurfaceNetherAbstract.java +@@ -35,7 +35,7 @@ public abstract class WorldGenSurfaceNetherAbstract extends WorldGenSurface= 0; --k2) { // Paper - fix MC-187716 - use configured height + blockposition_mutableblockposition.d(k1, k2, l1); diff --git a/patches/Purpur/patches/server/0109-Crying-obsidian-valid-for-portal-frames.patch b/patches/Purpur/patches/server/0109-Crying-obsidian-valid-for-portal-frames.patch new file mode 100644 index 00000000..8d9ccefa --- /dev/null +++ b/patches/Purpur/patches/server/0109-Crying-obsidian-valid-for-portal-frames.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 17 Aug 2020 17:34:33 -0500 +Subject: [PATCH] Crying obsidian valid for portal frames + + +diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java +index 1fc98698b81c079ebe4a524200232db1fe143bdf..d621b11ba9029a732a819e0558d6f8a439990dbe 100644 +--- a/src/main/java/net/minecraft/server/Block.java ++++ b/src/main/java/net/minecraft/server/Block.java +@@ -100,6 +100,7 @@ public class Block extends BlockBase implements IMaterial { + return tag.isTagged(this); + } + ++ public boolean equals(Block block) { return a(block); } // Purpur - OBFHELPER + public boolean a(Block block) { + return this == block; + } +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index 6aea156d7c7a9ca8a357aad6a6781d7209c9b8ae..b40f5167d2a9772658c115091f13706fbb4959b7 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -687,6 +687,7 @@ public abstract class BlockBase { + return this.getBlock().a(tag) && predicate.test(this); + } + ++ public boolean equals(Block block) { return a(block); } // Purpur - OBFHELPER + public boolean a(Block block) { + return this.getBlock().a(block); + } +diff --git a/src/main/java/net/minecraft/server/BlockPortalShape.java b/src/main/java/net/minecraft/server/BlockPortalShape.java +index 6ef81aeb4c63bc6c23163796dbd977602ca2f540..9ea3c30b679da4e77b86d96d0cc476732040f184 100644 +--- a/src/main/java/net/minecraft/server/BlockPortalShape.java ++++ b/src/main/java/net/minecraft/server/BlockPortalShape.java +@@ -14,7 +14,7 @@ import org.bukkit.event.world.PortalCreateEvent; + public class BlockPortalShape { + + private static final BlockBase.e a = (iblockdata, iblockaccess, blockposition) -> { +- return iblockdata.a(Blocks.OBSIDIAN); ++ return iblockdata.equals(Blocks.OBSIDIAN) || (net.pl3x.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && iblockdata.equals(Blocks.CRYING_OBSIDIAN)); // Purpur + }; + private final GeneratorAccess b; + private final EnumDirection.EnumAxis c; +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 59f3122aab9940cb3c3c1efb2664ab0835656d48..a5febd7671ae1818edb18955d5e8176303c92c93 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -179,6 +179,7 @@ public class PurpurConfig { + public static boolean barrelSixRows = false; + public static boolean enderChestSixRows = false; + public static boolean enderChestPermissionRows = false; ++ public static boolean cryingObsidianValidForPortalFrame = false; + private static void blockSettings() { + if (version < 3) { + boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); +@@ -193,6 +194,7 @@ public class PurpurConfig { + enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); + InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); ++ cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame); + } + + public static boolean endermanShortHeight = false; diff --git a/patches/Purpur/patches/server/0110-Entities-can-use-portals-configuration.patch b/patches/Purpur/patches/server/0110-Entities-can-use-portals-configuration.patch new file mode 100644 index 00000000..e3531bb1 --- /dev/null +++ b/patches/Purpur/patches/server/0110-Entities-can-use-portals-configuration.patch @@ -0,0 +1,48 @@ +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 + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 0115e2c73eff9d5e4c6778e32fc54b9c116b6b22..e4f2e51b6306fcaf161b7dfb734d9d28947e964b 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -2414,7 +2414,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + public void d(BlockPosition blockposition) { + if (this.ai()) { + this.resetPortalCooldown(); +- } else { ++ } else if (world.purpurConfig.entitiesCanUsePortals || this instanceof EntityPlayer) { // Purpur + if (!this.world.isClientSide && !blockposition.equals(this.ac)) { + this.ac = blockposition.immutableCopy(); + } +@@ -2994,7 +2994,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + + public boolean canPortal() { +- return isAlive() && valid; // Paper ++ return isAlive() && valid && (world.purpurConfig.entitiesCanUsePortals || this instanceof EntityPlayer); // Paper // Purpur + } + + public float a(Explosion explosion, IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, float f) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 2644fbcaa8be55b0c09b79baa0727d04d87a70e2..012246fa0dc596ce13819a33438940c52672e97a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -146,6 +146,7 @@ public class PurpurWorldConfig { + public boolean boatEjectPlayersOnLand = false; + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesPickUpLootBypassMobGriefing = false; ++ public boolean entitiesCanUsePortals = true; + public boolean milkCuresBadOmen = true; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; +@@ -154,6 +155,7 @@ public class PurpurWorldConfig { + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); ++ entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); diff --git a/patches/Purpur/patches/server/0111-LivingEntity-broadcastItemBreak.patch b/patches/Purpur/patches/server/0111-LivingEntity-broadcastItemBreak.patch new file mode 100644 index 00000000..57899e54 --- /dev/null +++ b/patches/Purpur/patches/server/0111-LivingEntity-broadcastItemBreak.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 17 Aug 2020 21:50:39 -0500 +Subject: [PATCH] LivingEntity#broadcastItemBreak + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 0292cae6225ae2ee156f436c8827a018e8ffa723..9f5802364d4098173be2d08893efa697033407e9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -837,5 +837,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setSafeFallDistance(float safeFallDistance) { + getHandle().safeFallDistance = safeFallDistance; + } ++ ++ @Override ++ public void broadcastItemBreak(org.bukkit.inventory.EquipmentSlot slot) { ++ if (slot == null) return; ++ getHandle().broadcastItemBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); ++ } + // Purpur end + } diff --git a/patches/Purpur/patches/server/0112-Customizable-wither-health-and-healing.patch b/patches/Purpur/patches/server/0112-Customizable-wither-health-and-healing.patch new file mode 100644 index 00000000..637379fc --- /dev/null +++ b/patches/Purpur/patches/server/0112-Customizable-wither-health-and-healing.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 20 Aug 2020 17:38:12 -0700 +Subject: [PATCH] Customizable wither health and healing + +Adds the ability to customize the health of the wither, as well as the amount that it heals, and how often. + +diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java +index 0abfa34a8fa0088c2089645f54f85d4490a04bc9..510797225d50de1565288c8df484384874050172 100644 +--- a/src/main/java/net/minecraft/server/EntityWither.java ++++ b/src/main/java/net/minecraft/server/EntityWither.java +@@ -150,6 +150,11 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + skull.setPositionRaw(headX, headY, headZ); + world.addEntity(skull); + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(world.purpurConfig.witherMaxHealth); ++ } + // Purpur end + + @Override +@@ -353,7 +358,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + + this.setInvul(i); + if (this.ticksLived % 10 == 0) { +- this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit ++ this.heal(this.getMaxHealth() / 33, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur - use max health for healing instead of a constant + } + + } else { +@@ -462,8 +467,10 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + } + } + +- if (this.ticksLived % 20 == 0) { +- this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit ++ // Purpur start - customizable heal rate and amount ++ if (this.ticksLived % world.purpurConfig.witherHealthRegenDelay == 0) { ++ this.heal(world.purpurConfig.witherHealthRegenAmount, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit ++ // Purpur end + } + + //this.bossBattle.setProgress(this.getHealth() / this.getMaxHealth()); // Paper - Moved down +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 012246fa0dc596ce13819a33438940c52672e97a..8d7afc65bf8b399fa49944fb11d14168a351bb2e 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -909,10 +909,21 @@ public class PurpurWorldConfig { + public boolean witherRidable = false; + public boolean witherRidableInWater = false; + public double witherMaxY = 256D; ++ public float witherHealthRegenAmount = 1.0f; ++ public int witherHealthRegenDelay = 20; ++ public double witherMaxHealth = 300.0D; + private void witherSettings() { + witherRidable = getBoolean("mobs.wither.ridable", witherRidable); + witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); + witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY); ++ witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); ++ witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); ++ if (PurpurConfig.version < 8) { ++ double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth); ++ set("mobs.wither.attributes.max-health", oldValue); ++ set("mobs.wither.max-health", null); ++ } ++ witherMaxHealth = getDouble("mobs.wither.attributes.max-health", witherMaxHealth); + } + + public boolean witherSkeletonRidable = false; diff --git a/patches/Purpur/patches/server/0113-Allow-toggling-special-MobSpawners-per-world.patch b/patches/Purpur/patches/server/0113-Allow-toggling-special-MobSpawners-per-world.patch new file mode 100644 index 00000000..bc9fbba1 --- /dev/null +++ b/patches/Purpur/patches/server/0113-Allow-toggling-special-MobSpawners-per-world.patch @@ -0,0 +1,142 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sat, 22 Aug 2020 20:47:11 -0700 +Subject: [PATCH] Allow toggling special MobSpawners per world + +In vanilla, these are all hardcoded on for world type 0 (overworld) and hardcoded off for every other world type. Default config behaviour matches this. + +diff --git a/src/main/java/net/minecraft/server/MobSpawnerTrader.java b/src/main/java/net/minecraft/server/MobSpawnerTrader.java +index 8d89f51182444852062d549d23c00a93e601eb38..072ec40f751b19c2a78dfcc6e439c64358d864d3 100644 +--- a/src/main/java/net/minecraft/server/MobSpawnerTrader.java ++++ b/src/main/java/net/minecraft/server/MobSpawnerTrader.java +@@ -132,7 +132,17 @@ public class MobSpawnerTrader implements MobSpawner { + int k = blockposition.getX() + this.a.nextInt(i * 2) - i; + int l = blockposition.getZ() + this.a.nextInt(i * 2) - i; + int i1 = iworldreader.a(HeightMap.Type.WORLD_SURFACE, k, l); +- BlockPosition blockposition2 = new BlockPosition(k, i1, l); ++ // Purpur start - allow traders to spawn below nether roof ++ BlockPosition.MutableBlockPosition blockposition2 = new BlockPosition.MutableBlockPosition(k, i1, l); ++ if (iworldreader.getDimensionManager().hasCeiling()) { ++ do { ++ blockposition2.c(EnumDirection.DOWN); ++ } while (!iworldreader.getType(blockposition2).isAir()); ++ do { ++ blockposition2.c(EnumDirection.DOWN); ++ } while (iworldreader.getType(blockposition2).isAir() && blockposition2.getY() > 0); ++ } ++ // Purpur end + + if (SpawnerCreature.a(EntityPositionTypes.Surface.ON_GROUND, iworldreader, blockposition2, EntityTypes.WANDERING_TRADER)) { + blockposition1 = blockposition2; +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index cf1f4fe5832781df7d0bdd5eb24eff8539691c30..aa1b037c0103552761b81318f1d2ad8215bd0370 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -156,7 +156,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper + this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config +- this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((((WorldDataServer)worlddatamutable).getName())); // Purpur ++ this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig(((WorldDataServer) worlddatamutable).getName(), env); // Purpur + this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; + this.world = new CraftWorld((WorldServer) this, gen, env); +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 79ae47ffafae09059774e45b48a36818e104b036..7ee7975d5569d24c836018e240ebd785a9e8fed5 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -339,7 +339,24 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.L = new ObjectLinkedOpenHashSet(); + this.Q = flag1; + this.server = minecraftserver; +- this.mobSpawners = list; ++ // Purpur start - enable/disable MobSpawners per world ++ this.mobSpawners = new java.util.ArrayList<>(); ++ if (purpurConfig.phantomSpawning) { ++ mobSpawners.add(new MobSpawnerPhantom()); ++ } ++ if (purpurConfig.patrolSpawning) { ++ mobSpawners.add(new MobSpawnerPatrol()); ++ } ++ if (purpurConfig.catSpawning) { ++ mobSpawners.add(new MobSpawnerCat()); ++ } ++ if (purpurConfig.villageSiegeSpawning) { ++ mobSpawners.add(new VillageSiege()); ++ } ++ if (purpurConfig.villagerTraderSpawning) { ++ mobSpawners.add(new MobSpawnerTrader(iworlddataserver)); ++ } ++ // Purpur end + // CraftBukkit start + this.worldDataServer = (WorldDataServer) iworlddataserver; + worldDataServer.world = this; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 8d7afc65bf8b399fa49944fb11d14168a351bb2e..853f55b91609df363d92cf403eb76e2353d377b1 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -7,6 +7,8 @@ import net.minecraft.server.IRegistry; + import net.minecraft.server.Item; + import net.minecraft.server.Items; + import net.minecraft.server.MinecraftKey; ++import org.apache.commons.lang.BooleanUtils; ++import org.bukkit.World.Environment; + import org.bukkit.configuration.ConfigurationSection; + + import java.util.ArrayList; +@@ -15,6 +17,7 @@ import java.util.HashSet; + import java.util.List; + import java.util.Map; + import java.util.Set; ++import java.util.function.Predicate; + import java.util.logging.Level; + + import static net.pl3x.purpur.PurpurConfig.log; +@@ -22,9 +25,11 @@ import static net.pl3x.purpur.PurpurConfig.log; + public class PurpurWorldConfig { + + private final String worldName; ++ private final Environment environment; + +- public PurpurWorldConfig(String worldName) { ++ public PurpurWorldConfig(String worldName, Environment environment) { + this.worldName = worldName; ++ this.environment = environment; + init(); + } + +@@ -52,6 +57,12 @@ public class PurpurWorldConfig { + return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); + } + ++ private boolean getBoolean(String path, Predicate predicate) { ++ String val = getString(path, "default").toLowerCase(); ++ Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default"); ++ return predicate.test(bool); ++ } ++ + private double getDouble(String path, double def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path)); +@@ -161,6 +172,21 @@ public class PurpurWorldConfig { + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + } + ++ public boolean catSpawning; ++ public boolean patrolSpawning; ++ public boolean phantomSpawning; ++ public boolean villagerTraderSpawning; ++ public boolean villageSiegeSpawning; ++ private void mobSpawnerSettings() { ++ // values of "default" or null will default to true only if the world environment is normal (aka overworld) ++ Predicate predicate = (bool) -> (bool != null && bool) || (bool == null && environment == Environment.NORMAL); ++ catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate); ++ patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate); ++ phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate); ++ villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate); ++ villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate); ++ } ++ + public int elytraDamagePerSecond = 1; + public double elytraDamageMultiplyBySpeed = 0; + public boolean elytraIgnoreUnbreaking = false; diff --git a/patches/Purpur/patches/server/0114-Raid-cooldown-setting.patch b/patches/Purpur/patches/server/0114-Raid-cooldown-setting.patch new file mode 100644 index 00000000..92448f4f --- /dev/null +++ b/patches/Purpur/patches/server/0114-Raid-cooldown-setting.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Thu, 27 Aug 2020 13:48:52 -0700 +Subject: [PATCH] Raid cooldown setting + + +diff --git a/src/main/java/net/minecraft/server/PersistentRaid.java b/src/main/java/net/minecraft/server/PersistentRaid.java +index 826dcf9f7eedc3664d66170b97b2a19552a0dc60..807910c60e6cad58b91474b0477e6fc109eaf281 100644 +--- a/src/main/java/net/minecraft/server/PersistentRaid.java ++++ b/src/main/java/net/minecraft/server/PersistentRaid.java +@@ -9,6 +9,7 @@ import javax.annotation.Nullable; + + public class PersistentRaid extends PersistentBase { + ++ public final Map playerCooldowns = new java.util.HashMap<>(); // Purpur + public final Map raids = Maps.newHashMap(); + private final WorldServer b; + private int c; +@@ -27,6 +28,17 @@ public class PersistentRaid extends PersistentBase { + + public void a() { + ++this.d; ++ // Purpur start ++ if (b.purpurConfig.raidCooldownSeconds != 0 && this.d % 20 == 0) { ++ com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((id, i) -> { ++ if (i < 1) { ++ playerCooldowns.remove(id); ++ } else { ++ playerCooldowns.put(id, i - 1); ++ } ++ }); ++ } ++ // Purpur end + Iterator iterator = this.raids.values().iterator(); + + while (iterator.hasNext()) { +@@ -110,10 +122,15 @@ public class PersistentRaid extends PersistentBase { + + if (flag) { + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, entityplayer)) { ++ if ((b.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(entityplayer.getUniqueID())) || !org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, entityplayer)) { // Purpur + entityplayer.removeEffect(MobEffects.BAD_OMEN); + return null; + } ++ // Purpur start ++ if (b.purpurConfig.raidCooldownSeconds != 0) { ++ playerCooldowns.put(entityplayer.getUniqueID(), b.purpurConfig.raidCooldownSeconds); ++ } ++ // Purpur end + + if (!this.raids.containsKey(raid.getId())) { + this.raids.put(raid.getId(), raid); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 853f55b91609df363d92cf403eb76e2353d377b1..649b1efbb8f637e91f554e6bbb92fd06c26683ae 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -161,6 +161,7 @@ public class PurpurWorldConfig { + public boolean milkCuresBadOmen = true; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; ++ public int raidCooldownSeconds = 0; + private void miscGameplayMechanicsSettings() { + useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); +@@ -170,6 +171,7 @@ public class PurpurWorldConfig { + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); ++ raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); + } + + public boolean catSpawning; diff --git a/patches/Purpur/patches/server/0115-Despawn-rate-config-options-per-projectile-type.patch b/patches/Purpur/patches/server/0115-Despawn-rate-config-options-per-projectile-type.patch new file mode 100644 index 00000000..1397a4df --- /dev/null +++ b/patches/Purpur/patches/server/0115-Despawn-rate-config-options-per-projectile-type.patch @@ -0,0 +1,341 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Mon, 14 Sep 2020 10:09:05 -0700 +Subject: [PATCH] Despawn rate config options per projectile type + +Default values of -1 respect vanilla behaviour. + +diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java +index f983516b89cdf7ce7fdea8f5a5b1a29dd01ae597..74529a297540ea9c69952d88fe3bb5a13816dcef 100644 +--- a/src/main/java/net/minecraft/server/EntityArrow.java ++++ b/src/main/java/net/minecraft/server/EntityArrow.java +@@ -23,7 +23,7 @@ public abstract class EntityArrow extends IProjectile { + protected int c; + public EntityArrow.PickupStatus fromPlayer; + public int shake; +- public int despawnCounter; ++ //public int despawnCounter; // Purpur - moved to IProjectile + private double damage; + public int knockbackStrength; + private SoundEffect ak; +@@ -257,13 +257,23 @@ public abstract class EntityArrow extends IProjectile { + + } + +- protected final void tickDespawnCounter() { this.h(); } // Paper - OBFHELPER +- protected void h() { +- ++this.despawnCounter; +- if (this.despawnCounter >= (fromPlayer == PickupStatus.CREATIVE_ONLY ? world.paperConfig.creativeArrowDespawnRate : (fromPlayer == PickupStatus.DISALLOWED ? world.paperConfig.nonPlayerArrowDespawnRate : ((this instanceof EntityThrownTrident) ? world.spigotConfig.tridentDespawnRate : world.spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - TODO: Extract this to init? +- this.die(); ++ // Purpur start ++ protected int getPurpurDespawnRate() { ++ if (fromPlayer == PickupStatus.CREATIVE_ONLY) { ++ return world.paperConfig.creativeArrowDespawnRate; ++ } ++ if (fromPlayer == PickupStatus.DISALLOWED) { ++ return world.paperConfig.nonPlayerArrowDespawnRate; ++ } ++ if (this instanceof EntityThrownTrident) { ++ return world.spigotConfig.tridentDespawnRate; + } ++ return world.spigotConfig.arrowDespawnRate; ++ } ++ // Purpur end + ++ protected void h() { ++ tickDespawnCounter(); // Purpur + } + + private void A() { +diff --git a/src/main/java/net/minecraft/server/EntityDragonFireball.java b/src/main/java/net/minecraft/server/EntityDragonFireball.java +index 27032abad4f3da1d1b28a3cec49e3fc079deadb9..9d2d5be5eedc60749e276434be9be6ab41f2289d 100644 +--- a/src/main/java/net/minecraft/server/EntityDragonFireball.java ++++ b/src/main/java/net/minecraft/server/EntityDragonFireball.java +@@ -75,4 +75,11 @@ public class EntityDragonFireball extends EntityFireball { + protected boolean W_() { + return false; + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.dragonFireballDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityEgg.java b/src/main/java/net/minecraft/server/EntityEgg.java +index edce89169b3ca2894852087b83a6bf035ba43c3f..4951abdfa13d170b7075a0223dd0096d77dec6ea 100644 +--- a/src/main/java/net/minecraft/server/EntityEgg.java ++++ b/src/main/java/net/minecraft/server/EntityEgg.java +@@ -87,4 +87,11 @@ public class EntityEgg extends EntityProjectileThrowable { + protected Item getDefaultItem() { + return Items.EGG; + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.eggDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityEnderPearl.java b/src/main/java/net/minecraft/server/EntityEnderPearl.java +index 63b4a449b56ef549830e4bbd3eab116e64379189..e8650c1bfa8f94ba461b7094125679112d825980 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderPearl.java ++++ b/src/main/java/net/minecraft/server/EntityEnderPearl.java +@@ -106,4 +106,11 @@ public class EntityEnderPearl extends EntityProjectileThrowable { + + return super.b(worldserver); + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.enderPearlDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityFireworks.java b/src/main/java/net/minecraft/server/EntityFireworks.java +index dd18eabd7104995f0e6a8ecb279a3872b46773de..601f639275f4df983f19aa9cb0dc5f2611387853 100644 +--- a/src/main/java/net/minecraft/server/EntityFireworks.java ++++ b/src/main/java/net/minecraft/server/EntityFireworks.java +@@ -300,4 +300,11 @@ public class EntityFireworks extends IProjectile { + public Packet P() { + return new PacketPlayOutSpawnEntity(this); + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.fireworkDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityFishingHook.java b/src/main/java/net/minecraft/server/EntityFishingHook.java +index 0816ab54bc99bcf29356b56516e83759a3f2988f..2bad182d9d6248c1e8ac9138e46d192dccc2a973 100644 +--- a/src/main/java/net/minecraft/server/EntityFishingHook.java ++++ b/src/main/java/net/minecraft/server/EntityFishingHook.java +@@ -578,4 +578,11 @@ public class EntityFishingHook extends IProjectile { + + private HookState() {} + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.fishingHookDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityLargeFireball.java b/src/main/java/net/minecraft/server/EntityLargeFireball.java +index b4b0dfbc70f91f74f9792b835ec2f8d5af41c311..d12de20cf4bb2345c616d3cc0b9f50bddb5135ee 100644 +--- a/src/main/java/net/minecraft/server/EntityLargeFireball.java ++++ b/src/main/java/net/minecraft/server/EntityLargeFireball.java +@@ -66,4 +66,11 @@ public class EntityLargeFireball extends EntityFireballFireball { + } + + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.largeFireballDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityLlamaSpit.java b/src/main/java/net/minecraft/server/EntityLlamaSpit.java +index 480a02a8f6ec7110f9af8f2037fdc09a7a54ef01..aa9afb60808a9988b38cf588ec9f649ee09c728e 100644 +--- a/src/main/java/net/minecraft/server/EntityLlamaSpit.java ++++ b/src/main/java/net/minecraft/server/EntityLlamaSpit.java +@@ -73,4 +73,11 @@ public class EntityLlamaSpit extends IProjectile { + public Packet P() { + return new PacketPlayOutSpawnEntity(this); + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.llamaSpitDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityPotion.java b/src/main/java/net/minecraft/server/EntityPotion.java +index ddf1f80f3ba45e09cd13ca646a8ec6e8d47be555..bd84a3ac6146cfe6952a9334820696b4583b45b5 100644 +--- a/src/main/java/net/minecraft/server/EntityPotion.java ++++ b/src/main/java/net/minecraft/server/EntityPotion.java +@@ -240,4 +240,11 @@ public class EntityPotion extends EntityProjectileThrowable { + } + + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.potionDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityShulkerBullet.java b/src/main/java/net/minecraft/server/EntityShulkerBullet.java +index a4d94385ede0303417d676155c2c0b226681cc59..da38bdd6055d06005cfee3e73c32230ad7b480ff 100644 +--- a/src/main/java/net/minecraft/server/EntityShulkerBullet.java ++++ b/src/main/java/net/minecraft/server/EntityShulkerBullet.java +@@ -313,4 +313,11 @@ public class EntityShulkerBullet extends IProjectile { + public Packet P() { + return new PacketPlayOutSpawnEntity(this); + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.shulkerBulletDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntitySmallFireball.java b/src/main/java/net/minecraft/server/EntitySmallFireball.java +index 350e92ac99fe48ba046a51e1db4b977dd9bfc20a..4ed7a20bfed267776628457a4b33178bac7d1972 100644 +--- a/src/main/java/net/minecraft/server/EntitySmallFireball.java ++++ b/src/main/java/net/minecraft/server/EntitySmallFireball.java +@@ -86,4 +86,11 @@ public class EntitySmallFireball extends EntityFireballFireball { + public boolean damageEntity(DamageSource damagesource, float f) { + return false; + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.smallFireballDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntitySnowball.java b/src/main/java/net/minecraft/server/EntitySnowball.java +index e44249f59b742d16f08643ee2a83fdcd0bed9590..34a5f481e6ed1357861dca15fb4013ec8484a292 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowball.java ++++ b/src/main/java/net/minecraft/server/EntitySnowball.java +@@ -14,6 +14,12 @@ public class EntitySnowball extends EntityProjectileThrowable { + super(EntityTypes.SNOWBALL, d0, d1, d2, world); + } + ++ // Purpur start ++ protected int getPurpurDespawnRate() { ++ return world.purpurConfig.snowballDespawnRate; ++ } ++ // Purpur end ++ + @Override + protected Item getDefaultItem() { + return Items.SNOWBALL; +diff --git a/src/main/java/net/minecraft/server/EntityThrownExpBottle.java b/src/main/java/net/minecraft/server/EntityThrownExpBottle.java +index 2d3ca8c424f2088027d51066d634c48723e96214..1d32518bd7982f20574d56f2f2ea4142ea1e015d 100644 +--- a/src/main/java/net/minecraft/server/EntityThrownExpBottle.java ++++ b/src/main/java/net/minecraft/server/EntityThrownExpBottle.java +@@ -51,4 +51,11 @@ public class EntityThrownExpBottle extends EntityProjectileThrowable { + } + + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.expBottleDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/EntityWitherSkull.java b/src/main/java/net/minecraft/server/EntityWitherSkull.java +index 2c02e114cce1f49b643e75e7ab3c05be716d7dba..4a97a7517dc1a2a25c578d9e168240cc19ab0831 100644 +--- a/src/main/java/net/minecraft/server/EntityWitherSkull.java ++++ b/src/main/java/net/minecraft/server/EntityWitherSkull.java +@@ -116,4 +116,11 @@ public class EntityWitherSkull extends EntityFireball { + protected boolean W_() { + return false; + } ++ ++ // Purpur start ++ @Override ++ protected int getPurpurDespawnRate() { ++ return this.world.purpurConfig.witherSkullDespawnRate; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/IProjectile.java b/src/main/java/net/minecraft/server/IProjectile.java +index b7e540dfeeabb13227596ecfc6eddabf3cfde537..56adefabdfbf444e87129715f107c6d3aafe4ca7 100644 +--- a/src/main/java/net/minecraft/server/IProjectile.java ++++ b/src/main/java/net/minecraft/server/IProjectile.java +@@ -13,11 +13,25 @@ public abstract class IProjectile extends Entity { + private UUID shooter; + private int c; + private boolean d; public boolean leftOwner() { return d; } public void setLeftOwner(boolean leftOwner) { this.d = leftOwner; } // Purpur - OBFHELPER ++ public int despawnCounter; // Purpur - moved from EntityArrow + + IProjectile(EntityTypes entitytypes, World world) { + super(entitytypes, world); + } + ++ // Purpur start ++ protected final void tickDespawnCounter() { ++ if (this.getPurpurDespawnRate() != -1) { ++ ++this.despawnCounter; ++ if (this.despawnCounter >= this.getPurpurDespawnRate()) { ++ this.die(); ++ } ++ } ++ } ++ ++ protected abstract int getPurpurDespawnRate(); ++ // Purpur end ++ + public void setShooter(@Nullable Entity entity) { + if (entity != null) { + this.shooter = entity.getUniqueID(); +@@ -72,6 +86,12 @@ public abstract class IProjectile extends Entity { + } + + super.tick(); ++ ++ // Purpur start ++ if (!(this instanceof EntityArrow)) { // EntityArrow handles its own despawn counter ++ this.tickDespawnCounter(); ++ } ++ // Purpur end + } + + public boolean checkIfLeftOwner() { return this.h(); } // Purpur - OBFHELPER +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 649b1efbb8f637e91f554e6bbb92fd06c26683ae..4e75bad4bb9cce557868152b9f2bc3e8cda4d826 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -153,6 +153,35 @@ public class PurpurWorldConfig { + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + } + ++ public int dragonFireballDespawnRate = -1; ++ public int eggDespawnRate = -1; ++ public int enderPearlDespawnRate = -1; ++ public int expBottleDespawnRate = -1; ++ public int fireworkDespawnRate = -1; ++ public int fishingHookDespawnRate = -1; ++ public int largeFireballDespawnRate = -1; ++ public int llamaSpitDespawnRate = -1; ++ public int potionDespawnRate = -1; ++ public int shulkerBulletDespawnRate = -1; ++ public int smallFireballDespawnRate = -1; ++ public int snowballDespawnRate = -1; ++ public int witherSkullDespawnRate = -1; ++ private void projectileDespawnRateSettings() { ++ dragonFireballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.dragon_fireball", dragonFireballDespawnRate); ++ eggDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.egg", eggDespawnRate); ++ enderPearlDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.ender_pearl", enderPearlDespawnRate); ++ expBottleDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.experience_bottle", expBottleDespawnRate); ++ fireworkDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.firework_rocket", fireworkDespawnRate); ++ fishingHookDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.fishing_bobber", fishingHookDespawnRate); ++ largeFireballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.fireball", largeFireballDespawnRate); ++ llamaSpitDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.llama_spit", llamaSpitDespawnRate); ++ potionDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.potion", potionDespawnRate); ++ shulkerBulletDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.shulker_bullet", shulkerBulletDespawnRate); ++ smallFireballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.small_fireball", smallFireballDespawnRate); ++ snowballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.snowball", snowballDespawnRate); ++ witherSkullDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.wither_skull", witherSkullDespawnRate); ++ } ++ + public boolean useBetterMending = false; + public boolean boatEjectPlayersOnLand = false; + public boolean disableDropsOnCrammingDeath = false; diff --git a/patches/Purpur/patches/server/0116-PaperPR-Add-hex-color-code-support-for-console-loggi.patch b/patches/Purpur/patches/server/0116-PaperPR-Add-hex-color-code-support-for-console-loggi.patch new file mode 100644 index 00000000..b0118f91 --- /dev/null +++ b/patches/Purpur/patches/server/0116-PaperPR-Add-hex-color-code-support-for-console-loggi.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Esophose +Date: Thu, 27 Aug 2020 12:25:18 -0500 +Subject: [PATCH] PaperPR - Add hex color code support for console logging + + +diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +index 685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869..2a0ada490b15b0c4939dd4304f86e01634fb1cfa 100644 +--- a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java ++++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +@@ -1,17 +1,52 @@ + package com.destroystokyo.paper.console; + ++import java.awt.Color; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++import net.minecrell.terminalconsole.MinecraftFormattingConverter; ++import net.minecrell.terminalconsole.TerminalConsoleAppender; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.apache.logging.log4j.util.PropertiesUtil; ++import org.bukkit.ChatColor; + import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; + + public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { + + private static final Logger LOGGER = LogManager.getRootLogger(); ++ private static final char ANSI_ESC_CHAR = '\u001B'; ++ private static final String RGB_STRING = ANSI_ESC_CHAR + "[38;2;%d;%d;%dm"; ++ private static final String ANSI_RESET = ANSI_ESC_CHAR + "[m"; ++ private static final Pattern HEX_PATTERN = Pattern.compile("(?i)(" + ChatColor.COLOR_CHAR + "x(" + ChatColor.COLOR_CHAR + "[0-9a-f]){6})"); ++ private static final boolean keepFormatting = PropertiesUtil.getProperties().getBooleanProperty(MinecraftFormattingConverter.KEEP_FORMATTING_PROPERTY); + + @Override + public void sendRawMessage(String message) { + // TerminalConsoleAppender supports color codes directly in log messages +- LOGGER.info(message); ++ // However, we need to convert hex colors manually, as those do not get transformed ++ LOGGER.info(hexMagicToAnsi(message)); + } + ++ private static String hexMagicToAnsi(String input) { ++ // If formatting should be kept, just leave the input as-is ++ if (keepFormatting || !net.pl3x.purpur.PurpurConfig.useHexColorsInConsole) // Purpur ++ return input; ++ ++ // If Ansi is not supported, just strip out any hex coloring ++ if (!TerminalConsoleAppender.isAnsiSupported()) ++ return HEX_PATTERN.matcher(input).replaceAll(""); ++ ++ Matcher matcher = HEX_PATTERN.matcher(input); ++ StringBuffer buffer = new StringBuffer(); ++ while (matcher.find()) { ++ String hex = matcher.group().replace(String.valueOf(ChatColor.COLOR_CHAR), "").replace('x', '#'); ++ Color color = Color.decode(hex); ++ String replacement = String.format(RGB_STRING, color.getRed(), color.getGreen(), color.getBlue()); ++ matcher.appendReplacement(buffer, replacement); ++ } ++ matcher.appendTail(buffer); ++ ++ // We add the Ansi reset to the end of each message to prevent the color from carrying over to the next logged message ++ return buffer.toString() + ANSI_RESET; ++ } + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index a5febd7671ae1818edb18955d5e8176303c92c93..966834c737c4f388f76fcc515f266f684de593b5 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -220,9 +220,11 @@ public class PurpurConfig { + + public static boolean loggerSuppressInitLegacyMaterialError = false; + public static boolean loggerSuppressIgnoredAdvancementWarnings = false; ++ public static boolean useHexColorsInConsole = true; + private static void loggerSettings() { + loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError); + loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings); ++ useHexColorsInConsole = getBoolean("settings.logger.hex-color-support-in-console", useHexColorsInConsole); + } + + public static boolean tpsCatchup = true; diff --git a/patches/Purpur/patches/server/0117-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/Purpur/patches/server/0117-Add-option-to-disable-zombie-aggressiveness-towards-.patch new file mode 100644 index 00000000..55f8b099 --- /dev/null +++ b/patches/Purpur/patches/server/0117-Add-option-to-disable-zombie-aggressiveness-towards-.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nitricspace +Date: Mon, 21 Sep 2020 23:19:43 +0100 +Subject: [PATCH] Add option to disable zombie aggressiveness towards villagers + when lagging + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +index 89b56de7567ae54be75e0735b712e4dd713f1bf4..ded483ace0e93b695a5078391582c1654b6d139a 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -131,6 +131,10 @@ public class MobGoalHelper { + deobfuscationMap.put("wither_a", "wither_do_nothing"); + deobfuscationMap.put("wolf_a", "wolf_avoid_entity"); + deobfuscationMap.put("zombie_a", "zombie_attack_turtle_egg"); ++ // Purpur start ++ deobfuscationMap.put("zombie_1", "zombie_attack_villager"); ++ deobfuscationMap.put("drowned_1", "drowned_attack_villager"); ++ // Purpur end + + ignored.add("selector_1"); + ignored.add("selector_2"); +diff --git a/src/main/java/net/minecraft/server/EntityDrowned.java b/src/main/java/net/minecraft/server/EntityDrowned.java +index 125eab60f2b4657e52a71eddf7586c574945252e..638efc67d66001ee085957d4698f51a7daac77fc 100644 +--- a/src/main/java/net/minecraft/server/EntityDrowned.java ++++ b/src/main/java/net/minecraft/server/EntityDrowned.java +@@ -58,7 +58,18 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + this.goalSelector.a(7, new PathfinderGoalRandomStroll(this, 1.0D)); + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityDrowned.class})).a(EntityPigZombie.class)); + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::i)); +- if ( world.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); // Paper ++ // Purpur start ++ if ( world.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget(this, EntityVillagerAbstract.class, false) { // Spigot ++ @Override ++ public boolean a() { ++ return (world.purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !world.getMinecraftServer().server.isLagging()) && super.a(); ++ } ++ @Override ++ public boolean b() { ++ return (world.purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !world.getMinecraftServer().server.isLagging()) && super.b(); ++ } ++ }); ++ // Purpur end + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); + this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, true, false, EntityTurtle.bo)); + } +diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java +index d835ce3fe7c71333efeed5b9cf2a827bebae97f2..556f7a3ebb5c58a87471b2d098f29ffb216aaa1d 100644 +--- a/src/main/java/net/minecraft/server/EntityZombie.java ++++ b/src/main/java/net/minecraft/server/EntityZombie.java +@@ -86,7 +86,18 @@ public class EntityZombie extends EntityMonster { + this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D)); + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(EntityPigZombie.class)); + this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true)); +- if ( world.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); // Spigot ++ // Purpur start ++ if ( world.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget(this, EntityVillagerAbstract.class, false) { // Spigot ++ @Override ++ public boolean a() { ++ return (world.purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !world.getMinecraftServer().server.isLagging()) && super.a(); ++ } ++ @Override ++ public boolean b() { ++ return (world.purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !world.getMinecraftServer().server.isLagging()) && super.b(); ++ } ++ }); ++ // Purpur end + this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true)); + this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, true, false, EntityTurtle.bo)); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 4e75bad4bb9cce557868152b9f2bc3e8cda4d826..d98ddaf02456ff5204a498bb6827bfad08cbc8d6 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1011,12 +1011,14 @@ public class PurpurWorldConfig { + public boolean zombieJockeyOnlyBaby = true; + public double zombieJockeyChance = 0.05D; + public boolean zombieJockeyTryExistingChickens = true; ++ public boolean zombieAggressiveTowardsVillagerWhenLagging = true; + private void zombieSettings() { + zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); + zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); + zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); + zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); + zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); ++ zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging); + } + + public boolean zombieHorseCanSwim = false; diff --git a/patches/Purpur/patches/server/0118-Persistent-TileEntity-Lore-and-DisplayName.patch b/patches/Purpur/patches/server/0118-Persistent-TileEntity-Lore-and-DisplayName.patch new file mode 100644 index 00000000..0e5cb119 --- /dev/null +++ b/patches/Purpur/patches/server/0118-Persistent-TileEntity-Lore-and-DisplayName.patch @@ -0,0 +1,186 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 30 Sep 2020 14:32:46 -0700 +Subject: [PATCH] Persistent TileEntity Lore and DisplayName + +Makes it so that when a TileEntity is placed in the world and then broken, +the dropped ItemStack retains any original custom display name/lore. + +diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java +index d621b11ba9029a732a819e0558d6f8a439990dbe..c276a2896ba73d86e7e18d656903b36d2970ac41 100644 +--- a/src/main/java/net/minecraft/server/Block.java ++++ b/src/main/java/net/minecraft/server/Block.java +@@ -207,7 +207,7 @@ public class Block extends BlockBase implements IMaterial { + public static void a(IBlockData iblockdata, GeneratorAccess generatoraccess, BlockPosition blockposition, @Nullable TileEntity tileentity) { + if (generatoraccess instanceof WorldServer) { + a(iblockdata, (WorldServer) generatoraccess, blockposition, tileentity).forEach((itemstack) -> { +- a((World) ((WorldServer) generatoraccess), blockposition, itemstack); ++ dropItem((WorldServer) generatoraccess, blockposition, applyDisplayNameAndLoreFromTile(itemstack, tileentity)); // Purpur + }); + iblockdata.dropNaturally((WorldServer) generatoraccess, blockposition, ItemStack.b); + } +@@ -216,14 +216,56 @@ public class Block extends BlockBase implements IMaterial { + + public static void dropItems(IBlockData iblockdata, World world, BlockPosition blockposition, @Nullable TileEntity tileentity, Entity entity, ItemStack itemstack) { + if (world instanceof WorldServer) { +- getDrops(iblockdata, (WorldServer) world, blockposition, tileentity, entity, itemstack).forEach((itemstack1) -> { +- a(world, blockposition, itemstack1); ++ // Purpur start ++ getDrops(iblockdata, (WorldServer) world, blockposition, tileentity, entity, itemstack).forEach(stackToDrop -> { ++ dropItem(world, blockposition, applyDisplayNameAndLoreFromTile(stackToDrop, tileentity)); ++ // Purpur end + }); + iblockdata.dropNaturally((WorldServer) world, blockposition, itemstack); + } + + } + ++ // Purpur start ++ private static ItemStack applyDisplayNameAndLoreFromTile(ItemStack itemStack, TileEntity tile) { ++ if (itemStack.getItem() instanceof ItemBlock) { ++ if (tile != null && tile.getWorld() instanceof WorldServer && tile.getWorld().purpurConfig.persistentTileEntityDisplayNames) { ++ String name = tile.getPersistentDisplayName(); ++ NBTTagList lore = tile.getPersistentLore(); ++ if (tile instanceof INamableTileEntity) { ++ INamableTileEntity namedTile = (INamableTileEntity) tile; ++ if (namedTile.hasCustomName()) { ++ name = IChatBaseComponent.ChatSerializer.componentToJson(namedTile.getCustomName()); ++ } ++ } ++ ++ if (name != null || lore != null) { ++ NBTTagCompound display = itemStack.getSubTag("display"); ++ if (display == null) { ++ display = new NBTTagCompound(); ++ } ++ ++ if (name != null) { ++ display.set("Name", NBTTagString.create(name)); ++ } ++ if (lore != null) { ++ display.set("Lore", lore); ++ } ++ ++ NBTTagCompound tag = itemStack.getTag(); ++ if (tag == null) { ++ tag = new NBTTagCompound(); ++ } ++ tag.set("display", display); ++ ++ itemStack.setTag(tag); ++ } ++ } ++ } ++ return itemStack; ++ } ++ // Purpur end ++ + public static void a(World world, BlockPosition blockposition, ItemStack itemstack) { dropItem(world, blockposition, itemstack); } public static void dropItem(World world, BlockPosition blockposition, ItemStack itemstack) { // Paper - OBFHELPER + if (!world.isClientSide && !itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.DO_TILE_DROPS)) { + float f = 0.5F; +diff --git a/src/main/java/net/minecraft/server/ItemBlock.java b/src/main/java/net/minecraft/server/ItemBlock.java +index 9d62bc6d600526881dccb44d158e30f565078cec..b8c71925a6e4c58fcefba0ab38270aaa236aabba 100644 +--- a/src/main/java/net/minecraft/server/ItemBlock.java ++++ b/src/main/java/net/minecraft/server/ItemBlock.java +@@ -96,7 +96,24 @@ public class ItemBlock extends Item { + } + + protected boolean a(BlockPosition blockposition, World world, @Nullable EntityHuman entityhuman, ItemStack itemstack, IBlockData iblockdata) { +- return a(world, entityhuman, blockposition, itemstack); ++ // Purpur start ++ boolean handled = a(world, entityhuman, blockposition, itemstack); ++ if (world.purpurConfig.persistentTileEntityDisplayNames && itemstack.hasTag()) { ++ NBTTagCompound display = itemstack.getSubTag("display"); ++ if (display != null) { ++ TileEntity tile = world.getTileEntity(blockposition); ++ if (tile != null) { ++ if (display.hasKeyOfType("Name", 8)) { ++ tile.setPersistentDisplayName(display.getString("Name")); ++ } ++ if (display.hasKeyOfType("Lore", 9)) { ++ tile.setPersistentLore(display.getList("Lore", 8)); ++ } ++ } ++ } ++ } ++ return handled; ++ // Purpur end + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java +index 58d958a88ac5af5b889d719d9f1ea90ce45cf184..8e8749095427b44e04582593114cae8cf6d00f42 100644 +--- a/src/main/java/net/minecraft/server/TileEntity.java ++++ b/src/main/java/net/minecraft/server/TileEntity.java +@@ -90,9 +90,25 @@ public abstract class TileEntity implements KeyedObject { // Paper + this.persistentDataContainer.putAll((NBTTagCompound) persistentDataTag); + } + // CraftBukkit end ++ // Purpur start ++ if (nbttagcompound.hasKey("Purpur.persistentDisplayName")) { ++ this.persistentDisplayName = nbttagcompound.getString("Purpur.persistentDisplayName"); ++ } ++ if (nbttagcompound.hasKey("Purpur.persistentLore")) { ++ this.persistentLore = nbttagcompound.getList("Purpur.persistentLore", 8); ++ } ++ // Purpur end + } + + public NBTTagCompound save(NBTTagCompound nbttagcompound) { ++ // Purpur start ++ if (this.persistentDisplayName != null) { ++ nbttagcompound.set("Purpur.persistentDisplayName", NBTTagString.create(this.persistentDisplayName)); ++ } ++ if (this.persistentLore != null) { ++ nbttagcompound.set("Purpur.persistentLore", this.persistentLore); ++ } ++ // Purpur end + return this.b(nbttagcompound); + } + +@@ -253,4 +269,25 @@ public abstract class TileEntity implements KeyedObject { // Paper + return null; + } + // CraftBukkit end ++ ++ // Purpur start ++ private String persistentDisplayName = null; ++ private NBTTagList persistentLore = null; ++ ++ public void setPersistentDisplayName(String json) { ++ this.persistentDisplayName = json; ++ } ++ ++ public void setPersistentLore(NBTTagList lore) { ++ this.persistentLore = lore; ++ } ++ ++ public String getPersistentDisplayName() { ++ return this.persistentDisplayName; ++ } ++ ++ public NBTTagList getPersistentLore() { ++ return this.persistentLore; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d98ddaf02456ff5204a498bb6827bfad08cbc8d6..72a16f70bd5be8232b2bc6caf489c64e34a6694c 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -188,6 +188,7 @@ public class PurpurWorldConfig { + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean entitiesCanUsePortals = true; + public boolean milkCuresBadOmen = true; ++ public boolean persistentTileEntityDisplayNames = false; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + public int raidCooldownSeconds = 0; +@@ -198,6 +199,7 @@ public class PurpurWorldConfig { + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); ++ persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); diff --git a/patches/Purpur/patches/server/0119-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch b/patches/Purpur/patches/server/0119-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch new file mode 100644 index 00000000..3cb20473 --- /dev/null +++ b/patches/Purpur/patches/server/0119-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 17:40:52 -0500 +Subject: [PATCH] Add predicate to recipe's ExactChoice ingredient + + +diff --git a/src/main/java/net/minecraft/server/RecipeItemStack.java b/src/main/java/net/minecraft/server/RecipeItemStack.java +index 0f96abd0ca78e9c78306fed69684dee71be37703..f6d92949409b66d5d0c578e010aebd058903c6fa 100644 +--- a/src/main/java/net/minecraft/server/RecipeItemStack.java ++++ b/src/main/java/net/minecraft/server/RecipeItemStack.java +@@ -26,6 +26,7 @@ public final class RecipeItemStack implements Predicate { + public ItemStack[] choices; + private IntList d; + public boolean exact; // CraftBukkit ++ public Predicate predicate; // Purpur + + public RecipeItemStack(Stream stream) { + this.b = (RecipeItemStack.Provider[]) stream.toArray((i) -> { +@@ -52,6 +53,12 @@ public final class RecipeItemStack implements Predicate { + if (this.choices.length == 0) { + return itemstack.isEmpty(); + } else { ++ // Purpur start ++ if (predicate != null) { ++ return predicate.test(itemstack.asBukkitCopy()); ++ } ++ // Purpur end ++ + ItemStack[] aitemstack = this.choices; + int i = aitemstack.length; + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +index ef29599a89bc630899c65df9a7004f836787d95e..18413e6327458c6d60d2a0ca8167fc5d75389934 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +@@ -22,6 +22,7 @@ public interface CraftRecipe extends Recipe { + } else if (bukkit instanceof RecipeChoice.ExactChoice) { + stack = new RecipeItemStack(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> new net.minecraft.server.RecipeItemStack.StackProvider(CraftItemStack.asNMSCopy(mat)))); + stack.exact = true; ++ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur + } else { + throw new IllegalArgumentException("Unknown recipe stack instance " + bukkit); + } diff --git a/patches/Purpur/patches/server/0120-Infinity-bow-settings.patch b/patches/Purpur/patches/server/0120-Infinity-bow-settings.patch new file mode 100644 index 00000000..0293285c --- /dev/null +++ b/patches/Purpur/patches/server/0120-Infinity-bow-settings.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 4 Oct 2020 19:08:53 -0500 +Subject: [PATCH] Infinity bow settings + + +diff --git a/src/main/java/net/minecraft/server/ItemBow.java b/src/main/java/net/minecraft/server/ItemBow.java +index dd6a93dc78a4589f2c65d1738c432def1285f3e2..8241f3dafa5852bed7a3967e7260b36f47198dba 100644 +--- a/src/main/java/net/minecraft/server/ItemBow.java ++++ b/src/main/java/net/minecraft/server/ItemBow.java +@@ -24,7 +24,7 @@ public class ItemBow extends ItemProjectileWeapon implements ItemVanishable { + float f = a(j); + + if ((double) f >= 0.1D) { +- boolean flag1 = flag && itemstack1.getItem() == Items.ARROW; ++ boolean flag1 = flag && ((itemstack1.getItem() == Items.ARROW && world.purpurConfig.infinityWorksWithNormalArrows) || (itemstack1.getItem() == Items.TIPPED_ARROW && world.purpurConfig.infinityWorksWithTippedArrows) || (itemstack1.getItem() == Items.SPECTRAL_ARROW && world.purpurConfig.infinityWorksWithSpectralArrows)); // Purpur + + if (!world.isClientSide) { + ItemArrow itemarrow = (ItemArrow) ((ItemArrow) (itemstack1.getItem() instanceof ItemArrow ? itemstack1.getItem() : Items.ARROW)); +@@ -85,6 +85,7 @@ public class ItemBow extends ItemProjectileWeapon implements ItemVanishable { + entityhuman.inventory.f(itemstack1); + } + } ++ else if (!entityhuman.abilities.canInstantlyBuild) ((org.bukkit.entity.Player) entityhuman.getBukkitEntity()).updateInventory(); // Purpur + + entityhuman.b(StatisticList.ITEM_USED.b(this)); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 72a16f70bd5be8232b2bc6caf489c64e34a6694c..be0adb6027fd375b02a63aaeee931f1a86d91943 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -153,6 +153,15 @@ public class PurpurWorldConfig { + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + } + ++ public boolean infinityWorksWithNormalArrows = true; ++ public boolean infinityWorksWithSpectralArrows = false; ++ public boolean infinityWorksWithTippedArrows = false; ++ private void infinityArrowsSettings() { ++ infinityWorksWithNormalArrows = getBoolean("gameplay-mechanics.infinity-bow.normal-arrows", infinityWorksWithNormalArrows); ++ infinityWorksWithSpectralArrows = getBoolean("gameplay-mechanics.infinity-bow.spectral-arrows", infinityWorksWithSpectralArrows); ++ infinityWorksWithTippedArrows = getBoolean("gameplay-mechanics.infinity-bow.tipped-arrows", infinityWorksWithTippedArrows); ++ } ++ + public int dragonFireballDespawnRate = -1; + public int eggDespawnRate = -1; + public int enderPearlDespawnRate = -1; diff --git a/patches/Purpur/patches/server/0121-Stonecutter-damage.patch b/patches/Purpur/patches/server/0121-Stonecutter-damage.patch new file mode 100644 index 00000000..a19792ea --- /dev/null +++ b/patches/Purpur/patches/server/0121-Stonecutter-damage.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 5 Oct 2020 12:15:14 -0500 +Subject: [PATCH] Stonecutter damage + + +diff --git a/src/main/java/net/minecraft/server/BlockStonecutter.java b/src/main/java/net/minecraft/server/BlockStonecutter.java +index 3e57abd33ee61d78f6d895ec710adb5e5983d42c..08ba9e1c4e916ee09df1bd397b8fc36b4780b9b5 100644 +--- a/src/main/java/net/minecraft/server/BlockStonecutter.java ++++ b/src/main/java/net/minecraft/server/BlockStonecutter.java +@@ -71,4 +71,16 @@ public class BlockStonecutter extends Block { + public boolean a(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition, PathMode pathmode) { + return false; + } ++ ++ // Purpur start ++ @Override ++ public void stepOn(World world, BlockPosition pos, Entity entity) { ++ if (world.purpurConfig.stonecutterDamage > 0.0F && entity instanceof EntityLiving) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ entity.damageEntity(DamageSource.CACTUS, world.purpurConfig.stonecutterDamage); ++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; ++ } ++ super.stepOn(world, pos, entity); ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/PathfinderNormal.java b/src/main/java/net/minecraft/server/PathfinderNormal.java +index 33804e68931e8b4145b896eedeab79bde78779f2..fabadcd7a21b0e4ad0e2eeadcd8926dfad6c4b7a 100644 +--- a/src/main/java/net/minecraft/server/PathfinderNormal.java ++++ b/src/main/java/net/minecraft/server/PathfinderNormal.java +@@ -480,7 +480,7 @@ public class PathfinderNormal extends PathfinderAbstract { + return iblockdata.neighbourOverridePathType = PathType.DANGER_CACTUS; // Tuinity - reduce pathfinder branching + } + +- if (iblockdata.a(Blocks.SWEET_BERRY_BUSH)) { ++ if (iblockdata.a(Blocks.SWEET_BERRY_BUSH) || iblockdata.a(Blocks.STONECUTTER)) { // Purpur + return iblockdata.neighbourOverridePathType = PathType.DANGER_OTHER; // Tuinity - reduce pathfinder branching + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index be0adb6027fd375b02a63aaeee931f1a86d91943..c6977f68116bcbf287d10ca573311b292ec7b734 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -371,6 +371,11 @@ public class PurpurWorldConfig { + spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); + } + ++ public float stonecutterDamage = 0.0F; ++ private void stonecutterSettings() { ++ stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); ++ } ++ + public boolean turtleEggsBreakFromExpOrbs = true; + public boolean turtleEggsBreakFromItems = true; + public boolean turtleEggsBreakFromMinecarts = true; diff --git a/patches/Purpur/patches/server/0122-Configurable-daylight-cycle.patch b/patches/Purpur/patches/server/0122-Configurable-daylight-cycle.patch new file mode 100644 index 00000000..a5d5038b --- /dev/null +++ b/patches/Purpur/patches/server/0122-Configurable-daylight-cycle.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 10 Oct 2020 14:29:55 -0500 +Subject: [PATCH] Configurable daylight cycle + + +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutUpdateTime.java b/src/main/java/net/minecraft/server/PacketPlayOutUpdateTime.java +index 1b9b43ee696575d986c25cafec07d863acb951a7..e837db171545ceacbc84a2b360cf0d95347145d0 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutUpdateTime.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutUpdateTime.java +@@ -5,7 +5,7 @@ import java.io.IOException; + public class PacketPlayOutUpdateTime implements Packet { + + private long a; private final void setWorldAge(final long age) { this.a = age; } private final long getWorldAge() { return this.a; } // Paper - OBFHELPER +- private long b; ++ private long b; public void setPlayerTime(long time) { this.b = time; } // Purpur + + public PacketPlayOutUpdateTime() {} + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 7ee7975d5569d24c836018e240ebd785a9e8fed5..6d5616dc4a899a1c01a21daece17f1c2cf0411bd 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -94,6 +94,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final EnderDragonBattle dragonBattle; + private final StructureManager structureManager; + private final boolean Q; ++ private double fakeTime; // Purpur + + + // CraftBukkit start +@@ -388,6 +389,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.getServer().addWorld(this.getWorld()); // CraftBukkit + + this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper ++ this.fakeTime = this.worldDataServer.getDayTime(); // Purpur + } + + // Tuinity start - optimise collision +@@ -964,7 +966,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.nextTickListBlock.nextTick(); // Paper + this.nextTickListFluid.nextTick(); // Paper + this.worldDataServer.u().a(this.server, i); +- if (this.worldData.q().getBoolean(GameRules.DO_DAYLIGHT_CYCLE)) { ++ // Purpur start ++ WorldServer world = this.worldDataServer.world; ++ if (world.getGameRules().getBoolean(GameRules.DO_DAYLIGHT_CYCLE)) { ++ double incrementTimeBy = 12000.0D / (double) (world.isDay() ? world.purpurConfig.daytimeTicks : world.purpurConfig.nighttimeTicks); ++ if (incrementTimeBy != 1.0D) { ++ this.fakeTime += incrementTimeBy; ++ this.setDayTime(this.fakeTime); ++ PacketPlayOutUpdateTime packet = new PacketPlayOutUpdateTime(world.getTime(), world.getDayTime(), true); ++ for (EntityHuman entityhuman : world.players) { ++ EntityPlayer player = (EntityPlayer) entityhuman; ++ packet.setPlayerTime(player.getPlayerTime()); ++ player.playerConnection.sendPacket(packet); ++ } ++ } else ++ // Purpur end + this.setDayTime(this.worldData.getDayTime() + 1L); + } + +@@ -973,6 +989,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + public void setDayTime(long i) { + this.worldDataServer.setDayTime(i); ++ // Purpur start ++ this.fakeTime = i; ++ } ++ public void setDayTime(double i) { ++ this.worldDataServer.setDayTime((long) i); ++ // Purpur end + } + + public void doMobSpawning(boolean flag, boolean flag1) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index c6977f68116bcbf287d10ca573311b292ec7b734..6761612d9a3f7fdce142c709cbb2ffec1b669094 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -116,6 +116,13 @@ public class PurpurWorldConfig { + } + } + ++ public int daytimeTicks = 12000; ++ public int nighttimeTicks = 12000; ++ private void daytimeCycleSettings() { ++ daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks); ++ nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks); ++ } ++ + public int entityLifeSpan = 0; + private void entitySettings() { + entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); diff --git a/patches/Purpur/patches/server/0123-Allow-infinite-and-mending-enchantments-together.patch b/patches/Purpur/patches/server/0123-Allow-infinite-and-mending-enchantments-together.patch new file mode 100644 index 00000000..fbbe9361 --- /dev/null +++ b/patches/Purpur/patches/server/0123-Allow-infinite-and-mending-enchantments-together.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 13 Oct 2020 20:04:33 -0500 +Subject: [PATCH] Allow infinite and mending enchantments together + + +diff --git a/src/main/java/net/minecraft/server/EnchantmentInfiniteArrows.java b/src/main/java/net/minecraft/server/EnchantmentInfiniteArrows.java +index 408cfa460920f74e0394ab27101ecb12cceb9c43..3d4e34f7070a48c436284ba7744a94aeacbb7651 100644 +--- a/src/main/java/net/minecraft/server/EnchantmentInfiniteArrows.java ++++ b/src/main/java/net/minecraft/server/EnchantmentInfiniteArrows.java +@@ -23,6 +23,6 @@ public class EnchantmentInfiniteArrows extends Enchantment { + + @Override + public boolean a(Enchantment enchantment) { +- return enchantment instanceof EnchantmentMending ? false : super.a(enchantment); ++ return enchantment instanceof EnchantmentMending ? net.pl3x.purpur.PurpurConfig.allowInfinityMending : super.a(enchantment); // Purpur + } + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 966834c737c4f388f76fcc515f266f684de593b5..47d323038914fd601e17499802bf3dae5f842d31 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -197,6 +197,16 @@ public class PurpurConfig { + cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame); + } + ++ public static boolean allowInfinityMending = false; ++ private static void enchantmentSettings() { ++ if (version < 5) { ++ boolean oldValue = getBoolean("settings.enchantment.allow-infinite-and-mending-together", false); ++ set("settings.enchantment.allow-infinity-and-mending-together", oldValue); ++ set("settings.enchantment.allow-infinite-and-mending-together", null); ++ } ++ allowInfinityMending = getBoolean("settings.enchantment.allow-infinity-and-mending-together", allowInfinityMending); ++ } ++ + public static boolean endermanShortHeight = false; + private static void entitySettings() { + endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight); diff --git a/patches/Purpur/patches/server/0124-Infinite-fuel-furnace.patch b/patches/Purpur/patches/server/0124-Infinite-fuel-furnace.patch new file mode 100644 index 00000000..1fa17561 --- /dev/null +++ b/patches/Purpur/patches/server/0124-Infinite-fuel-furnace.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 19 Oct 2020 15:14:01 -0500 +Subject: [PATCH] Infinite fuel furnace + + +diff --git a/src/main/java/net/minecraft/server/TileEntityFurnace.java b/src/main/java/net/minecraft/server/TileEntityFurnace.java +index e75e676d196d9f5a3409ec50645fab611b0afdad..76ea1d003b43d822e2b85eec3b8740155efd531a 100644 +--- a/src/main/java/net/minecraft/server/TileEntityFurnace.java ++++ b/src/main/java/net/minecraft/server/TileEntityFurnace.java +@@ -278,6 +278,22 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + if (!this.world.isClientSide) { + ItemStack itemstack = (ItemStack) this.items.get(1); + ++ // Purpur start ++ boolean infiniteFuel = false; ++ if (world.purpurConfig.furnaceInfiniteFuel && !isBurning() && itemstack.isEmpty() && !items.get(0).isEmpty() && world.getTime() % 20 == 0) { ++ BlockPosition pos = getPosition().down(); ++ IBlockData iblockdata = world.getTypeIfLoaded(pos); ++ if (iblockdata != null && iblockdata.equals(Blocks.LAVA)) { ++ Fluid fluid = iblockdata.getFluid(); ++ if (fluid != null && fluid.isSource()) { ++ world.setTypeAndData(pos, Blocks.AIR.getBlockData(), 3); ++ itemstack = Items.LAVA_BUCKET.createItemStack(); ++ infiniteFuel = true; ++ } ++ } ++ } ++ // Purpur end ++ + if (!this.isBurning() && (itemstack.isEmpty() || ((ItemStack) this.items.get(0)).isEmpty())) { + if (!this.isBurning() && this.cookTime > 0) { + this.cookTime = MathHelper.clamp(this.cookTime - 2, 0, this.cookTimeTotal); +@@ -331,6 +347,8 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I + flag1 = true; + this.world.setTypeAndData(this.position, (IBlockData) this.world.getType(this.position).set(BlockFurnace.LIT, this.isBurning()), 3); + } ++ ++ if (infiniteFuel) this.items.set(1, ItemStack.NULL_ITEM); // Purpur + } + + if (flag1) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 6761612d9a3f7fdce142c709cbb2ffec1b669094..3e2e915f29267544ff96a317e428a378fcf0783c 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -339,6 +339,11 @@ public class PurpurWorldConfig { + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); + } + ++ public boolean furnaceInfiniteFuel = false; ++ private void furnaceSettings() { ++ furnaceInfiniteFuel = getBoolean("blocks.furnace.infinite-fuel", furnaceInfiniteFuel); ++ } ++ + public boolean lavaInfinite = false; + public int lavaInfiniteRequiredSources = 2; + public int lavaSpeedNether = 10; diff --git a/patches/Purpur/patches/server/0125-Arrows-should-not-reset-despawn-counter.patch b/patches/Purpur/patches/server/0125-Arrows-should-not-reset-despawn-counter.patch new file mode 100644 index 00000000..c7508333 --- /dev/null +++ b/patches/Purpur/patches/server/0125-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 + +This prevents keeping arrows alive indefinitely (such as when the block +the arrow is stuck in gets removed, like a piston head going up/down) + +diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java +index 74529a297540ea9c69952d88fe3bb5a13816dcef..2e11286e2baffa66bea9570b0377478cd45fac1e 100644 +--- a/src/main/java/net/minecraft/server/EntityArrow.java ++++ b/src/main/java/net/minecraft/server/EntityArrow.java +@@ -245,7 +245,7 @@ public abstract class EntityArrow extends IProjectile { + Vec3D vec3d = this.getMot(); + + this.setMot(vec3d.d((double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F))); +- this.despawnCounter = 0; ++ // this.despawnCounter = 0; // Purpur - do not reset despawn counter + } + + @Override diff --git a/patches/Purpur/patches/server/0126-Add-tablist-suffix-option-for-afk.patch b/patches/Purpur/patches/server/0126-Add-tablist-suffix-option-for-afk.patch new file mode 100644 index 00000000..6268ca93 --- /dev/null +++ b/patches/Purpur/patches/server/0126-Add-tablist-suffix-option-for-afk.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: montlikadani +Date: Thu, 12 Nov 2020 11:02:50 +0100 +Subject: [PATCH] Add tablist suffix option for afk + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 9e3c85d3dd70bccdbff9663e86dd0bb449701085..54853eda0c9dd56bb25ff059030d32e97e78f829 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1972,7 +1972,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + + if (world.purpurConfig.idleTimeoutUpdateTabList) { +- getBukkitEntity().setPlayerListName((setAfk ? net.pl3x.purpur.PurpurConfig.afkTabListPrefix : "") + getName()); ++ if (setAfk) { ++ getBukkitEntity().setPlayerListName(net.pl3x.purpur.PurpurConfig.afkTabListPrefix + getName() + net.pl3x.purpur.PurpurConfig.afkTabListSuffix); ++ } else { ++ getBukkitEntity().setPlayerListName(getName()); ++ } + } + + ((WorldServer) world).everyoneSleeping(); +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 47d323038914fd601e17499802bf3dae5f842d31..74c76b7f3e0992a90acec338a96077bf4f7afd2a 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -135,12 +135,14 @@ public class PurpurConfig { + public static String afkBroadcastAway = "§e§o%s is now AFK"; + public static String afkBroadcastBack = "§e§o%s is no longer AFK"; + public static String afkTabListPrefix = "[AFK] "; ++ public static String afkTabListSuffix = ""; + public static String pingCommandOutput = "§a%s's ping is %sms"; + public static String cannotRideMob = "§cYou cannot mount that mob"; + private static void messages() { + afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); + afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); + afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix); ++ afkTabListSuffix = getString("settings.messages.afk-tab-list-suffix", afkTabListSuffix); + pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); + cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); + } diff --git a/patches/Purpur/patches/server/0127-Ability-to-re-add-farmland-mechanics-from-Alpha.patch b/patches/Purpur/patches/server/0127-Ability-to-re-add-farmland-mechanics-from-Alpha.patch new file mode 100644 index 00000000..e67dae14 --- /dev/null +++ b/patches/Purpur/patches/server/0127-Ability-to-re-add-farmland-mechanics-from-Alpha.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Sat, 14 Nov 2020 08:06:20 -0800 +Subject: [PATCH] Ability to re-add farmland mechanics from Alpha + + +diff --git a/src/main/java/net/minecraft/server/BlockSoil.java b/src/main/java/net/minecraft/server/BlockSoil.java +index 8dd48669c29dd51ed4d535dad0b0319f4bb2250c..099e0d3df219408ebe2a741a02e53eb9f7def28e 100644 +--- a/src/main/java/net/minecraft/server/BlockSoil.java ++++ b/src/main/java/net/minecraft/server/BlockSoil.java +@@ -90,6 +90,14 @@ public class BlockSoil extends Block { + return; + } + ++ // Purpur start ++ if (world.purpurConfig.farmlandAlpha) { ++ Block block = world.getType(blockposition.down()).getBlock(); ++ if (block instanceof BlockFence || block instanceof BlockCobbleWall) { ++ return; ++ } ++ } ++ // Purpur end + if (CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, Blocks.DIRT.getBlockData()).isCancelled()) { + return; + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 3e2e915f29267544ff96a317e428a378fcf0783c..fe4e91ef5fca328143c2598ac5725babc5061a41 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -335,8 +335,10 @@ public class PurpurWorldConfig { + } + + public boolean farmlandGetsMoistFromBelow = false; ++ public boolean farmlandAlpha = false; + private void farmlandSettings() { + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); ++ farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); + } + + public boolean furnaceInfiniteFuel = false; diff --git a/patches/Purpur/patches/server/0128-Add-adjustable-breeding-cooldown-to-config.patch b/patches/Purpur/patches/server/0128-Add-adjustable-breeding-cooldown-to-config.patch new file mode 100644 index 00000000..262e1c4c --- /dev/null +++ b/patches/Purpur/patches/server/0128-Add-adjustable-breeding-cooldown-to-config.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: montlikadani +Date: Fri, 13 Nov 2020 17:52:40 +0100 +Subject: [PATCH] Add adjustable breeding cooldown to config + + +diff --git a/src/main/java/net/minecraft/server/EntityAnimal.java b/src/main/java/net/minecraft/server/EntityAnimal.java +index bba343542e7e6fa83ec802d97b4c139bb210ab28..d9f9e2235d091e14e5d34bb9a3273e7f56e94295 100644 +--- a/src/main/java/net/minecraft/server/EntityAnimal.java ++++ b/src/main/java/net/minecraft/server/EntityAnimal.java +@@ -120,7 +120,7 @@ public abstract class EntityAnimal extends EntityAgeable { + if (this.k(itemstack)) { + int i = this.getAge(); + +- if (!this.world.isClientSide && i == 0 && this.eP()) { ++ if (!this.world.isClientSide && i == 0 && this.eP() && (this.world.purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.world.hasBreedingCooldown(entityhuman.getUniqueID(), this.getClass()))) { // Purpur + this.a(entityhuman, itemstack); + this.g(entityhuman); + return EnumInteractionResult.SUCCESS; +@@ -212,6 +212,14 @@ public abstract class EntityAnimal extends EntityAgeable { + if (entityplayer == null && entityanimal.getBreedCause() != null) { + entityplayer = entityanimal.getBreedCause(); + } ++ // Purpur start ++ if (entityplayer != null && worldserver.purpurConfig.animalBreedingCooldownSeconds > 0) { ++ if (worldserver.hasBreedingCooldown(entityplayer.getUniqueID(), this.getClass())) { ++ return; ++ } ++ worldserver.addBreedingCooldown(entityplayer.getUniqueID(), this.getClass()); ++ } ++ // Purpur end + // CraftBukkit start - call EntityBreedEvent + int experience = this.getRandom().nextInt(7) + 1; + org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, entityanimal, entityplayer, this.breedItem, experience); +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index aa1b037c0103552761b81318f1d2ad8215bd0370..91aa8a2bc111ee6935ada0ae471fe1a3bc8fad80 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -104,6 +104,48 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + 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 ++ // Purpur start ++ private com.google.common.cache.Cache playerBreedingCooldowns; ++ ++ private com.google.common.cache.Cache getNewBreedingCooldownCache() { ++ return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build(); ++ } ++ ++ public void resetBreedingCooldowns() { ++ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); ++ } ++ ++ boolean hasBreedingCooldown(java.util.UUID player, Class animalType) { ++ return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null; ++ } ++ ++ void addBreedingCooldown(java.util.UUID player, Class animalType) { ++ this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object()); ++ } ++ ++ private static final class BreedingCooldownPair { ++ private final java.util.UUID playerUUID; ++ private final Class animalType; ++ ++ public BreedingCooldownPair(java.util.UUID playerUUID, Class animalType) { ++ this.playerUUID = playerUUID; ++ this.animalType = animalType; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ BreedingCooldownPair that = (BreedingCooldownPair) o; ++ return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType); ++ } ++ ++ @Override ++ public int hashCode() { ++ return java.util.Objects.hash(playerUUID, animalType); ++ } ++ } ++ // Purpur end + + public CraftWorld getWorld() { + return this.world; +@@ -157,6 +199,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper + this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config + this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig(((WorldDataServer) worlddatamutable).getName(), env); // Purpur ++ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur + this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; + this.world = new CraftWorld((WorldServer) this, gen, env); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index fe4e91ef5fca328143c2598ac5725babc5061a41..d9e437014728111b17b3fdb9e723584a264b7672 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -208,6 +208,7 @@ public class PurpurWorldConfig { + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + public int raidCooldownSeconds = 0; ++ public int animalBreedingCooldownSeconds = 0; + private void miscGameplayMechanicsSettings() { + useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); +@@ -219,6 +220,7 @@ public class PurpurWorldConfig { + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); ++ animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds); + } + + public boolean catSpawning; +diff --git a/src/main/java/net/pl3x/purpur/command/PurpurCommand.java b/src/main/java/net/pl3x/purpur/command/PurpurCommand.java +index 4904be939c7a4b1d1583fd7b6232c930b79caba6..860d07cd686e0a6e3eebf2deaf6bcecc1fb9dfd2 100644 +--- a/src/main/java/net/pl3x/purpur/command/PurpurCommand.java ++++ b/src/main/java/net/pl3x/purpur/command/PurpurCommand.java +@@ -49,6 +49,7 @@ public class PurpurCommand extends Command { + PurpurConfig.init((File) console.options.valueOf("purpur-settings")); + for (WorldServer world : console.getWorlds()) { + world.purpurConfig.init(); ++ world.resetBreedingCooldowns(); + } + console.server.reloadCount++; + diff --git a/patches/Purpur/patches/server/0129-Make-animal-breeding-times-configurable.patch b/patches/Purpur/patches/server/0129-Make-animal-breeding-times-configurable.patch new file mode 100644 index 00000000..c9d4f8eb --- /dev/null +++ b/patches/Purpur/patches/server/0129-Make-animal-breeding-times-configurable.patch @@ -0,0 +1,639 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sun, 15 Nov 2020 02:18:15 -0800 +Subject: [PATCH] Make animal breeding times configurable + + +diff --git a/src/main/java/net/minecraft/server/EntityAnimal.java b/src/main/java/net/minecraft/server/EntityAnimal.java +index d9f9e2235d091e14e5d34bb9a3273e7f56e94295..dd6725debe0cf72dce13b6f2cac0556060e0eb41 100644 +--- a/src/main/java/net/minecraft/server/EntityAnimal.java ++++ b/src/main/java/net/minecraft/server/EntityAnimal.java +@@ -13,6 +13,7 @@ public abstract class EntityAnimal extends EntityAgeable { + public int loveTicks; + public UUID breedCause; + public ItemStack breedItem; // CraftBukkit - Add breedItem variable ++ abstract int getPurpurBreedTime(); // Purpur + + protected EntityAnimal(EntityTypes entitytypes, World world) { + super(entitytypes, world); +@@ -234,8 +235,10 @@ public abstract class EntityAnimal extends EntityAgeable { + CriterionTriggers.o.a(entityplayer, this, entityanimal, entityageable); + } + +- this.setAgeRaw(6000); +- entityanimal.setAgeRaw(6000); ++ // Purpur start ++ this.setAgeRaw(this.getPurpurBreedTime()); ++ entityanimal.setAgeRaw(entityanimal.getPurpurBreedTime()); ++ // Purpur end + this.resetLove(); + entityanimal.resetLove(); + entityageable.setBaby(true); +diff --git a/src/main/java/net/minecraft/server/EntityBee.java b/src/main/java/net/minecraft/server/EntityBee.java +index d8354ec4d19fc3fbddc2551ee217acb137482e63..ded4e10f5082fb5aa25368d9035affba287c3345 100644 +--- a/src/main/java/net/minecraft/server/EntityBee.java ++++ b/src/main/java/net/minecraft/server/EntityBee.java +@@ -100,6 +100,11 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + setMot(mot.a(0.9D)); + } + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.beeBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCat.java b/src/main/java/net/minecraft/server/EntityCat.java +index 700314b02abb355e6a600d744887a705cbcfb4e4..8346db6c8521064ff51445a0da747a2d70d14633 100644 +--- a/src/main/java/net/minecraft/server/EntityCat.java ++++ b/src/main/java/net/minecraft/server/EntityCat.java +@@ -58,6 +58,11 @@ public class EntityCat extends EntityTameableAnimal { + setSleepingWithOwner(false); + setHeadDown(false); + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.catBreedingTicks; ++ } + // Purpur end + + public MinecraftKey eU() { +diff --git a/src/main/java/net/minecraft/server/EntityChicken.java b/src/main/java/net/minecraft/server/EntityChicken.java +index ee59a9f272a9caebec8f2329e1e4b22ddd27a0f9..2e1dc047459889aea85a79eaa04e8fe1a80e5b9e 100644 +--- a/src/main/java/net/minecraft/server/EntityChicken.java ++++ b/src/main/java/net/minecraft/server/EntityChicken.java +@@ -34,6 +34,11 @@ public class EntityChicken extends EntityAnimal { + this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(2.0D); + } + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.chickenBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java +index 1219b0aa9c62bc9a1bda45cc9e9a27f14a28fe2e..63497ca0266073dc0a16b7dc22641d08c3eaf400 100644 +--- a/src/main/java/net/minecraft/server/EntityCow.java ++++ b/src/main/java/net/minecraft/server/EntityCow.java +@@ -21,6 +21,11 @@ public class EntityCow extends EntityAnimal { + public boolean isRidableInWater() { + return world.purpurConfig.cowRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.cowBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityFox.java b/src/main/java/net/minecraft/server/EntityFox.java +index f5defe4713c6be7d32fb2116110516717460284e..e87f5aeb97fc35ff2b3464f31a2ec18432774cd4 100644 +--- a/src/main/java/net/minecraft/server/EntityFox.java ++++ b/src/main/java/net/minecraft/server/EntityFox.java +@@ -86,6 +86,11 @@ public class EntityFox extends EntityAnimal { + super.onDismount(entityhuman); + setCanPickupLoot(true); + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.foxBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHoglin.java b/src/main/java/net/minecraft/server/EntityHoglin.java +index 548ff4449faca0abdf72487276fe49207bacfe17..a1578aade4a535144b5e40277c902f2e9ab9e940 100644 +--- a/src/main/java/net/minecraft/server/EntityHoglin.java ++++ b/src/main/java/net/minecraft/server/EntityHoglin.java +@@ -30,6 +30,11 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin { + public boolean isRidableInWater() { + return world.purpurConfig.hoglinRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.hoglinBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorse.java b/src/main/java/net/minecraft/server/EntityHorse.java +index 4ffc61acdff8c51dc9b111e3024c828fb5386118..669bce5d9806c80bddc247fe103ff20dc6aaa8a5 100644 +--- a/src/main/java/net/minecraft/server/EntityHorse.java ++++ b/src/main/java/net/minecraft/server/EntityHorse.java +@@ -17,6 +17,11 @@ public class EntityHorse extends EntityHorseAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.horseRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.horseBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorseDonkey.java b/src/main/java/net/minecraft/server/EntityHorseDonkey.java +index cb8aee5691ff4ecaa6ae60f1637b1852d3b6c162..f6421bb45c5e6adf39fdc085efe2b2f500b76c0c 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseDonkey.java ++++ b/src/main/java/net/minecraft/server/EntityHorseDonkey.java +@@ -13,6 +13,11 @@ public class EntityHorseDonkey extends EntityHorseChestedAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.donkeyRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.donkeyBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorseMule.java b/src/main/java/net/minecraft/server/EntityHorseMule.java +index 243aeb736e350418e9476819bbfec0e7ab59f92f..30cbc505d2b0f4d3247edfd271de8daab023eb2a 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseMule.java ++++ b/src/main/java/net/minecraft/server/EntityHorseMule.java +@@ -13,6 +13,11 @@ public class EntityHorseMule extends EntityHorseChestedAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.muleRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.muleBreedingTicks; ++ } + // Purpur end + @Override + protected SoundEffect getSoundAmbient() { +diff --git a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java +index e2c6a5807a4554a7eebb148e40f1f8a1d979df5e..408db52cacbdfbca8af0a6a8e913b0128a3f5a76 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java ++++ b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java +@@ -22,6 +22,11 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + public boolean isTamed() { + return true; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return 6000; ++ } + // Purpur end + + public static AttributeProvider.Builder eL() { +diff --git a/src/main/java/net/minecraft/server/EntityHorseZombie.java b/src/main/java/net/minecraft/server/EntityHorseZombie.java +index 559ba50977147b8e2a0e7c1e7dc281faabd7f292..2121a6c979ba2ea7cb596ca6081750d2f8c7df9f 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseZombie.java ++++ b/src/main/java/net/minecraft/server/EntityHorseZombie.java +@@ -18,6 +18,11 @@ public class EntityHorseZombie extends EntityHorseAbstract { + public boolean isTamed() { + return true; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return 6000; ++ } + // Purpur end + + public static AttributeProvider.Builder eL() { +diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java +index 1099277868f92fdaf4b0ec3a982f26f20ead7369..3bc6e6df9e0107debe5b15f5f7aad97ad336f304 100644 +--- a/src/main/java/net/minecraft/server/EntityLlama.java ++++ b/src/main/java/net/minecraft/server/EntityLlama.java +@@ -57,6 +57,11 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + public boolean hasSaddle() { + return super.hasSaddle() || (isTamed() && getColor() != null); + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.llamaBreedingTicks; ++ } + // Purpur end + + public void setStrength(int i) { +diff --git a/src/main/java/net/minecraft/server/EntityMushroomCow.java b/src/main/java/net/minecraft/server/EntityMushroomCow.java +index 7966b34f8d202d2260a35baa4cd594e4def89257..eb1f95d8bae4bc2580849614ba879dd1a8792ecb 100644 +--- a/src/main/java/net/minecraft/server/EntityMushroomCow.java ++++ b/src/main/java/net/minecraft/server/EntityMushroomCow.java +@@ -30,6 +30,11 @@ public class EntityMushroomCow extends EntityCow implements IShearable { + public boolean isRidableInWater() { + return world.purpurConfig.mooshroomRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.mooshroomBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityOcelot.java b/src/main/java/net/minecraft/server/EntityOcelot.java +index 2f8275cd6b3cde0d3f949219f67ba7f0e0031dc3..a5be10dfb0de08b0d97265278b1f11ad1e94b821 100644 +--- a/src/main/java/net/minecraft/server/EntityOcelot.java ++++ b/src/main/java/net/minecraft/server/EntityOcelot.java +@@ -26,6 +26,11 @@ public class EntityOcelot extends EntityAnimal { + public boolean isRidableInWater() { + return world.purpurConfig.ocelotRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.ocelotBreedingTicks; ++ } + // Purpur end + + private boolean isTrusting() { +diff --git a/src/main/java/net/minecraft/server/EntityPanda.java b/src/main/java/net/minecraft/server/EntityPanda.java +index eafae5516b9b5d51aa943796557926cf61476d2b..c70180fddb829419b9cc5188766e9130f9b8a94a 100644 +--- a/src/main/java/net/minecraft/server/EntityPanda.java ++++ b/src/main/java/net/minecraft/server/EntityPanda.java +@@ -65,6 +65,11 @@ public class EntityPanda extends EntityAnimal { + this.setEating(false); + this.setLayingOnBack(false); + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.pandaBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityParrot.java b/src/main/java/net/minecraft/server/EntityParrot.java +index e402d4a77b57b8b12b7575a9793c30d7acfa7fb0..398e92bf7053c411bd98626efe4261e15256d3ee 100644 +--- a/src/main/java/net/minecraft/server/EntityParrot.java ++++ b/src/main/java/net/minecraft/server/EntityParrot.java +@@ -115,6 +115,11 @@ public class EntityParrot extends EntityPerchable implements EntityBird { + setMot(mot.a(0.9D)); + } + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return 6000; ++ } + // Purpur end + + @Nullable +diff --git a/src/main/java/net/minecraft/server/EntityPig.java b/src/main/java/net/minecraft/server/EntityPig.java +index dade0bb29422ebd68fae0edb74cbbf6d3ab89d64..7172e8cabf5b715ae9a1087b0d11e6cee81ea6e4 100644 +--- a/src/main/java/net/minecraft/server/EntityPig.java ++++ b/src/main/java/net/minecraft/server/EntityPig.java +@@ -29,6 +29,11 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable { + public boolean isRidableInWater() { + return world.purpurConfig.pigRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.pigBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPolarBear.java b/src/main/java/net/minecraft/server/EntityPolarBear.java +index 3d649843f565d2c8820b525c199bd2b9f9120cc7..40395dd7ea515e51a189d014a3274d15dc1d8ee6 100644 +--- a/src/main/java/net/minecraft/server/EntityPolarBear.java ++++ b/src/main/java/net/minecraft/server/EntityPolarBear.java +@@ -67,6 +67,11 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + return this.isInLove() && polarbear.isInLove(); + } + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.polarBearBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java +index 95f4592944a53aab0ff9843ae8e7c9b9cd0201c7..b7f4d61f0038d865ef9ee3c14f6dc146f8de5ac4 100644 +--- a/src/main/java/net/minecraft/server/EntityRabbit.java ++++ b/src/main/java/net/minecraft/server/EntityRabbit.java +@@ -30,6 +30,11 @@ public class EntityRabbit extends EntityAnimal { + public boolean isRidableInWater() { + return world.purpurConfig.rabbitRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.rabbitBreedingTicks; ++ } + // Purpur end + + // CraftBukkit start - code from constructor +diff --git a/src/main/java/net/minecraft/server/EntitySheep.java b/src/main/java/net/minecraft/server/EntitySheep.java +index a151d4295c02930687a23212647de60cce5405ca..32130c0681501e3e5a47b199f0bb39daac416ed3 100644 +--- a/src/main/java/net/minecraft/server/EntitySheep.java ++++ b/src/main/java/net/minecraft/server/EntitySheep.java +@@ -66,6 +66,11 @@ public class EntitySheep extends EntityAnimal implements IShearable { + public boolean isRidableInWater() { + return world.purpurConfig.sheepRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.sheepBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityStrider.java b/src/main/java/net/minecraft/server/EntityStrider.java +index 172867f50d0dba45a296b029c8fa85f1a19a49dc..9ab1b5af68774fa4d4da89195fdb91172370d43d 100644 +--- a/src/main/java/net/minecraft/server/EntityStrider.java ++++ b/src/main/java/net/minecraft/server/EntityStrider.java +@@ -38,6 +38,11 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab + public boolean isRidableInWater() { + return world.purpurConfig.striderRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.striderBreedingTicks; ++ } + // Purpur end + + public static boolean c(EntityTypes entitytypes, GeneratorAccess generatoraccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { +diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java +index 2b34e6cf3b86319bd2875d92b63902889fec32a8..067f7f28b02b388d56b93b1ed8274799757196e6 100644 +--- a/src/main/java/net/minecraft/server/EntityTurtle.java ++++ b/src/main/java/net/minecraft/server/EntityTurtle.java +@@ -37,6 +37,11 @@ public class EntityTurtle extends EntityAnimal { + public boolean isRidableInWater() { + return world.purpurConfig.turtleRidableInWater; + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.turtleBreedingTicks; ++ } + // Purpur end + + public void setHomePos(BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java +index 9ae7168595dd66860e09ef87f946b18b010e54b1..6c25f667eecdf345289a0dbf885c9d71c6a26958 100644 +--- a/src/main/java/net/minecraft/server/EntityWolf.java ++++ b/src/main/java/net/minecraft/server/EntityWolf.java +@@ -48,6 +48,11 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + super.onMount(entityhuman); + setSitting(false); + } ++ ++ @Override ++ int getPurpurBreedTime() { ++ return this.world.purpurConfig.wolfBreedingTicks; ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index d9e437014728111b17b3fdb9e723584a264b7672..705e8153fc6ca92fa6f0132498027d09ff8e447f 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -422,10 +422,12 @@ public class PurpurWorldConfig { + public boolean beeRidable = false; + public boolean beeRidableInWater = false; + public double beeMaxY = 256D; ++ public int beeBreedingTicks = 6000; + private void beeSettings() { + beeRidable = getBoolean("mobs.bee.ridable", beeRidable); + beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); + beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); ++ beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); + } + + public boolean blazeRidable = false; +@@ -442,12 +444,14 @@ public class PurpurWorldConfig { + public int catSpawnDelay = 1200; + public int catSpawnSwampHutScanRange = 16; + public int catSpawnVillageScanRange = 48; ++ public int catBreedingTicks = 6000; + private void catSettings() { + catRidable = getBoolean("mobs.cat.ridable", catRidable); + catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); + catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); + catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); + catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); ++ catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks); + } + + public boolean caveSpiderRidable = false; +@@ -460,10 +464,12 @@ public class PurpurWorldConfig { + public boolean chickenRidable = false; + public boolean chickenRidableInWater = false; + public boolean chickenRetaliate = false; ++ public int chickenBreedingTicks = 6000; + private void chickenSettings() { + chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); + chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); ++ chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks); + } + + public boolean codRidable = false; +@@ -474,10 +480,12 @@ public class PurpurWorldConfig { + public boolean cowRidable = false; + public boolean cowRidableInWater = false; + public int cowFeedMushrooms = 0; ++ public int cowBreedingTicks = 6000; + private void cowSettings() { + cowRidable = getBoolean("mobs.cow.ridable", cowRidable); + cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); ++ cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks); + } + + public boolean creeperRidable = false; +@@ -505,8 +513,10 @@ public class PurpurWorldConfig { + } + + public boolean donkeyRidableInWater = false; ++ public int donkeyBreedingTicks = 6000; + private void donkeySettings() { + donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); ++ donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks); + } + + public boolean drownedRidable = false; +@@ -566,10 +576,12 @@ public class PurpurWorldConfig { + public boolean foxRidable = false; + public boolean foxRidableInWater = false; + public boolean foxTypeChangesWithTulips = false; ++ public int foxBreedingTicks = 6000; + private void foxSettings() { + foxRidable = getBoolean("mobs.fox.ridable", foxRidable); + foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); ++ foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); + } + + public boolean ghastRidable = false; +@@ -614,14 +626,18 @@ public class PurpurWorldConfig { + + public boolean hoglinRidable = false; + public boolean hoglinRidableInWater = false; ++ public int hoglinBreedingTicks = 6000; + private void hoglinSettings() { + hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); + hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); ++ hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks); + } + + public boolean horseRidableInWater = false; ++ public int horseBreedingTicks = 6000; + private void horseSettings() { + horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); ++ horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks); + } + + public boolean huskRidable = false; +@@ -666,9 +682,11 @@ public class PurpurWorldConfig { + + public boolean llamaRidable = false; + public boolean llamaRidableInWater = false; ++ public int llamaBreedingTicks = 6000; + private void llamaSettings() { + llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); + llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); ++ llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); + } + + public boolean llamaTraderRidable = false; +@@ -687,28 +705,36 @@ public class PurpurWorldConfig { + + public boolean mooshroomRidable = false; + public boolean mooshroomRidableInWater = false; ++ public int mooshroomBreedingTicks = 6000; + private void mooshroomSettings() { + mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); + mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); ++ mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks); + } + + public boolean muleRidableInWater = false; ++ public int muleBreedingTicks = 6000; + private void muleSettings() { + muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); ++ muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks); + } + + public boolean ocelotRidable = false; + public boolean ocelotRidableInWater = false; ++ public int ocelotBreedingTicks = 6000; + private void ocelotSettings() { + ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); + ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); ++ ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks); + } + + public boolean pandaRidable = false; + public boolean pandaRidableInWater = false; ++ public int pandaBreedingTicks = 6000; + private void pandaSettings() { + pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); + pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); ++ pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks); + } + + public boolean parrotRidable = false; +@@ -772,10 +798,12 @@ public class PurpurWorldConfig { + public boolean pigRidable = false; + public boolean pigRidableInWater = false; + public boolean pigGiveSaddleBack = false; ++ public int pigBreedingTicks = 6000; + private void pigSettings() { + pigRidable = getBoolean("mobs.pig.ridable", pigRidable); + pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); ++ pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks); + } + + public boolean piglinRidable = false; +@@ -803,12 +831,14 @@ public class PurpurWorldConfig { + public boolean polarBearRidableInWater = false; + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; ++ public int polarBearBreedingTicks = 6000; + private void polarBearSettings() { + polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); + polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); + polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); + Item item = IRegistry.ITEM.get(new MinecraftKey(polarBearBreedableItemString)); + if (item != Items.AIR) polarBearBreedableItem = item; ++ polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks); + } + + public boolean pufferfishRidable = false; +@@ -820,11 +850,13 @@ public class PurpurWorldConfig { + public boolean rabbitRidableInWater = false; + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; ++ public int rabbitBreedingTicks = 6000; + private void rabbitSettings() { + rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); + rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); ++ rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); + } + + public boolean ravagerRidable = false; +@@ -841,9 +873,11 @@ public class PurpurWorldConfig { + + public boolean sheepRidable = false; + public boolean sheepRidableInWater = false; ++ public int sheepBreedingTicks = 6000; + private void sheepSettings() { + sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); + sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); ++ sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); + } + + public boolean shulkerRidable = false; +@@ -919,9 +953,11 @@ public class PurpurWorldConfig { + + public boolean striderRidable = false; + public boolean striderRidableInWater = false; ++ public int striderBreedingTicks = 6000; + private void striderSettings() { + striderRidable = getBoolean("mobs.strider.ridable", striderRidable); + striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); ++ striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks); + } + + public boolean tropicalFishRidable = false; +@@ -931,9 +967,11 @@ public class PurpurWorldConfig { + + public boolean turtleRidable = false; + public boolean turtleRidableInWater = false; ++ public int turtleBreedingTicks = 6000; + private void turtleSettings() { + turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); + turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); ++ turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks); + } + + public boolean vexRidable = false; +@@ -1026,9 +1064,11 @@ public class PurpurWorldConfig { + + public boolean wolfRidable = false; + public boolean wolfRidableInWater = false; ++ public int wolfBreedingTicks = 6000; + private void wolfSettings() { + wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); + wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); ++ wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); + } + + public boolean zoglinRidable = false; diff --git a/patches/Purpur/patches/server/0130-Apply-display-names-from-item-forms-of-entities-to-e.patch b/patches/Purpur/patches/server/0130-Apply-display-names-from-item-forms-of-entities-to-e.patch new file mode 100644 index 00000000..de095169 --- /dev/null +++ b/patches/Purpur/patches/server/0130-Apply-display-names-from-item-forms-of-entities-to-e.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Tue, 17 Nov 2020 03:23:48 -0800 +Subject: [PATCH] Apply display names from item forms of entities to entities + and vice versa + + +diff --git a/src/main/java/net/minecraft/server/EntityArmorStand.java b/src/main/java/net/minecraft/server/EntityArmorStand.java +index 41a36ce6d446b78bdd7a4739ad372a2ee19da116..e6de89e7f57c3c130dedb8407cd4cd577d394b9a 100644 +--- a/src/main/java/net/minecraft/server/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/server/EntityArmorStand.java +@@ -553,7 +553,13 @@ public class EntityArmorStand extends EntityLiving { + } + + private void f(DamageSource damagesource) { +- drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(new ItemStack(Items.ARMOR_STAND))); // CraftBukkit - add to drops ++ // Purpur start ++ final ItemStack armorStand = new ItemStack(Items.ARMOR_STAND); ++ if (this.world.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { ++ armorStand.setName(this.getCustomName()); ++ } ++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(armorStand)); // CraftBukkit - add to drops ++ // Purpur end + this.g(damagesource); + } + +diff --git a/src/main/java/net/minecraft/server/EntityBoat.java b/src/main/java/net/minecraft/server/EntityBoat.java +index 603910a6f9ecc34be9eb2d4fb28e5c2e20aca90a..72d2eea40c37b5fa627c8deeda1802734e94f866 100644 +--- a/src/main/java/net/minecraft/server/EntityBoat.java ++++ b/src/main/java/net/minecraft/server/EntityBoat.java +@@ -155,7 +155,13 @@ public class EntityBoat extends Entity { + } + // CraftBukkit end + if (!flag && this.world.getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) { +- this.a((IMaterial) this.g()); ++ // Purpur start ++ final ItemStack boat = new ItemStack(this.getBoatItem()); ++ if (this.world.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { ++ boat.setName(this.getCustomName()); ++ } ++ this.dropItem(boat); ++ // Purpur end + } + + this.die(); +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index 8c36027dffca818ec5b23040640c344dff9037dc..f209880771209a829bbbf9afb913c4c04a248267 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -1367,7 +1367,13 @@ public abstract class EntityInsentient extends EntityLiving { + this.by = null; + if (!this.world.isClientSide && flag1) { + this.forceDrops = true; // CraftBukkit +- this.a((IMaterial) Items.LEAD); ++ // Purpur start ++ final ItemStack lead = new ItemStack(Items.LEAD); ++ if (this.world.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { ++ lead.setName(this.getCustomName()); ++ } ++ this.dropItem(lead); ++ // Purpur end + this.forceDrops = false; // CraftBukkit + } + +@@ -1443,7 +1449,13 @@ public abstract class EntityInsentient extends EntityLiving { + } + + if (this.ticksLived > 100) { +- this.a((IMaterial) Items.LEAD); ++ // Purpur start ++ final ItemStack lead = new ItemStack(Items.LEAD); ++ if (this.world.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { ++ lead.setName(this.getCustomName()); ++ } ++ this.dropItem(lead); ++ // Purpur end + this.by = null; + } + } +diff --git a/src/main/java/net/minecraft/server/EntityItemFrame.java b/src/main/java/net/minecraft/server/EntityItemFrame.java +index 8a95e698d5caa3730954ce1135b0ec37a389dd70..372be937f1cf95775e37931f326f6a77836968f3 100644 +--- a/src/main/java/net/minecraft/server/EntityItemFrame.java ++++ b/src/main/java/net/minecraft/server/EntityItemFrame.java +@@ -199,7 +199,13 @@ public class EntityItemFrame extends EntityHanging { + } + + if (flag) { +- this.a((IMaterial) Items.ITEM_FRAME); ++ // Purpur start ++ final ItemStack itemFrame = new ItemStack(Items.ITEM_FRAME); ++ if (this.world.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { ++ itemFrame.setName(this.getCustomName()); ++ } ++ this.dropItem(itemFrame); ++ // Purpur end + } + + if (!itemstack.isEmpty()) { +diff --git a/src/main/java/net/minecraft/server/EntityPainting.java b/src/main/java/net/minecraft/server/EntityPainting.java +index 4b7cd7c59fefbd56d38e0301b08d06ce92c9d8a2..d01fc8b11026536be30c8149aca253280524811f 100644 +--- a/src/main/java/net/minecraft/server/EntityPainting.java ++++ b/src/main/java/net/minecraft/server/EntityPainting.java +@@ -92,7 +92,13 @@ public class EntityPainting extends EntityHanging { + } + } + +- this.a((IMaterial) Items.PAINTING); ++ // Purpur start ++ final ItemStack painting = new ItemStack(Items.PAINTING); ++ if (this.world.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { ++ painting.setName(this.getCustomName()); ++ } ++ this.dropItem(painting); ++ // Purpur end + } + } + +diff --git a/src/main/java/net/minecraft/server/ItemArmorStand.java b/src/main/java/net/minecraft/server/ItemArmorStand.java +index c9a5d3b583076cf8f2f32b12c142beb3f5e22dc0..315faee9e35d27071a62ea1d335dfbe5351582ca 100644 +--- a/src/main/java/net/minecraft/server/ItemArmorStand.java ++++ b/src/main/java/net/minecraft/server/ItemArmorStand.java +@@ -43,6 +43,14 @@ public class ItemArmorStand extends Item { + return EnumInteractionResult.FAIL; + } + // CraftBukkit end ++ // Purpur start ++ if (itemactioncontext.getWorld().purpurConfig.persistentDroppableEntityDisplayNames && itemactioncontext.getItemStack().hasName()) { ++ entityarmorstand.setCustomName(itemactioncontext.getItemStack().getName()); ++ if (itemactioncontext.getWorld().purpurConfig.armorstandSetNameVisible) { ++ entityarmorstand.setCustomNameVisible(true); ++ } ++ } ++ // Purpur end + worldserver.addAllEntities(entityarmorstand); // Paper - moved down + world.playSound((EntityHuman) null, entityarmorstand.locX(), entityarmorstand.locY(), entityarmorstand.locZ(), SoundEffects.ENTITY_ARMOR_STAND_PLACE, SoundCategory.BLOCKS, 0.75F, 0.8F); + } +diff --git a/src/main/java/net/minecraft/server/ItemBoat.java b/src/main/java/net/minecraft/server/ItemBoat.java +index 0580ce55ec945b5bc6ce8c5d0cee13b03ccc7d1a..6183da7ad2a458f4ada288ec82fdaf097d771122 100644 +--- a/src/main/java/net/minecraft/server/ItemBoat.java ++++ b/src/main/java/net/minecraft/server/ItemBoat.java +@@ -52,6 +52,11 @@ public class ItemBoat extends Item { + + entityboat.setType(this.b); + entityboat.yaw = entityhuman.yaw; ++ // Purpur start ++ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasName()) { ++ entityboat.setCustomName(itemstack.getName()); ++ } ++ // Purpur end + if (!world.getCubes(entityboat, entityboat.getBoundingBox().g(-0.1D))) { + return InteractionResultWrapper.fail(itemstack); + } else { +diff --git a/src/main/java/net/minecraft/server/ItemHanging.java b/src/main/java/net/minecraft/server/ItemHanging.java +index a3eaeeda875d96fe4b047bd6bf993018722c96b9..f2f800087adb0238b4b672b9f6f4c8c4836f2891 100644 +--- a/src/main/java/net/minecraft/server/ItemHanging.java ++++ b/src/main/java/net/minecraft/server/ItemHanging.java +@@ -26,7 +26,7 @@ public class ItemHanging extends Item { + return EnumInteractionResult.FAIL; + } else { + World world = itemactioncontext.getWorld(); +- Object object; ++ Entity object; // Purpur + + if (this.a == EntityTypes.PAINTING) { + object = new EntityPainting(world, blockposition1, enumdirection); +@@ -42,6 +42,11 @@ public class ItemHanging extends Item { + + if (nbttagcompound != null) { + EntityTypes.a(world, entityhuman, (Entity) object, nbttagcompound); ++ // Purpur start ++ if (itemactioncontext.getWorld().purpurConfig.persistentDroppableEntityDisplayNames && itemactioncontext.getItemStack().hasName()) { ++ object.setCustomName(itemactioncontext.getItemStack().getName()); ++ } ++ // Purpur end + } + + if (((EntityHanging) object).survives()) { +diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java +index a4edfb02fd350433020b0f3699726b6127ab9933..3f9062d8eca3ce53c0fb9e9e40330aa4e3296c9a 100644 +--- a/src/main/java/net/minecraft/server/ItemStack.java ++++ b/src/main/java/net/minecraft/server/ItemStack.java +@@ -733,6 +733,7 @@ public final class ItemStack { + return this.getItem().h(this); + } + ++ public ItemStack setName(@Nullable IChatBaseComponent component) { return this.a(component); } // Purpur - OBFHELPER + public ItemStack a(@Nullable IChatBaseComponent ichatbasecomponent) { + NBTTagCompound nbttagcompound = this.a("display"); + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 705e8153fc6ca92fa6f0132498027d09ff8e447f..9be0daea2efb9debec13e8a695e540798f39bcce 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -83,8 +83,10 @@ public class PurpurWorldConfig { + return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); + } + ++ public boolean armorstandSetNameVisible = false; + public float armorstandStepHeight = 0.0F; + private void armorstandSettings() { ++ armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); + } + +@@ -205,6 +207,7 @@ public class PurpurWorldConfig { + public boolean entitiesCanUsePortals = true; + public boolean milkCuresBadOmen = true; + public boolean persistentTileEntityDisplayNames = false; ++ public boolean persistentDroppableEntityDisplayNames = false; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + public int raidCooldownSeconds = 0; +@@ -217,6 +220,7 @@ public class PurpurWorldConfig { + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); ++ persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); diff --git a/patches/Purpur/patches/server/0131-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch b/patches/Purpur/patches/server/0131-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch new file mode 100644 index 00000000..c46d099a --- /dev/null +++ b/patches/Purpur/patches/server/0131-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Tue, 17 Nov 2020 13:12:09 -0800 +Subject: [PATCH] Set name visible when using a Name Tag on an Armor Stand + + +diff --git a/src/main/java/net/minecraft/server/ItemNameTag.java b/src/main/java/net/minecraft/server/ItemNameTag.java +index 01163ce38602f9345f00ee0535b4e73be7c6d735..a7efce97318fcf95d98f33ad4ac2da69a1ba0df0 100644 +--- a/src/main/java/net/minecraft/server/ItemNameTag.java ++++ b/src/main/java/net/minecraft/server/ItemNameTag.java +@@ -11,6 +11,11 @@ public class ItemNameTag extends Item { + if (itemstack.hasName() && !(entityliving instanceof EntityHuman)) { + if (!entityhuman.world.isClientSide && entityliving.isAlive()) { + entityliving.setCustomName(itemstack.getName()); ++ // Purpur start ++ if (entityhuman.world.purpurConfig.armorstandFixNametags && entityliving instanceof EntityArmorStand) { ++ entityliving.setCustomNameVisible(true); ++ } ++ // Purpur end + if (entityliving instanceof EntityInsentient) { + ((EntityInsentient) entityliving).setPersistent(); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 9be0daea2efb9debec13e8a695e540798f39bcce..29c5a182aef7631a33f691c312d83f08cc383ec4 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -84,9 +84,11 @@ public class PurpurWorldConfig { + } + + public boolean armorstandSetNameVisible = false; ++ public boolean armorstandFixNametags = false; + public float armorstandStepHeight = 0.0F; + private void armorstandSettings() { + armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); ++ armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags); + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); + } + diff --git a/patches/Purpur/patches/server/0132-Add-twisting-and-weeping-vines-growth-rates.patch b/patches/Purpur/patches/server/0132-Add-twisting-and-weeping-vines-growth-rates.patch new file mode 100644 index 00000000..8e156ee7 --- /dev/null +++ b/patches/Purpur/patches/server/0132-Add-twisting-and-weeping-vines-growth-rates.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 22 Nov 2020 06:02:32 -0600 +Subject: [PATCH] Add twisting and weeping vines growth rates + + +diff --git a/src/main/java/net/minecraft/server/BlockGrowingTop.java b/src/main/java/net/minecraft/server/BlockGrowingTop.java +index 7963411be990fed8eb0ffca3eba35d15a9b8d7bd..6c084ad5cda41425eed04465d942f6a73968cd61 100644 +--- a/src/main/java/net/minecraft/server/BlockGrowingTop.java ++++ b/src/main/java/net/minecraft/server/BlockGrowingTop.java +@@ -23,9 +23,11 @@ public abstract class BlockGrowingTop extends BlockGrowingAbstract implements IB + return (Integer) iblockdata.get(BlockGrowingTop.d) < 25; + } + ++ public abstract double getGrowthModifier(WorldServer worldserver); // Purpur ++ + @Override + public void tick(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { +- if ((Integer) iblockdata.get(BlockGrowingTop.d) < 25 && random.nextDouble() < (100.0D / worldserver.spigotConfig.kelpModifier) * this.e) { // Spigot ++ if ((Integer) iblockdata.get(BlockGrowingTop.d) < 25 && random.nextDouble() < (100.0D / getGrowthModifier(worldserver)) * this.e) { // Spigot // Purpur + BlockPosition blockposition1 = blockposition.shift(this.a); + + if (this.h(worldserver.getType(blockposition1))) { +diff --git a/src/main/java/net/minecraft/server/BlockKelp.java b/src/main/java/net/minecraft/server/BlockKelp.java +index a243aaed58454ae304c988df8a8a090a8236075e..2a7a6e5943f2ff87815c398ffec01bb78d320690 100644 +--- a/src/main/java/net/minecraft/server/BlockKelp.java ++++ b/src/main/java/net/minecraft/server/BlockKelp.java +@@ -53,4 +53,10 @@ public class BlockKelp extends BlockGrowingTop implements IFluidContainer { + public Fluid d(IBlockData iblockdata) { + return FluidTypes.WATER.a(false); + } ++ ++ // Purpur start ++ public double getGrowthModifier(WorldServer worldserver) { ++ return worldserver.spigotConfig.kelpModifier; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/BlockTwistingVines.java b/src/main/java/net/minecraft/server/BlockTwistingVines.java +index be381674632c49d7465dd7d52084b52f45194b54..146638111c56ec81ab46b514d45a7cc8aac2b36a 100644 +--- a/src/main/java/net/minecraft/server/BlockTwistingVines.java ++++ b/src/main/java/net/minecraft/server/BlockTwistingVines.java +@@ -24,4 +24,10 @@ public class BlockTwistingVines extends BlockGrowingTop { + protected boolean h(IBlockData iblockdata) { + return BlockNetherVinesUtil.a(iblockdata); + } ++ ++ // Purpur start ++ public double getGrowthModifier(WorldServer worldserver) { ++ return worldserver.purpurConfig.twistingVinesGrowthModifier; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/BlockWeepingVines.java b/src/main/java/net/minecraft/server/BlockWeepingVines.java +index 23dca1940375d243531fc4a891f04e937ae2f48f..94ffadb91fec65a721cf0c8fa98bad708a2ca269 100644 +--- a/src/main/java/net/minecraft/server/BlockWeepingVines.java ++++ b/src/main/java/net/minecraft/server/BlockWeepingVines.java +@@ -24,4 +24,10 @@ public class BlockWeepingVines extends BlockGrowingTop { + protected boolean h(IBlockData iblockdata) { + return BlockNetherVinesUtil.a(iblockdata); + } ++ ++ // Purpur start ++ public double getGrowthModifier(WorldServer worldserver) { ++ return worldserver.purpurConfig.weepingVinesGrowthModifier; ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 29c5a182aef7631a33f691c312d83f08cc383ec4..5d56d8efaed6503a59959b431bab4096a8beaee7 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -407,6 +407,16 @@ public class PurpurWorldConfig { + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + } + ++ public double twistingVinesGrowthModifier = 0.10D; ++ private void twistingVinesSettings() { ++ twistingVinesGrowthModifier = getDouble("blocks.twisting_vines.growth-modifier", twistingVinesGrowthModifier); ++ } ++ ++ public double weepingVinesGrowthModifier = 0.10D; ++ private void weepingVinesSettings() { ++ weepingVinesGrowthModifier = getDouble("blocks.weeping_vines.growth-modifier", weepingVinesGrowthModifier); ++ } ++ + public boolean babiesAreRidable = true; + public boolean untamedTamablesAreRidable = true; + public boolean useNightVisionWhenRiding = false; diff --git a/patches/Purpur/patches/server/0133-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch b/patches/Purpur/patches/server/0133-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch new file mode 100644 index 00000000..def1ece8 --- /dev/null +++ b/patches/Purpur/patches/server/0133-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 22 Nov 2020 20:13:27 -0600 +Subject: [PATCH] Kelp weeping and twisting vines configurable max growth age + + +diff --git a/src/main/java/net/minecraft/server/BlockGrowingTop.java b/src/main/java/net/minecraft/server/BlockGrowingTop.java +index 6c084ad5cda41425eed04465d942f6a73968cd61..6d49422c3358b06369e1a31ee5580ff4a0057c5f 100644 +--- a/src/main/java/net/minecraft/server/BlockGrowingTop.java ++++ b/src/main/java/net/minecraft/server/BlockGrowingTop.java +@@ -15,7 +15,7 @@ public abstract class BlockGrowingTop extends BlockGrowingAbstract implements IB + + @Override + public IBlockData a(GeneratorAccess generatoraccess) { +- return (IBlockData) this.getBlockData().set(BlockGrowingTop.d, generatoraccess.getRandom().nextInt(25)); ++ return (IBlockData) this.getBlockData().set(BlockGrowingTop.d, generatoraccess.getRandom().nextInt(getMaxGrowthAge(generatoraccess.getMinecraftWorld()))); // Purpur + } + + @Override +@@ -25,9 +25,11 @@ public abstract class BlockGrowingTop extends BlockGrowingAbstract implements IB + + public abstract double getGrowthModifier(WorldServer worldserver); // Purpur + ++ public abstract int getMaxGrowthAge(WorldServer worldserver); // Purpur ++ + @Override + public void tick(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, Random random) { +- if ((Integer) iblockdata.get(BlockGrowingTop.d) < 25 && random.nextDouble() < (100.0D / getGrowthModifier(worldserver)) * this.e) { // Spigot // Purpur ++ if ((Integer) iblockdata.get(BlockGrowingTop.d) < getMaxGrowthAge(worldserver) && random.nextDouble() < (100.0D / getGrowthModifier(worldserver)) * this.e) { // Spigot // Purpur + BlockPosition blockposition1 = blockposition.shift(this.a); + + if (this.h(worldserver.getType(blockposition1))) { +@@ -72,13 +74,13 @@ public abstract class BlockGrowingTop extends BlockGrowingAbstract implements IB + @Override + public void a(WorldServer worldserver, Random random, BlockPosition blockposition, IBlockData iblockdata) { + BlockPosition blockposition1 = blockposition.shift(this.a); +- int i = Math.min((Integer) iblockdata.get(BlockGrowingTop.d) + 1, 25); ++ int i = Math.min((Integer) iblockdata.get(BlockGrowingTop.d) + 1, getMaxGrowthAge(worldserver)); // Purpur + int j = this.a(random); + + for (int k = 0; k < j && this.h(worldserver.getType(blockposition1)); ++k) { + worldserver.setTypeUpdate(blockposition1, (IBlockData) iblockdata.set(BlockGrowingTop.d, i)); + blockposition1 = blockposition1.shift(this.a); +- i = Math.min(i + 1, 25); ++ i = Math.min(i + 1, getMaxGrowthAge(worldserver)); // Purpur + } + + } +diff --git a/src/main/java/net/minecraft/server/BlockKelp.java b/src/main/java/net/minecraft/server/BlockKelp.java +index 2a7a6e5943f2ff87815c398ffec01bb78d320690..b35c115e34cf5f7a24cd26ca31c19a63c82e0080 100644 +--- a/src/main/java/net/minecraft/server/BlockKelp.java ++++ b/src/main/java/net/minecraft/server/BlockKelp.java +@@ -58,5 +58,9 @@ public class BlockKelp extends BlockGrowingTop implements IFluidContainer { + public double getGrowthModifier(WorldServer worldserver) { + return worldserver.spigotConfig.kelpModifier; + } ++ ++ public int getMaxGrowthAge(WorldServer worldserver) { ++ return worldserver.purpurConfig.kelpMaxGrowthAge; ++ } + // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/BlockTwistingVines.java b/src/main/java/net/minecraft/server/BlockTwistingVines.java +index 146638111c56ec81ab46b514d45a7cc8aac2b36a..71b9b7183df5702f2753c7372d0c491b2230b365 100644 +--- a/src/main/java/net/minecraft/server/BlockTwistingVines.java ++++ b/src/main/java/net/minecraft/server/BlockTwistingVines.java +@@ -29,5 +29,9 @@ public class BlockTwistingVines extends BlockGrowingTop { + public double getGrowthModifier(WorldServer worldserver) { + return worldserver.purpurConfig.twistingVinesGrowthModifier; + } ++ ++ public int getMaxGrowthAge(WorldServer worldserver) { ++ return worldserver.purpurConfig.twistingVinesMaxGrowthAge; ++ } + // Purpur end + } +diff --git a/src/main/java/net/minecraft/server/BlockWeepingVines.java b/src/main/java/net/minecraft/server/BlockWeepingVines.java +index 94ffadb91fec65a721cf0c8fa98bad708a2ca269..067df63ab27ecb9fe0a0d012b16efbd546fdfff7 100644 +--- a/src/main/java/net/minecraft/server/BlockWeepingVines.java ++++ b/src/main/java/net/minecraft/server/BlockWeepingVines.java +@@ -29,5 +29,9 @@ public class BlockWeepingVines extends BlockGrowingTop { + public double getGrowthModifier(WorldServer worldserver) { + return worldserver.purpurConfig.weepingVinesGrowthModifier; + } ++ ++ public int getMaxGrowthAge(WorldServer worldserver) { ++ return worldserver.purpurConfig.weepingVinesMaxGrowthAge; ++ } + // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 5d56d8efaed6503a59959b431bab4096a8beaee7..e1641988b031c357790b9dac0b56b7766a7f0b2f 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -354,6 +354,11 @@ public class PurpurWorldConfig { + furnaceInfiniteFuel = getBoolean("blocks.furnace.infinite-fuel", furnaceInfiniteFuel); + } + ++ public int kelpMaxGrowthAge = 25; ++ private void kelpSettings() { ++ kelpMaxGrowthAge = getInt("blocks.kelp.max-growth-age", kelpMaxGrowthAge); ++ } ++ + public boolean lavaInfinite = false; + public int lavaInfiniteRequiredSources = 2; + public int lavaSpeedNether = 10; +@@ -408,13 +413,17 @@ public class PurpurWorldConfig { + } + + public double twistingVinesGrowthModifier = 0.10D; ++ public int twistingVinesMaxGrowthAge = 25; + private void twistingVinesSettings() { + twistingVinesGrowthModifier = getDouble("blocks.twisting_vines.growth-modifier", twistingVinesGrowthModifier); ++ twistingVinesMaxGrowthAge = getInt("blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge); + } + + public double weepingVinesGrowthModifier = 0.10D; ++ public int weepingVinesMaxGrowthAge = 25; + private void weepingVinesSettings() { + weepingVinesGrowthModifier = getDouble("blocks.weeping_vines.growth-modifier", weepingVinesGrowthModifier); ++ weepingVinesMaxGrowthAge = getInt("blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge); + } + + public boolean babiesAreRidable = true; diff --git a/patches/Purpur/patches/server/0134-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch b/patches/Purpur/patches/server/0134-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch new file mode 100644 index 00000000..cc81f923 --- /dev/null +++ b/patches/Purpur/patches/server/0134-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sun, 22 Nov 2020 22:17:53 -0800 +Subject: [PATCH] Add config for allowing Endermen to despawn even while + holding a block + +This should help to reduce the amount of dirt, gravel, grass, and etc. +that Endermen like to randomly place all over the world. + +diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java +index 995849212c25568d3aa28ada78babf8b8e669960..acb2b3ed04ea0bf19335415310ce22cd076dd92a 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderman.java ++++ b/src/main/java/net/minecraft/server/EntityEnderman.java +@@ -372,7 +372,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + + @Override + public boolean isSpecialPersistence() { +- return super.isSpecialPersistence() || this.getCarried() != null; ++ return super.isSpecialPersistence() || (!this.world.purpurConfig.endermanDespawnEvenWithBlock && this.getCarried() != null); // Purpur + } + + static class PathfinderGoalEndermanPickupBlock extends PathfinderGoal { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index e1641988b031c357790b9dac0b56b7766a7f0b2f..25145291e3dce7f75d62f6389613e070742cf99d 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -578,10 +578,12 @@ public class PurpurWorldConfig { + public boolean endermanRidable = false; + public boolean endermanRidableInWater = false; + public boolean endermanAllowGriefing = true; ++ public boolean endermanDespawnEvenWithBlock = false; + private void endermanSettings() { + endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); + endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); ++ endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); + } + + public boolean endermiteRidable = false; diff --git a/patches/Purpur/patches/server/0135-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch b/patches/Purpur/patches/server/0135-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch new file mode 100644 index 00000000..d7ac0fbc --- /dev/null +++ b/patches/Purpur/patches/server/0135-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 24 Nov 2020 04:30:46 -0600 +Subject: [PATCH] Add critical hit check to EntityDamagedByEntityEvent + + +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index aef1dc47f2025f09d650a04f6dfa867d5ea1b65a..c973a1f1aecd47f11a12c94325cc18c3307d7ab5 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -73,6 +73,7 @@ public abstract class EntityHuman extends EntityLiving { + // Paper start + public boolean affectsSpawning = true; + // Paper end ++ public boolean isCritical = false; // Purpur + + // CraftBukkit start + public boolean fauxSleeping; +@@ -1060,6 +1061,7 @@ public abstract class EntityHuman extends EntityLiving { + flag2 = flag2 && !world.paperConfig.disablePlayerCrits; // Paper + flag2 = flag2 && !this.isSprinting(); + if (flag2) { ++ this.isCritical = true; // Purpur + f *= 1.5F; + } + +@@ -1096,6 +1098,7 @@ public abstract class EntityHuman extends EntityLiving { + + Vec3D vec3d = entity.getMot(); + boolean flag5 = entity.damageEntity(DamageSource.playerAttack(this), f); ++ this.isCritical = false; // Purpur + + if (flag5) { + if (i > 0) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 55ab5e078d28de962ec450c575c7f5bb4fa7ce6f..533051ec74a64ef6893d848eb4579166d26c6bf7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1056,7 +1056,7 @@ public class CraftEventFactory { + private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled) { + EntityDamageEvent event; + if (damager != null) { +- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); ++ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, damager instanceof HumanEntity && ((EntityHuman)damager).isCritical); // Purpur + damager.processClick(EnumHand.MAIN_HAND); // Purpur + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); diff --git a/patches/Purpur/patches/server/0136-Add-configurable-snowball-damage.patch b/patches/Purpur/patches/server/0136-Add-configurable-snowball-damage.patch new file mode 100644 index 00000000..94fc37c0 --- /dev/null +++ b/patches/Purpur/patches/server/0136-Add-configurable-snowball-damage.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 24 Nov 2020 05:32:02 -0600 +Subject: [PATCH] Add configurable snowball damage + + +diff --git a/src/main/java/net/minecraft/server/EntitySnowball.java b/src/main/java/net/minecraft/server/EntitySnowball.java +index 34a5f481e6ed1357861dca15fb4013ec8484a292..d7bab4446a5a8eef98c10b1f6eb89de90365dfeb 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowball.java ++++ b/src/main/java/net/minecraft/server/EntitySnowball.java +@@ -29,7 +29,7 @@ public class EntitySnowball extends EntityProjectileThrowable { + protected void a(MovingObjectPositionEntity movingobjectpositionentity) { + super.a(movingobjectpositionentity); + Entity entity = movingobjectpositionentity.getEntity(); +- int i = entity instanceof EntityBlaze ? 3 : 0; ++ int i = entity.world.purpurConfig.snowballDamage >= 0 ? entity.world.purpurConfig.snowballDamage : entity instanceof EntityBlaze ? 3 : 0; // Purpur + + entity.damageEntity(DamageSource.projectile(this, this.getShooter()), (float) i); + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 25145291e3dce7f75d62f6389613e070742cf99d..8af4082e3e18d4d16cebb0f9ab563f938e77611e 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -202,6 +202,11 @@ public class PurpurWorldConfig { + witherSkullDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.wither_skull", witherSkullDespawnRate); + } + ++ public int snowballDamage = -1; ++ private void snowballSettings() { ++ snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage); ++ } ++ + public boolean useBetterMending = false; + public boolean boatEjectPlayersOnLand = false; + public boolean disableDropsOnCrammingDeath = false; diff --git a/patches/Purpur/patches/server/0137-Zombie-break-door-minimum-difficulty-option.patch b/patches/Purpur/patches/server/0137-Zombie-break-door-minimum-difficulty-option.patch new file mode 100644 index 00000000..4b4972c3 --- /dev/null +++ b/patches/Purpur/patches/server/0137-Zombie-break-door-minimum-difficulty-option.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 27 Nov 2020 10:33:33 -0600 +Subject: [PATCH] Zombie break door minimum difficulty option + + +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java b/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java +index 7488a12926c5ee4adc3bc1fa3973988350381544..23870a271b759a953a095df835e08ea2a09f4218 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java +@@ -13,7 +13,7 @@ public class PathfinderGoalBreakDoor extends PathfinderGoalDoorInteract { + super(entityinsentient); + this.b = -1; + this.c = -1; +- this.g = predicate; ++ this.g = entityinsentient instanceof EntityZombie ? difficulty -> testDifficulty(entity) : predicate; // Purpur + } + + public PathfinderGoalBreakDoor(EntityInsentient entityinsentient, int i, Predicate predicate) { +@@ -82,4 +82,21 @@ public class PathfinderGoalBreakDoor extends PathfinderGoalDoorInteract { + private boolean a(EnumDifficulty enumdifficulty) { + return this.g.test(enumdifficulty); + } ++ ++ // Purpur start ++ private boolean testDifficulty(Entity entity) { ++ EnumDifficulty difficulty = entity.world.getDifficulty(); ++ switch (entity.world.purpurConfig.zombieBreakDoorMinDifficulty) { ++ case PEACEFUL: ++ return difficulty == EnumDifficulty.HARD || difficulty == EnumDifficulty.NORMAL || difficulty == EnumDifficulty.EASY || difficulty == EnumDifficulty.PEACEFUL; ++ case EASY: ++ return difficulty == EnumDifficulty.HARD || difficulty == EnumDifficulty.NORMAL || difficulty == EnumDifficulty.EASY; ++ case NORMAL: ++ return difficulty == EnumDifficulty.HARD || difficulty == EnumDifficulty.NORMAL; ++ case HARD: ++ default: ++ return difficulty == EnumDifficulty.HARD; ++ } ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 8af4082e3e18d4d16cebb0f9ab563f938e77611e..c6afd32d595d8cb017ef0ff34c48b33f4e410fb5 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -2,6 +2,7 @@ package net.pl3x.purpur; + + import net.minecraft.server.Block; + import net.minecraft.server.Blocks; ++import net.minecraft.server.EnumDifficulty; + import net.minecraft.server.Explosion; + import net.minecraft.server.IRegistry; + import net.minecraft.server.Item; +@@ -1116,6 +1117,7 @@ public class PurpurWorldConfig { + public double zombieJockeyChance = 0.05D; + public boolean zombieJockeyTryExistingChickens = true; + public boolean zombieAggressiveTowardsVillagerWhenLagging = true; ++ public EnumDifficulty zombieBreakDoorMinDifficulty = EnumDifficulty.HARD; + private void zombieSettings() { + zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); + zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); +@@ -1123,6 +1125,11 @@ public class PurpurWorldConfig { + zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); + zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); + zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging); ++ try { ++ zombieBreakDoorMinDifficulty = EnumDifficulty.valueOf(getString("mobs.zombie.break-door-minimum-difficulty", zombieBreakDoorMinDifficulty.name())); ++ } catch (IllegalArgumentException ignore) { ++ zombieBreakDoorMinDifficulty = EnumDifficulty.HARD; ++ } + } + + public boolean zombieHorseCanSwim = false; diff --git a/patches/Purpur/patches/server/0138-Add-demo-command.patch b/patches/Purpur/patches/server/0138-Add-demo-command.patch new file mode 100644 index 00000000..16d4ac02 --- /dev/null +++ b/patches/Purpur/patches/server/0138-Add-demo-command.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 30 Nov 2020 03:12:04 -0600 +Subject: [PATCH] Add demo command + + +diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java +index d080bf58ebc9c1dc9d41fae7d515547bc3f26d54..b5cc099746e9f05ea69bc438bda22a5ac3ebc3c5 100644 +--- a/src/main/java/net/minecraft/server/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/server/CommandDispatcher.java +@@ -107,6 +107,7 @@ public class CommandDispatcher { + CommandIdleTimeout.a(this.b); + CommandStop.a(this.b); + CommandWhitelist.a(this.b); ++ net.pl3x.purpur.command.DemoCommand.register(getDispatcher()); // Purpur + net.pl3x.purpur.command.PingCommand.register(getDispatcher()); // Purpur + } + +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutGameStateChange.java b/src/main/java/net/minecraft/server/PacketPlayOutGameStateChange.java +index 08cbc787e2bf6587878bdeffa7248e5d23cdcf98..57d39ed441ec7be933f4fce48225f527db7e6a3c 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutGameStateChange.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutGameStateChange.java +@@ -11,7 +11,7 @@ public class PacketPlayOutGameStateChange implements Packet dispatcher) { ++ dispatcher.register(CommandDispatcher.literal("demo") ++ .requires((listener) -> { ++ return listener.hasPermission(2); ++ }) ++ .executes((context) -> { ++ return execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException())); ++ }) ++ .then(CommandDispatcher.argument("targets", ArgumentEntity.players()) ++ .executes((context) -> { ++ return execute(context.getSource(), ArgumentEntity.getPlayers(context, "targets")); ++ }) ++ ) ++ ).setPermission("bukkit.command.demo"); ++ } ++ ++ private static int execute(CommandListenerWrapper sender, Collection targets) { ++ for (EntityPlayer player : targets) { ++ PacketPlayOutGameStateChange packet = new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.demo(), 0); ++ player.playerConnection.sendPacket(packet); ++ String output = String.format(PurpurConfig.demoCommandOutput, player.getProfile().getName(), player.ping); ++ sender.sendMessage(CraftChatMessage.fromStringOrNull(output), false); ++ } ++ return targets.size(); ++ } ++} diff --git a/patches/Purpur/patches/server/0139-Left-handed-API.patch b/patches/Purpur/patches/server/0139-Left-handed-API.patch new file mode 100644 index 00000000..13a40ed8 --- /dev/null +++ b/patches/Purpur/patches/server/0139-Left-handed-API.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 30 Nov 2020 06:03:06 -0600 +Subject: [PATCH] Left handed API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index eaad222fd38a4db4074db04c931bcff7a9ca5e24..eb1d0d8e8962b5338b3ea7a306130d0af77eaca0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -85,4 +85,14 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + return getHandle().isInDaylight(); + } + // Paper end ++ ++ // Purpur start ++ public boolean isLeftHanded() { ++ return getHandle().isLeftHanded(); ++ } ++ ++ public void setLeftHanded(boolean leftHanded) { ++ getHandle().setLeftHanded(leftHanded); ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0140-Origami-Fix-ProtocolLib-issues-on-Java-15.patch b/patches/Purpur/patches/server/0140-Origami-Fix-ProtocolLib-issues-on-Java-15.patch new file mode 100644 index 00000000..59e16c6e --- /dev/null +++ b/patches/Purpur/patches/server/0140-Origami-Fix-ProtocolLib-issues-on-Java-15.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 19 Oct 2020 17:20:53 +0100 +Subject: [PATCH] Origami - Fix ProtocolLib issues on Java 15 + + +diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java +index 6a0ec0105399066dede622b45c9471b32c162cf6..548c62a838848a9183e14f91b21a9dc309d8a3b2 100644 +--- a/src/main/java/net/minecraft/server/NetworkManager.java ++++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -394,9 +394,9 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + // note: since the type is not dynamic here, we need to actually copy the old executor code + // into two branches. On conflict, just re-copy - no changes were made inside the executor code. + if (flush) { +- choice1 = () -> { ++ choice1 = new Runnable() { public void run() { // Origami - flatten lambda + if (enumprotocol != enumprotocol1) { +- this.setProtocol(enumprotocol); ++ NetworkManager.this.setProtocol(enumprotocol); // Origami - flatten lambda + } + + // Paper start +@@ -406,7 +406,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + try { + // Paper end +- ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter ++ ChannelFuture channelfuture1 = (flush) ? NetworkManager.this.channel.writeAndFlush(packet) : NetworkManager.this.channel.write(packet); // Tuinity - add flush parameter // Origami - flatten lambda + + + if (genericfuturelistener != null) { +@@ -426,12 +426,12 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + packet.onPacketDispatchFinish(player, null); + } + // Paper end +- }; ++ }}; // Origami - flatten lambda + } else { + // explicitly declare a variable to make the lambda use the type +- choice2 = () -> { ++ choice2 = new AbstractEventExecutor.LazyRunnable() { public void run() { // Origami - flatten lambda + if (enumprotocol != enumprotocol1) { +- this.setProtocol(enumprotocol); ++ NetworkManager.this.setProtocol(enumprotocol); // Origami - flatten lambda + } + + // Paper start +@@ -441,7 +441,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + try { + // Paper end +- ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter ++ ChannelFuture channelfuture1 = (flush) ? NetworkManager.this.channel.writeAndFlush(packet) : NetworkManager.this.channel.write(packet); // Tuinity - add flush parameter // Origami - flatten lambda + + + if (genericfuturelistener != null) { +@@ -461,7 +461,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + packet.onPacketDispatchFinish(player, null); + } + // Paper end +- }; ++ }}; // Origami - flatten lambda + } + this.channel.eventLoop().execute(choice1 != null ? choice1 : choice2); + // Tuinity end - optimise packets that are not flushed diff --git a/patches/Purpur/patches/server/0141-Changeable-Mob-Left-Handed-Chance.patch b/patches/Purpur/patches/server/0141-Changeable-Mob-Left-Handed-Chance.patch new file mode 100644 index 00000000..3d9e1308 --- /dev/null +++ b/patches/Purpur/patches/server/0141-Changeable-Mob-Left-Handed-Chance.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ben Kerllenevich +Date: Mon, 30 Nov 2020 11:40:11 -0500 +Subject: [PATCH] Changeable Mob Left Handed Chance + + +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index f209880771209a829bbbf9afb913c4c04a248267..b9c537104bfdc0bca0ad387c073650da169b871a 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -1136,7 +1136,7 @@ public abstract class EntityInsentient extends EntityLiving { + @Nullable + public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { + this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).addModifier(new AttributeModifier("Random spawn bonus", this.random.nextGaussian() * 0.05D, AttributeModifier.Operation.MULTIPLY_BASE)); +- if (this.random.nextFloat() < 0.05F) { ++ if (this.random.nextFloat() < this.world.purpurConfig.entityLeftHandedChance) { // Purpur + this.setLeftHanded(true); + } else { + this.setLeftHanded(false); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index c6afd32d595d8cb017ef0ff34c48b33f4e410fb5..f089b73d6450583a668902f584e88c04310bd236 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -129,8 +129,10 @@ public class PurpurWorldConfig { + } + + public int entityLifeSpan = 0; ++ public float entityLeftHandedChance = 0.05f; + private void entitySettings() { + entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); ++ entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance); + } + + public List itemImmuneToCactus = new ArrayList<>(); diff --git a/patches/Purpur/patches/server/0142-Add-boat-fall-damage-config.patch b/patches/Purpur/patches/server/0142-Add-boat-fall-damage-config.patch new file mode 100644 index 00000000..a8861fb1 --- /dev/null +++ b/patches/Purpur/patches/server/0142-Add-boat-fall-damage-config.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 30 Nov 2020 19:36:35 -0600 +Subject: [PATCH] Add boat fall damage config + + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 54853eda0c9dd56bb25ff059030d32e97e78f829..670b3d72c1d339f2dd05efbd243e7c2e896e2f79 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1030,7 +1030,16 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + if (this.isInvulnerable(damagesource)) { + return false; + } else { +- if (damagesource == DamageSource.FALL && getRootVehicle() instanceof EntityMinecartAbstract && world.purpurConfig.controllableMinecarts && !world.purpurConfig.controllableMinecartsFallDamage) return false; // Purpur ++ // Purpur start ++ if (damagesource == DamageSource.FALL) { ++ if (getRootVehicle() instanceof EntityMinecartAbstract && world.purpurConfig.controllableMinecarts && !world.purpurConfig.controllableMinecartsFallDamage) { ++ return false; ++ } ++ if (getRootVehicle() instanceof EntityBoat && !world.purpurConfig.boatsDoFallDamage) { ++ return false; ++ } ++ } ++ // Purpur end + boolean flag = this.server.j() && this.canPvP() && "fall".equals(damagesource.translationIndex); + + if (!flag && isSpawnInvulnerable() && damagesource != DamageSource.OUT_OF_WORLD) { // Purpur +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f089b73d6450583a668902f584e88c04310bd236..0609ced6d555fd09aad3d57cb9e319a85e9f7a8f 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -212,6 +212,7 @@ public class PurpurWorldConfig { + + public boolean useBetterMending = false; + public boolean boatEjectPlayersOnLand = false; ++ public boolean boatsDoFallDamage = true; + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean entitiesCanUsePortals = true; +@@ -225,6 +226,7 @@ public class PurpurWorldConfig { + private void miscGameplayMechanicsSettings() { + useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); ++ boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage); + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); diff --git a/patches/Purpur/patches/server/0143-Config-migration-disable-saving-projectiles-to-disk-.patch b/patches/Purpur/patches/server/0143-Config-migration-disable-saving-projectiles-to-disk-.patch new file mode 100644 index 00000000..ee5aeee5 --- /dev/null +++ b/patches/Purpur/patches/server/0143-Config-migration-disable-saving-projectiles-to-disk-.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Mon, 30 Nov 2020 18:30:13 -0800 +Subject: [PATCH] Config migration: disable saving projectiles to disk -> + projectile load/save limit of 0 + + +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index 9294bfab12ef88690e359ff90551c5c615cdd3dd..e928716d7a2dcb390507b746763e6f7eaae241b5 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -1,6 +1,7 @@ + package net.pl3x.purpur; + + import co.aikar.timings.TimingsManager; ++import com.destroystokyo.paper.PaperConfig; + import com.google.common.base.Throwables; + import net.minecraft.server.EntitySize; + import net.minecraft.server.EntityTypes; +@@ -132,6 +133,17 @@ public class PurpurConfig { + return config.getString(path, config.getString(path)); + } + ++ private static void migrateDisableProjectileSaving() { ++ if (PurpurConfig.version < 6) { ++ final boolean saveProjectilesToDisk = getBoolean("world-settings.default.gameplay-mechanics.save-projectiles-to-disk", true); ++ set("world-settings.default.gameplay-mechanics.save-projectiles-to-disk", null); ++ if (!saveProjectilesToDisk) { ++ PaperConfig.config.set("world-settings.default.projectile-load-save-per-chunk-limit", 0); ++ PaperConfig.saveConfig(); ++ } ++ } ++ } ++ + public static String afkBroadcastAway = "§e§o%s is now AFK"; + public static String afkBroadcastBack = "§e§o%s is no longer AFK"; + public static String afkTabListPrefix = "[AFK] "; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 0609ced6d555fd09aad3d57cb9e319a85e9f7a8f..ca988380f122b67361ce4e2ebce57bbee15e899c 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1,5 +1,6 @@ + package net.pl3x.purpur; + ++import com.destroystokyo.paper.PaperConfig; + import net.minecraft.server.Block; + import net.minecraft.server.Blocks; + import net.minecraft.server.EnumDifficulty; +@@ -84,6 +85,17 @@ public class PurpurWorldConfig { + return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); + } + ++ private void migrateDisableProjectileSaving() { ++ if (PurpurConfig.version < 6) { ++ final boolean saveProjectilesToDisk = PurpurConfig.config.getBoolean("world-settings." + worldName + ".gameplay-mechanics.save-projectiles-to-disk", true); ++ PurpurConfig.config.set("world-settings." + worldName + ".gameplay-mechanics.save-projectiles-to-disk", null); ++ if (!saveProjectilesToDisk) { ++ PaperConfig.config.set("world-settings." + worldName + ".projectile-load-save-per-chunk-limit", 0); ++ PaperConfig.saveConfig(); ++ } ++ } ++ } ++ + public boolean armorstandSetNameVisible = false; + public boolean armorstandFixNametags = false; + public float armorstandStepHeight = 0.0F; diff --git a/patches/Purpur/patches/server/0144-Snow-Golem-rate-of-fire-config.patch b/patches/Purpur/patches/server/0144-Snow-Golem-rate-of-fire-config.patch new file mode 100644 index 00000000..0ec5d67d --- /dev/null +++ b/patches/Purpur/patches/server/0144-Snow-Golem-rate-of-fire-config.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Simon Gardling +Date: Tue, 1 Dec 2020 16:50:36 -0500 +Subject: [PATCH] Snow Golem rate of fire config + +The formula used to determine the amount of ticks between shots is: + ((sqrt(distanceToTarget) / snowGolemAttackDistance) / snowGolemSnowBallModifer) * (maxShootIntervalTicks - minShootIntervalTicks) + minShootIntervalTicks + +If min-shoot-interval-ticks and max-shoot-interval-ticks are both set to +0, snow golems won't shoot any snowballs. + +diff --git a/src/main/java/net/minecraft/server/EntitySnowman.java b/src/main/java/net/minecraft/server/EntitySnowman.java +index e980da14cf4f34c87a88ffd2b908223808404966..e5b3d298f52006f39a36cfdd95097e7b4f89939a 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowman.java ++++ b/src/main/java/net/minecraft/server/EntitySnowman.java +@@ -29,7 +29,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + @Override + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur +- this.goalSelector.a(1, new PathfinderGoalArrowAttack(this, 1.25D, 20, 10.0F)); ++ this.goalSelector.a(1, new PathfinderGoalArrowAttack(this, world.purpurConfig.snowGolemAttackDistance, world.purpurConfig.snowGolemSnowBallMin, world.purpurConfig.snowGolemSnowBallMax, world.purpurConfig.snowGolemSnowBallModifier)); // Purpur - configurable snow golem snowball throwing + this.goalSelector.a(2, new PathfinderGoalRandomStrollLand(this, 1.0D, 1.0000001E-5F)); + this.goalSelector.a(3, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F)); + this.goalSelector.a(4, new PathfinderGoalRandomLookaround(this)); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index ca988380f122b67361ce4e2ebce57bbee15e899c..cce1fa84c96a52656728e3dfe2c58748af48a348 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -969,12 +969,20 @@ public class PurpurWorldConfig { + public boolean snowGolemLeaveTrailWhenRidden = false; + public boolean snowGolemDropsPumpkin = false; + public boolean snowGolemPutPumpkinBack = false; ++ public int snowGolemSnowBallMin = 20; ++ public int snowGolemSnowBallMax = 20; ++ public float snowGolemSnowBallModifier = 10.0F; ++ public double snowGolemAttackDistance = 1.25D; + private void snowGolemSettings() { + snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); + snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); + snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden); + snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); + snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); ++ snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin); ++ snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax); ++ snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); ++ snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); + } + + public boolean squidRidable = false; diff --git a/patches/Purpur/patches/server/0145-PaperPR-Config-option-for-Piglins-guarding-chests.patch b/patches/Purpur/patches/server/0145-PaperPR-Config-option-for-Piglins-guarding-chests.patch new file mode 100644 index 00000000..3dc0026a --- /dev/null +++ b/patches/Purpur/patches/server/0145-PaperPR-Config-option-for-Piglins-guarding-chests.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 2 Dec 2020 03:07:58 -0800 +Subject: [PATCH] PaperPR - Config option for Piglins guarding chests + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 4735dcba31b556fafe9c7d7440c89e940755c81f..78250d7db036198ec7119a065444c9253c1ab043 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -704,6 +704,11 @@ public class PaperWorldConfig { + zombiesTargetTurtleEggs = getBoolean("zombies-target-turtle-eggs", zombiesTargetTurtleEggs); + } + ++ public boolean piglinsGuardChests = true; ++ private void piglinsGuardChests() { ++ piglinsGuardChests = getBoolean("piglins-guard-chests", piglinsGuardChests); ++ } ++ + public boolean useEigencraftRedstone = false; + private void useEigencraftRedstone() { + useEigencraftRedstone = this.getBoolean("use-faster-eigencraft-redstone", false); +diff --git a/src/main/java/net/minecraft/server/PiglinAI.java b/src/main/java/net/minecraft/server/PiglinAI.java +index 0407fa1751d89a037da8cb01f5ceef9b9833dd18..df5aafec9a5844a1ae3e948d8a787051a8903bce 100644 +--- a/src/main/java/net/minecraft/server/PiglinAI.java ++++ b/src/main/java/net/minecraft/server/PiglinAI.java +@@ -357,6 +357,7 @@ public class PiglinAI { + } + + public static void a(EntityHuman entityhuman, boolean flag) { ++ if (!entityhuman.world.paperConfig.piglinsGuardChests) return; // Paper + List list = entityhuman.world.a(EntityPiglin.class, entityhuman.getBoundingBox().g(16.0D)); // CraftBukkit - decompile error + + list.stream().filter(PiglinAI::d).filter((entitypiglin) -> { diff --git a/patches/Purpur/patches/server/0146-EMC-Configurable-disable-give-dropping.patch b/patches/Purpur/patches/server/0146-EMC-Configurable-disable-give-dropping.patch new file mode 100644 index 00000000..bd6e539a --- /dev/null +++ b/patches/Purpur/patches/server/0146-EMC-Configurable-disable-give-dropping.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 14 Jan 2016 00:49:14 -0500 +Subject: [PATCH] EMC - Configurable disable give dropping + +Modified version of a patch by Aikar from EMC. Adds a config option in +purpur.yml to disable the /give command from dropping items on the +floor when a player's inventory is full. + +diff --git a/src/main/java/net/minecraft/server/CommandGive.java b/src/main/java/net/minecraft/server/CommandGive.java +index 1d22c45af884a917e77e02c272fcbae74794200c..7bf90f27fdc48440ef229cca0e100d2c5c0ebef7 100644 +--- a/src/main/java/net/minecraft/server/CommandGive.java ++++ b/src/main/java/net/minecraft/server/CommandGive.java +@@ -35,6 +35,7 @@ public class CommandGive { + boolean flag = entityplayer.inventory.pickup(itemstack); + EntityItem entityitem; + ++ if (net.pl3x.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping + if (flag && itemstack.isEmpty()) { + itemstack.setCount(1); + entityitem = entityplayer.drop(itemstack, false); +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index e928716d7a2dcb390507b746763e6f7eaae241b5..bce8781a2a1d856429d00b8cd9c9ac03425e1bbb 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -192,6 +192,11 @@ public class PurpurConfig { + useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive); + } + ++ public static boolean disableGiveCommandDrops = false; ++ private static void disableGiveCommandDrops() { ++ disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops); ++ } ++ + public static boolean barrelSixRows = false; + public static boolean enderChestSixRows = false; + public static boolean enderChestPermissionRows = false; diff --git a/patches/Purpur/patches/server/0147-Config-migration-climbing-should-not-bypass-cramming.patch b/patches/Purpur/patches/server/0147-Config-migration-climbing-should-not-bypass-cramming.patch new file mode 100644 index 00000000..dffa8100 --- /dev/null +++ b/patches/Purpur/patches/server/0147-Config-migration-climbing-should-not-bypass-cramming.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Wed, 2 Dec 2020 14:49:10 -0800 +Subject: [PATCH] Config migration: climbing should not bypass cramming + gamerule + + +diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java +index bce8781a2a1d856429d00b8cd9c9ac03425e1bbb..83c1176888ead8fe045f1e1dfc78115ad2bf69f3 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java +@@ -144,6 +144,17 @@ public class PurpurConfig { + } + } + ++ private static void migrateClimbingCrammingFix() { ++ if (PurpurConfig.version < 7) { ++ final boolean climbingCrammingFix = getBoolean("world-settings.default.gameplay-mechanics.fix-climbing-bypassing-cramming-rule", false); ++ set("world-settings.default.gameplay-mechanics.fix-climbing-bypassing-cramming-rule", null); ++ if (climbingCrammingFix) { ++ PaperConfig.config.set("world-settings.default.fix-climbing-bypassing-cramming-rule", true); ++ PaperConfig.saveConfig(); ++ } ++ } ++ } ++ + public static String afkBroadcastAway = "§e§o%s is now AFK"; + public static String afkBroadcastBack = "§e§o%s is no longer AFK"; + public static String afkTabListPrefix = "[AFK] "; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index cce1fa84c96a52656728e3dfe2c58748af48a348..a9f38839927e292add83a5d33063c57bae0198d6 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -96,6 +96,17 @@ public class PurpurWorldConfig { + } + } + ++ private void migrateClimbingCrammingFix() { ++ if (PurpurConfig.version < 7) { ++ final boolean climbingCrammingFix = PurpurConfig.config.getBoolean("world-settings." + worldName + ".gameplay-mechanics.fix-climbing-bypassing-cramming-rule", false); ++ PurpurConfig.config.set("world-settings." + worldName + ".gameplay-mechanics.fix-climbing-bypassing-cramming-rule", null); ++ if (climbingCrammingFix) { ++ PaperConfig.config.set("world-settings." + worldName + ".fix-climbing-bypassing-cramming-rule", true); ++ PaperConfig.saveConfig(); ++ } ++ } ++ } ++ + public boolean armorstandSetNameVisible = false; + public boolean armorstandFixNametags = false; + public float armorstandStepHeight = 0.0F; diff --git a/patches/Purpur/patches/server/0148-Lobotomize-stuck-villagers.patch b/patches/Purpur/patches/server/0148-Lobotomize-stuck-villagers.patch new file mode 100644 index 00000000..b04efb74 --- /dev/null +++ b/patches/Purpur/patches/server/0148-Lobotomize-stuck-villagers.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 3 Dec 2020 17:56:18 -0600 +Subject: [PATCH] Lobotomize stuck villagers + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index e4f2e51b6306fcaf161b7dfb734d9d28947e964b..cb4856e3bbb25e1077f5b4832f359549d57acd7e 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -114,7 +114,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + public double lastY; + public double lastZ; + private Vec3D loc; +- private BlockPosition locBlock; ++ private BlockPosition locBlock; public BlockPosition getBlockLocation() { return locBlock; } // Purpur + private Vec3D mot; + public float yaw; + public float pitch; +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index 548a993a1de939396d075f9176e0d60eebc7b010..b3f71c9dcada0ae14172c5694564a9f54a6e556e 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -187,15 +187,37 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + } + // Spigot End + ++ // Purpur start ++ boolean lobotomized = false; ++ ++ private boolean isLobotomized() { ++ if ((world.getTime() + brainTickOffset) % world.purpurConfig.villagerLobotomizeCheck == 0) { ++ this.lobotomized = !canTravelFrom(getBlockLocation().up()); ++ } ++ return this.lobotomized; ++ } ++ ++ private boolean canTravelFrom(BlockPosition pos) { ++ return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south()); ++ } ++ ++ private boolean canTravelTo(BlockPosition pos) { ++ PathEntity to = navigation.calculateDestination(pos, 0); ++ return to != null && to.getPoints().size() > 1; ++ } ++ // Purpur end ++ + @Override + protected void mobTick() { mobTick(false); } + protected void mobTick(boolean inactive) { + this.world.getMethodProfiler().enter("villagerBrain"); + // Purpur start ++ if (world.purpurConfig.villagerLobotomizeEnabled) inactive = inactive || isLobotomized(); + boolean tick = (world.getTime() + brainTickOffset) % world.purpurConfig.villagerBrainTicks == 0; + if (((WorldServer) world).getMinecraftServer().lagging ? tick : world.purpurConfig.villagerUseBrainTicksOnlyWhenLagging || tick) + // Purpur end + if (!inactive) this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper ++ else if (shouldRestock()) doRestock(); // Purpur + this.world.getMethodProfiler().exit(); + if (this.bF) { + this.bF = false; +@@ -327,6 +349,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + return true; + } + ++ public void doRestock() { fb(); } // Purpur - OBFHELPER + public void fb() { + this.fp(); + Iterator iterator = this.getOffers().iterator(); +@@ -361,6 +384,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + return this.bD == 0 || this.bD < 2 && this.world.getTime() > this.bC + 2400L; + } + ++ public boolean shouldRestock() { return fc(); } // Purpur - OBFHELPER + public boolean fc() { + long i = this.bC + 12000L; + long j = this.world.getTime(); +diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java +index b92ca4a6de01f3f86367fb8dfe3591b08a3e9218..1208464fba96daf276c9cc0c1c9b18db75b03abc 100644 +--- a/src/main/java/net/minecraft/server/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/server/NavigationAbstract.java +@@ -101,6 +101,7 @@ public abstract class NavigationAbstract { + } + + @Nullable ++ public PathEntity calculateDestination(BlockPosition blockposition, int i) { return a(blockposition, i); } // Purpur - OBFHELPER + public PathEntity a(BlockPosition blockposition, int i) { + // Paper start - add target parameter + return this.a(blockposition, null, i); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index a9f38839927e292add83a5d33063c57bae0198d6..cc6f406248860e251a1e002736552e3b54028320 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1061,6 +1061,8 @@ public class PurpurWorldConfig { + public int villagerSpawnIronGolemRadius = 0; + public int villagerSpawnIronGolemLimit = 0; + public boolean villagerCanBreed = true; ++ public boolean villagerLobotomizeEnabled = false; ++ public int villagerLobotomizeCheck = 60; + private void villagerSettings() { + villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); + villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); +@@ -1072,6 +1074,13 @@ public class PurpurWorldConfig { + villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); + villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); + villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); ++ if (PurpurConfig.version < 9) { ++ boolean oldValue = getBoolean("mobs.villager.lobotomize-1x1", villagerLobotomizeEnabled); ++ set("mobs.villager.lobotomize.enabled", oldValue); ++ set("mobs.villager.lobotomize-1x1", null); ++ } ++ villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled); ++ villagerLobotomizeCheck = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheck); + } + + public boolean villagerTraderRidable = false; diff --git a/patches/Purpur/patches/server/0149-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch b/patches/Purpur/patches/server/0149-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch new file mode 100644 index 00000000..8ad7dd00 --- /dev/null +++ b/patches/Purpur/patches/server/0149-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch @@ -0,0 +1,257 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sat, 5 Dec 2020 01:20:16 -0800 +Subject: [PATCH] Option for Villager Clerics to farm Nether Wart + +Adds an option so that Villagers with the Cleric profession are able to +farm Nether Wart. Reimplemented based on a feature of the carpet-extra +mod. + +diff --git a/src/main/java/net/minecraft/server/BehaviorFarm.java b/src/main/java/net/minecraft/server/BehaviorFarm.java +index 0ff202c0d77681f7e0d55d57c69dd0e455336eba..b9c6011c8dcab1a328260871f46d0216bce1818f 100644 +--- a/src/main/java/net/minecraft/server/BehaviorFarm.java ++++ b/src/main/java/net/minecraft/server/BehaviorFarm.java +@@ -12,6 +12,7 @@ public class BehaviorFarm extends Behavior { + private long c; + private int d; + private final List e = Lists.newArrayList(); ++ private boolean clericWartFarmer = false; // Purpur + + public BehaviorFarm() { + super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT)); +@@ -20,9 +21,14 @@ public class BehaviorFarm extends Behavior { + protected boolean a(WorldServer worldserver, EntityVillager entityvillager) { + if (!worldserver.getGameRules().getBoolean(GameRules.MOB_GRIEFING) && !worldserver.purpurConfig.villagerFarmingBypassMobGriefing) { // Purpur + return false; +- } else if (entityvillager.getVillagerData().getProfession() != VillagerProfession.FARMER) { ++ } else if (entityvillager.getVillagerData().getProfession() != VillagerProfession.FARMER && !(worldserver.purpurConfig.villagerClericsFarmWarts && entityvillager.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur + return false; + } else { ++ // Purpur start ++ if (!this.clericWartFarmer && entityvillager.getVillagerData().getProfession() == VillagerProfession.CLERIC) { ++ this.clericWartFarmer = true; ++ } ++ // Purpur end + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = entityvillager.getChunkCoordinates().i(); + + this.e.clear(); +@@ -53,6 +59,11 @@ public class BehaviorFarm extends Behavior { + Block block = iblockdata.getBlock(); + Block block1 = worldserver.getType(blockposition.down()).getBlock(); + ++ // Purpur start ++ if (this.clericWartFarmer) { ++ return block == Blocks.NETHER_WART && iblockdata.get(BlockNetherWart.AGE) == 3 || iblockdata.isAir() && block1 == Blocks.SOUL_SAND; ++ } ++ // Purpur end + return block instanceof BlockCrops && ((BlockCrops) block).isRipe(iblockdata) || iblockdata.isAir() && block1 instanceof BlockSoil; + } + +@@ -78,7 +89,7 @@ public class BehaviorFarm extends Behavior { + Block block = iblockdata.getBlock(); + Block block1 = worldserver.getType(this.farmBlock.down()).getBlock(); + +- if (block instanceof BlockCrops && ((BlockCrops) block).isRipe(iblockdata)) { ++ if (block instanceof BlockCrops && ((BlockCrops) block).isRipe(iblockdata) || this.clericWartFarmer && block == Blocks.NETHER_WART && iblockdata.get(BlockNetherWart.AGE) == 3) { // Purpur + // CraftBukkit start + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityvillager, this.farmBlock, Blocks.AIR.getBlockData()).isCancelled()) { + worldserver.a(this.farmBlock, true, entityvillager); +@@ -86,7 +97,7 @@ public class BehaviorFarm extends Behavior { + // CraftBukkit end + } + +- if (iblockdata.isAir() && block1 instanceof BlockSoil && entityvillager.canPlant()) { ++ if (iblockdata.isAir() && (block1 instanceof BlockSoil || this.clericWartFarmer && block1 == Blocks.SOUL_SAND) && entityvillager.canPlant()) { // Purpur + InventorySubcontainer inventorysubcontainer = entityvillager.getInventory(); + + for (int j = 0; j < inventorysubcontainer.getSize(); ++j) { +@@ -109,6 +120,12 @@ public class BehaviorFarm extends Behavior { + planted = Blocks.BEETROOTS; + flag = true; + } ++ // Purpur start ++ else if (itemstack.getItem() == Items.NETHER_WART) { ++ planted = Blocks.NETHER_WART; ++ flag = true; ++ } ++ // Purpur end + + if (planted != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityvillager, this.farmBlock, planted.getBlockData()).isCancelled()) { + worldserver.setTypeAndData(this.farmBlock, planted.getBlockData(), 3); +@@ -119,7 +136,7 @@ public class BehaviorFarm extends Behavior { + } + + if (flag) { +- worldserver.playSound((EntityHuman) null, (double) this.farmBlock.getX(), (double) this.farmBlock.getY(), (double) this.farmBlock.getZ(), SoundEffects.ITEM_CROP_PLANT, SoundCategory.BLOCKS, 1.0F, 1.0F); ++ worldserver.playSound((EntityHuman) null, (double) this.farmBlock.getX(), (double) this.farmBlock.getY(), (double) this.farmBlock.getZ(), this.clericWartFarmer ? SoundEffects.ITEM_NETHER_WART_PLANT : SoundEffects.ITEM_CROP_PLANT, SoundCategory.BLOCKS, 1.0F, 1.0F); // Purpur + itemstack.subtract(1); + if (itemstack.isEmpty()) { + inventorysubcontainer.setItem(j, ItemStack.b); +diff --git a/src/main/java/net/minecraft/server/BehaviorTradeVillager.java b/src/main/java/net/minecraft/server/BehaviorTradeVillager.java +index ad26ecd7fe6b6eedc743f2fab687bd0c6a62a46a..6d8b6951c3c07f71dc89131842d815099c62030f 100644 +--- a/src/main/java/net/minecraft/server/BehaviorTradeVillager.java ++++ b/src/main/java/net/minecraft/server/BehaviorTradeVillager.java +@@ -41,6 +41,11 @@ public class BehaviorTradeVillager extends Behavior { + if (entityvillager1.getVillagerData().getProfession() == VillagerProfession.FARMER && entityvillager.getInventory().a(Items.WHEAT) > Items.WHEAT.getMaxStackSize() / 2) { + a(entityvillager, ImmutableSet.of(Items.WHEAT), entityvillager1); + } ++ // Purpur start ++ if (worldserver.purpurConfig.villagerClericsFarmWarts && worldserver.purpurConfig.villagerClericFarmersThrowWarts && entityvillager.getVillagerData().getProfession() == VillagerProfession.CLERIC && entityvillager.getInventory().getAmount(Items.NETHER_WART) > Items.NETHER_WART.getMaxStackSize() / 2) { ++ tryThrowingItems(entityvillager, ImmutableSet.of(Items.NETHER_WART), entityvillager1); ++ } ++ // Purpur end + + if (!this.b.isEmpty() && entityvillager.getInventory().a(this.b)) { + a(entityvillager, this.b, entityvillager1); +@@ -62,6 +67,7 @@ public class BehaviorTradeVillager extends Behavior { + }).collect(Collectors.toSet()); + } + ++ private static void tryThrowingItems(EntityVillager entityVillager, Set acceptableItems, EntityLiving targetEntity) { a(entityVillager, acceptableItems, targetEntity); } // Purpur - OBFHELPER + private static void a(EntityVillager entityvillager, Set set, EntityLiving entityliving) { + InventorySubcontainer inventorysubcontainer = entityvillager.getInventory(); + ItemStack itemstack = ItemStack.b; +diff --git a/src/main/java/net/minecraft/server/Behaviors.java b/src/main/java/net/minecraft/server/Behaviors.java +index 2d91869660c36b4cd7bfe887956a26802cce7f8a..e376306bc2555620d1a61af2296f3dd8abc6ce0e 100644 +--- a/src/main/java/net/minecraft/server/Behaviors.java ++++ b/src/main/java/net/minecraft/server/Behaviors.java +@@ -12,10 +12,13 @@ public class Behaviors { + return ImmutableList.of(Pair.of(0, new BehaviorSwim(0.8F)), Pair.of(0, new BehaviorInteractDoor()), Pair.of(0, new BehaviorLook(45, 90)), Pair.of(0, new BehaviorPanic()), Pair.of(0, new BehaviorWake()), Pair.of(0, new BehaviorBellAlert()), Pair.of(0, new BehaviorRaid()), Pair.of(0, new BehaviorPositionValidate(villagerprofession.b(), MemoryModuleType.JOB_SITE)), Pair.of(0, new BehaviorPositionValidate(villagerprofession.b(), MemoryModuleType.POTENTIAL_JOB_SITE)), Pair.of(1, new BehavorMove()), Pair.of(2, new BehaviorBetterJob(villagerprofession)), Pair.of(3, new BehaviorInteractPlayer(f)), new Pair[]{Pair.of(5, new BehaviorFindAdmirableItem<>(f, false, 4)), Pair.of(6, new BehaviorFindPosition(villagerprofession.b(), MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, true, Optional.empty())), Pair.of(7, new BehaviorPotentialJobSite(f)), Pair.of(8, new BehaviorLeaveJob(f)), Pair.of(10, new BehaviorFindPosition(VillagePlaceType.r, MemoryModuleType.HOME, false, Optional.of((byte) 14))), Pair.of(10, new BehaviorFindPosition(VillagePlaceType.s, MemoryModuleType.MEETING_POINT, true, Optional.of((byte) 14))), Pair.of(10, new BehaviorCareer()), Pair.of(10, new BehaviorProfession())}); + } + +- public static ImmutableList>> b(VillagerProfession villagerprofession, float f) { +- Object object; ++ // Purpur start - OBFHELPER, add clericsFarmWarts param ++ public static ImmutableList>> b(VillagerProfession villagerprofession, float f) { return createWorkTask(villagerprofession, f, false); } ++ public static ImmutableList>> createWorkTask(VillagerProfession villagerprofession, float f, boolean clericsFarmWarts) { ++ BehaviorWork object; // Purpur - decompile fix + +- if (villagerprofession == VillagerProfession.FARMER) { ++ if (villagerprofession == VillagerProfession.FARMER || (clericsFarmWarts && villagerprofession == VillagerProfession.CLERIC)) { ++ // Purpur end + object = new BehaviorWorkComposter(); + } else { + object = new BehaviorWork(); +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index b3f71c9dcada0ae14172c5694564a9f54a6e556e..60962553e4f374b38de82f2cd4d72cef3d956c72 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -131,7 +131,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + behaviorcontroller.a(Activity.PLAY, Behaviors.a(0.5F)); + } else { + behaviorcontroller.setSchedule(Schedule.VILLAGER_DEFAULT); +- behaviorcontroller.a(Activity.WORK, Behaviors.b(villagerprofession, 0.5F), (Set) ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); ++ behaviorcontroller.a(Activity.WORK, Behaviors.createWorkTask(villagerprofession, 0.5F, this.world.purpurConfig.villagerClericsFarmWarts), (Set) ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); // Purpur + } + + behaviorcontroller.a(Activity.CORE, Behaviors.a(villagerprofession, 0.5F)); +@@ -845,6 +845,11 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + @Override + public boolean i(ItemStack itemstack) { + Item item = itemstack.getItem(); ++ // Purpur start ++ if (this.world.purpurConfig.villagerClericsFarmWarts && item.getItem() == Items.NETHER_WART && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) { ++ return true; ++ } ++ // Purpur end + + return (EntityVillager.bs.contains(item) || this.getVillagerData().getProfession().c().contains(item)) && this.getInventory().b(itemstack); + } +@@ -866,6 +871,11 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + } + + public boolean canPlant() { ++ // Purpur start ++ if (this.world.purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) { ++ return this.getInventory().containsAny(ImmutableSet.of(Items.NETHER_WART)); ++ } ++ // Purpur end + return this.getInventory().a((Set) ImmutableSet.of(Items.WHEAT_SEEDS, Items.POTATO, Items.CARROT, Items.BEETROOT_SEEDS)); + } + +diff --git a/src/main/java/net/minecraft/server/IInventory.java b/src/main/java/net/minecraft/server/IInventory.java +index 46b88056b852a7f91d32862dea7bd3a7ea4a1226..64f1767f66a5a91833225faa1b1444e62c6a0205 100644 +--- a/src/main/java/net/minecraft/server/IInventory.java ++++ b/src/main/java/net/minecraft/server/IInventory.java +@@ -31,6 +31,7 @@ public interface IInventory extends Clearable { + return true; + } + ++ default int getAmount(Item item) { return this.a(item); } // Purpur - OBFHELPER + default int a(Item item) { + int i = 0; + +@@ -45,6 +46,7 @@ public interface IInventory extends Clearable { + return i; + } + ++ default boolean containsAny(Set itemSet) { return a(itemSet); } // Purpur - OBFHELPER + default boolean a(Set set) { + for (int i = 0; i < this.getSize(); ++i) { + ItemStack itemstack = this.getItem(i); +diff --git a/src/main/java/net/minecraft/server/SensorSecondaryPlaces.java b/src/main/java/net/minecraft/server/SensorSecondaryPlaces.java +index 24173f0d3a6c2c9a4a564de6cd828bdef9afec90..2d8e3e77710b59967b7b18006194d73761df6c56 100644 +--- a/src/main/java/net/minecraft/server/SensorSecondaryPlaces.java ++++ b/src/main/java/net/minecraft/server/SensorSecondaryPlaces.java +@@ -12,6 +12,13 @@ public class SensorSecondaryPlaces extends Sensor { + } + + protected void a(WorldServer worldserver, EntityVillager entityvillager) { ++ // Purpur start - make sure clerics don't wander to soul sand when the option is off ++ BehaviorController behaviorcontroller = entityvillager.getBehaviorController(); ++ if (!worldserver.purpurConfig.villagerClericsFarmWarts && entityvillager.getVillagerData().getProfession() == VillagerProfession.CLERIC) { ++ behaviorcontroller.removeMemory(MemoryModuleType.SECONDARY_JOB_SITE); ++ return; ++ } ++ // Purpur end + ResourceKey resourcekey = worldserver.getDimensionKey(); + BlockPosition blockposition = entityvillager.getChunkCoordinates(); + List list = Lists.newArrayList(); +@@ -29,10 +36,10 @@ public class SensorSecondaryPlaces extends Sensor { + } + } + +- BehaviorController behaviorcontroller = entityvillager.getBehaviorController(); ++ //BehaviorController behaviorcontroller = entityvillager.getBehaviorController(); // Purpur - move up + + if (!list.isEmpty()) { +- behaviorcontroller.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, (Object) list); ++ behaviorcontroller.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, list); // Purpur - decompile fix + } else { + behaviorcontroller.removeMemory(MemoryModuleType.SECONDARY_JOB_SITE); + } +diff --git a/src/main/java/net/minecraft/server/VillagerProfession.java b/src/main/java/net/minecraft/server/VillagerProfession.java +index 3c60da7ac6faebe9d964e893974e42613c59b4c1..6493f220a0cf627e82e5f3f3c85e9934d9a9ebae 100644 +--- a/src/main/java/net/minecraft/server/VillagerProfession.java ++++ b/src/main/java/net/minecraft/server/VillagerProfession.java +@@ -9,7 +9,7 @@ public class VillagerProfession { + public static final VillagerProfession ARMORER = a("armorer", VillagePlaceType.d, SoundEffects.ENTITY_VILLAGER_WORK_ARMORER); + public static final VillagerProfession BUTCHER = a("butcher", VillagePlaceType.e, SoundEffects.ENTITY_VILLAGER_WORK_BUTCHER); + public static final VillagerProfession CARTOGRAPHER = a("cartographer", VillagePlaceType.f, SoundEffects.ENTITY_VILLAGER_WORK_CARTOGRAPHER); +- public static final VillagerProfession CLERIC = a("cleric", VillagePlaceType.g, SoundEffects.ENTITY_VILLAGER_WORK_CLERIC); ++ public static final VillagerProfession CLERIC = a("cleric", VillagePlaceType.g, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEffects.ENTITY_VILLAGER_WORK_CLERIC); // Purpur + public static final VillagerProfession FARMER = a("farmer", VillagePlaceType.h, ImmutableSet.of(Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT_SEEDS, Items.BONE_MEAL), ImmutableSet.of(Blocks.FARMLAND), SoundEffects.ENTITY_VILLAGER_WORK_FARMER); + public static final VillagerProfession FISHERMAN = a("fisherman", VillagePlaceType.i, SoundEffects.ENTITY_VILLAGER_WORK_FISHERMAN); + public static final VillagerProfession FLETCHER = a("fletcher", VillagePlaceType.j, SoundEffects.ENTITY_VILLAGER_WORK_FLETCHER); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index cc6f406248860e251a1e002736552e3b54028320..e1af862dd06d4c524ef16b7302578603a93823fa 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1063,6 +1063,8 @@ public class PurpurWorldConfig { + public boolean villagerCanBreed = true; + public boolean villagerLobotomizeEnabled = false; + public int villagerLobotomizeCheck = 60; ++ public boolean villagerClericsFarmWarts = false; ++ public boolean villagerClericFarmersThrowWarts = true; + private void villagerSettings() { + villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); + villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); +@@ -1081,6 +1083,8 @@ public class PurpurWorldConfig { + } + villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled); + villagerLobotomizeCheck = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheck); ++ villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); ++ villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); + } + + public boolean villagerTraderRidable = false; diff --git a/patches/Purpur/patches/server/0150-Toggle-for-Zombified-Piglin-death-always-counting-as.patch b/patches/Purpur/patches/server/0150-Toggle-for-Zombified-Piglin-death-always-counting-as.patch new file mode 100644 index 00000000..348d4200 --- /dev/null +++ b/patches/Purpur/patches/server/0150-Toggle-for-Zombified-Piglin-death-always-counting-as.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sat, 5 Dec 2020 02:34:22 -0800 +Subject: [PATCH] Toggle for Zombified Piglin death always counting as player + kill when angry + +In Vanilla (as of 1.16.4), when Zombified Piglins die while angry, it will +count as a player kill regardless of whether a player has ever hit them, +meaning they will drop XP. This is abused in Zombified Piglin farms where +the player kills the entities through cramming, but they still drop XP due +to the Piglin being angry, even though the player never hit them. + +This patch adds a toggle to disable this behavior. + +diff --git a/src/main/java/net/minecraft/server/EntityPigZombie.java b/src/main/java/net/minecraft/server/EntityPigZombie.java +index 3327dbbf87d8f43cbc7cd728df2f4c6a33dae40d..57f3358b8dfd53f5b1d2e976d64b809f74bc3ce3 100644 +--- a/src/main/java/net/minecraft/server/EntityPigZombie.java ++++ b/src/main/java/net/minecraft/server/EntityPigZombie.java +@@ -95,7 +95,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + this.eY(); + } + +- if (this.isAngry()) { ++ if (this.isAngry() && this.world.purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur + this.lastDamageByPlayerTime = this.ticksLived; + } + +@@ -150,7 +150,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + this.bt = EntityPigZombie.bs.a(this.random); + } + +- if (entityliving instanceof EntityHuman) { ++ if (entityliving instanceof EntityHuman && this.world.purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur + this.e((EntityHuman) entityliving); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index e1af862dd06d4c524ef16b7302578603a93823fa..f5037ca5a26f7725e99b6aef0eef894cf6023455 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1194,12 +1194,14 @@ public class PurpurWorldConfig { + public boolean zombifiedPiglinJockeyOnlyBaby = true; + public double zombifiedPiglinJockeyChance = 0.05D; + public boolean zombifiedPiglinJockeyTryExistingChickens = true; ++ public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; + private void zombifiedPiglinSettings() { + zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); + zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); + zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); + zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); + zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); ++ zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); + } + + public boolean zombieVillagerRidable = false; diff --git a/patches/Purpur/patches/server/0151-Spread-out-and-optimise-player-list-ticks.patch b/patches/Purpur/patches/server/0151-Spread-out-and-optimise-player-list-ticks.patch new file mode 100644 index 00000000..29a0c495 --- /dev/null +++ b/patches/Purpur/patches/server/0151-Spread-out-and-optimise-player-list-ticks.patch @@ -0,0 +1,76 @@ +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 ticks + + +diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java +index 3c19e931ad7d5330f1c77ef65aaa5858a001e4df..0efc210ad55d843fd297f0caa88a5f355fbfef80 100644 +--- a/src/main/java/net/minecraft/server/PlayerList.java ++++ b/src/main/java/net/minecraft/server/PlayerList.java +@@ -71,7 +71,7 @@ public abstract class PlayerList { + private int viewDistance; + private EnumGamemode u; + private boolean v; +- private int w; ++ private int w; private int getTick() { return this.w; } private int setTick(int i) { return this.w = i; } // Purpur - OBFHELPER + + // CraftBukkit start + private CraftServer cserver; +@@ -928,22 +928,23 @@ public abstract class PlayerList { + } + + public void tick() { +- if (++this.w > 600) { +- // CraftBukkit start +- for (int i = 0; i < this.players.size(); ++i) { +- final EntityPlayer target = (EntityPlayer) this.players.get(i); +- +- target.playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, Iterables.filter(this.players, new Predicate() { +- @Override +- public boolean apply(EntityPlayer input) { +- return target.getBukkitEntity().canSee(input.getBukkitEntity()); +- } +- }))); ++ // Purpur start ++ int tick = getTick(); ++ if (tick < this.players.size()) { ++ final org.bukkit.craftbukkit.entity.CraftPlayer target = this.players.get(tick).getBukkitEntity(); ++ final java.util.List list = new java.util.ArrayList<>(); ++ for (EntityPlayer entityplayer : this.players) { ++ if (target.canSee(entityplayer.getUniqueID())) { ++ list.add(entityplayer); ++ } + } +- // CraftBukkit end +- this.w = 0; ++ target.getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, list)); + } +- ++ if (++tick > 600) { ++ tick = 0; ++ } ++ setTick(tick); ++ // Purpur end + } + + public void sendAll(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 8a28c68c5fc22838c62ceef738b330afb840c4c6..95e548eea5acbece0e7036f23d79b6fc61e6786f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1382,7 +1382,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean canSee(Player player) { +- return !hiddenPlayers.containsKey(player.getUniqueId()); ++ // Purpur start ++ return canSee(player.getUniqueId()); ++ } ++ ++ public boolean canSee(UUID uuid) { ++ return !hiddenPlayers.containsKey(uuid); ++ // Purpur end + } + + @Override diff --git a/patches/Purpur/patches/server/0152-Configurable-chance-for-wolves-to-spawn-rabid.patch b/patches/Purpur/patches/server/0152-Configurable-chance-for-wolves-to-spawn-rabid.patch new file mode 100644 index 00000000..a11e7097 --- /dev/null +++ b/patches/Purpur/patches/server/0152-Configurable-chance-for-wolves-to-spawn-rabid.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Encode42 +Date: Tue, 8 Dec 2020 17:15:15 -0500 +Subject: [PATCH] Configurable chance for wolves to spawn rabid + +Configurable chance to spawn a wolf that is rabid. +Rabid wolves attack all players, mobs, and animals. + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 618739799bc5dc2550198465b1e46e494f473ad9..3836d772d07efb7e9a9e7ecc7141330f0cd15801 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -2094,6 +2094,7 @@ public abstract class EntityLiving extends Entity { + } + } + ++ public final void setItemInHand(EnumHand enumHand, ItemStack itemStack) { this.a(enumHand, itemStack); } // Purpur - OBFHELPER + public void a(EnumHand enumhand, ItemStack itemstack) { + if (enumhand == EnumHand.MAIN_HAND) { + this.setSlot(EnumItemSlot.MAINHAND, itemstack); +diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java +index 6c25f667eecdf345289a0dbf885c9d71c6a26958..5fe2e9f4bfbdc08690eacd6196e59529dc7953e8 100644 +--- a/src/main/java/net/minecraft/server/EntityWolf.java ++++ b/src/main/java/net/minecraft/server/EntityWolf.java +@@ -14,11 +14,42 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + private static final DataWatcherObject br = DataWatcher.a(EntityWolf.class, DataWatcherRegistry.i); + private static final DataWatcherObject bs = DataWatcher.a(EntityWolf.class, DataWatcherRegistry.b); + private static final DataWatcherObject bt = DataWatcher.a(EntityWolf.class, DataWatcherRegistry.b); +- public static final Predicate bq = (entityliving) -> { ++ public static Predicate vanillaPredicate() { return bq; } public static final Predicate bq = (entityliving) -> { // Purpur - OBFHELPER + EntityTypes entitytypes = entityliving.getEntityType(); + + return entitytypes == EntityTypes.SHEEP || entitytypes == EntityTypes.RABBIT || entitytypes == EntityTypes.FOX; + }; ++ // Purpur start - rabid wolf spawn chance ++ private boolean isRabid = false; ++ private static final Predicate RABID_PREDICATE = e -> e instanceof EntityPlayer || e instanceof EntityInsentient; ++ private final PathfinderGoal PATHFINDER_VANILLA = new PathfinderGoalRandomTargetNonTamed<>(this, EntityAnimal.class, false, vanillaPredicate()); ++ private final PathfinderGoal PATHFINDER_RABID = new PathfinderGoalRandomTargetNonTamed<>(this, EntityLiving.class, false, RABID_PREDICATE); ++ private static final class PathfinderGoalAvoidRabidWolves extends PathfinderGoalAvoidTarget { ++ private final EntityWolf wolf; ++ ++ public PathfinderGoalAvoidRabidWolves(EntityWolf wolf, float distance, double minSpeed, double maxSpeed) { ++ super(wolf, EntityWolf.class, distance, minSpeed, maxSpeed); ++ this.wolf = wolf; ++ } ++ ++ @Override ++ public boolean a() { // shouldExecute ++ return super.a() && !this.wolf.isRabid() && this.getTarget() != null && this.getTarget().isRabid(); // wolves which are not rabid run away from rabid wolves ++ } ++ ++ @Override ++ public void c() { // startExecuting ++ this.wolf.setGoalTarget(null); ++ super.c(); ++ } ++ ++ @Override ++ public void e() { // tick ++ this.wolf.setGoalTarget(null); ++ super.e(); ++ } ++ } ++ // Purpur end + private float bu; + private float bv; + private boolean bw; +@@ -53,6 +84,37 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + int getPurpurBreedTime() { + return this.world.purpurConfig.wolfBreedingTicks; + } ++ ++ public boolean isRabid() { ++ return this.isRabid; ++ } ++ ++ public void setRabid(boolean isRabid) { ++ this.isRabid = isRabid; ++ updatePathfinders(true); ++ } ++ ++ public void updatePathfinders(boolean modifyEffects) { ++ this.targetSelector.removeGoal(PATHFINDER_VANILLA); ++ this.targetSelector.removeGoal(PATHFINDER_RABID); ++ if (this.isRabid) { ++ setTamed(false); ++ setOwnerUUID(null); ++ this.targetSelector.addGoal(5, PATHFINDER_RABID); ++ if (modifyEffects) this.addEffect(new MobEffect(MobEffects.CONFUSION, 1200)); ++ } else { ++ this.targetSelector.addGoal(5, PATHFINDER_VANILLA); ++ this.pacify(); ++ if (modifyEffects) this.removeEffect(MobEffects.CONFUSION); ++ } ++ } ++ ++ @Override ++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { ++ this.isRabid = world.purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= world.purpurConfig.wolfNaturalRabid; ++ this.updatePathfinders(false); ++ return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); ++ } + // Purpur end + + @Override +@@ -61,6 +123,7 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + this.goalSelector.a(1, new PathfinderGoalHasRider(this)); // Purpur + this.goalSelector.a(2, new PathfinderGoalSit(this)); + this.goalSelector.a(3, new EntityWolf.a<>(this, EntityLlama.class, 24.0F, 1.5D, 1.5D)); ++ this.goalSelector.addGoal(3, new PathfinderGoalAvoidRabidWolves(this, 24.0F, 1.5D, 1.5D)); // Purpur + this.goalSelector.a(4, new PathfinderGoalLeapAtTarget(this, 0.4F)); + this.goalSelector.a(5, new PathfinderGoalMeleeAttack(this, 1.0D, true)); + this.goalSelector.a(6, new PathfinderGoalFollowOwner(this, 1.0D, 10.0F, 2.0F, false)); +@@ -74,7 +137,7 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + this.targetSelector.a(2, new PathfinderGoalOwnerHurtTarget(this)); + this.targetSelector.a(3, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[0])); // CraftBukkit - decompile error + this.targetSelector.a(4, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, 10, true, false, this::a_)); +- this.targetSelector.a(5, new PathfinderGoalRandomTargetNonTamed<>(this, EntityAnimal.class, false, EntityWolf.bq)); ++ //this.targetSelector.a(5, new PathfinderGoalRandomTargetNonTamed<>(this, EntityAnimal.class, false, EntityWolf.bq)); // Purpur - moved to updatePathfinders() + this.targetSelector.a(6, new PathfinderGoalRandomTargetNonTamed<>(this, EntityTurtle.class, false, EntityTurtle.bo)); + this.targetSelector.a(7, new PathfinderGoalNearestAttackableTarget<>(this, EntitySkeletonAbstract.class, false)); + this.targetSelector.a(8, new PathfinderGoalUniversalAngerReset<>(this, true)); +@@ -119,6 +182,7 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + public void saveData(NBTTagCompound nbttagcompound) { + super.saveData(nbttagcompound); + nbttagcompound.setByte("CollarColor", (byte) this.getCollarColor().getColorIndex()); ++ nbttagcompound.setBoolean("Purpur.IsRabid", this.isRabid); // Purpur + this.c(nbttagcompound); + } + +@@ -128,6 +192,10 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + if (nbttagcompound.hasKeyOfType("CollarColor", 99)) { + this.setCollarColor(EnumColor.fromColorIndex(nbttagcompound.getInt("CollarColor"))); + } ++ // Purpur start ++ this.isRabid = nbttagcompound.getBoolean("Purpur.IsRabid"); ++ this.updatePathfinders(false); ++ // Purpur end + + this.a((WorldServer) this.world, nbttagcompound); + } +@@ -172,6 +240,11 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + public void tick() { + super.tick(); + if (this.isAlive()) { ++ // Purpur start ++ if (this.ticksLived % 300 == 0 && this.isRabid()) { ++ this.addEffect(new MobEffect(MobEffects.CONFUSION, 400)); ++ } ++ // Purpur end + this.bv = this.bu; + if (this.eY()) { + this.bu += (1.0F - this.bu) * 0.4F; +@@ -343,6 +416,20 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + + return EnumInteractionResult.SUCCESS; + } ++ // Purpur start ++ else if (this.world.purpurConfig.wolfMilkCuresRabies && itemstack.getItem() == Items.MILK_BUCKET && this.isRabid()) { ++ if (!entityhuman.isCreative()) { ++ entityhuman.setItemInHand(enumhand, new ItemStack(Items.BUCKET)); ++ } ++ this.setRabid(false); ++ for (int i = 0; i < 10; ++i) { ++ ((WorldServer) world).sendParticles(((WorldServer) world).players, null, Particles.HAPPY_VILLAGER, ++ locX() + random.nextFloat(), locY() + (random.nextFloat() * 1.5), locZ() + random.nextFloat(), 1, ++ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); ++ } ++ return EnumInteractionResult.SUCCESS; ++ } ++ // Purpur end + + return super.b(entityhuman, enumhand); + } +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalAvoidTarget.java b/src/main/java/net/minecraft/server/PathfinderGoalAvoidTarget.java +index 9a4819815c7a4bf2fd4a92c4169ace35f2261704..da29898574d30d5fecc5a44ad7b365564fa686e1 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalAvoidTarget.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalAvoidTarget.java +@@ -8,7 +8,7 @@ public class PathfinderGoalAvoidTarget extends Pathfinde + protected final EntityCreature a; + private final double i; + private final double j; +- protected T b; ++ protected T b; protected T getTarget() { return this.b; } // Purpur - OBFHELPER + protected final float c; + protected PathEntity d; + protected final NavigationAbstract e; +@@ -18,12 +18,7 @@ public class PathfinderGoalAvoidTarget extends Pathfinde + private final PathfinderTargetCondition k; + + public PathfinderGoalAvoidTarget(EntityCreature entitycreature, Class oclass, float f, double d0, double d1) { +- Predicate predicate = (entityliving) -> { +- return true; +- }; +- Predicate predicate1 = IEntitySelector.e; +- +- this(entitycreature, oclass, predicate, f, d0, d1, predicate1::test); ++ this(entitycreature, oclass, entityliving -> true, f, d0, d1, IEntitySelector.e::test); // Purpur - decompile fix + } + + public PathfinderGoalAvoidTarget(EntityCreature entitycreature, Class oclass, Predicate predicate, float f, double d0, double d1, Predicate predicate1) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f5037ca5a26f7725e99b6aef0eef894cf6023455..5ce640b76573f59e62000eb721e7333579a55ef1 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1145,10 +1145,14 @@ public class PurpurWorldConfig { + + public boolean wolfRidable = false; + public boolean wolfRidableInWater = false; ++ public boolean wolfMilkCuresRabies = true; ++ public double wolfNaturalRabid = 0.0D; + public int wolfBreedingTicks = 6000; + private void wolfSettings() { + wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); + wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); ++ wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies); ++ wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid); + wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +index 5f3314febb2300a9b4f3a7c143cb65811e1d5320..507857ba247d0988e0011d215ea38a3622e78e05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +@@ -45,4 +45,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { + public void setCollarColor(DyeColor color) { + getHandle().setCollarColor(EnumColor.fromColorIndex(color.getWoolData())); + } ++ ++ // Purpur start ++ @Override ++ public boolean isRabid() { ++ return getHandle().isRabid(); ++ } ++ ++ @Override ++ public void setRabid(boolean isRabid) { ++ getHandle().setRabid(isRabid); ++ } ++ // Purpur end + } diff --git a/patches/Purpur/patches/server/0153-Configurable-default-wolf-collar-color.patch b/patches/Purpur/patches/server/0153-Configurable-default-wolf-collar-color.patch new file mode 100644 index 00000000..bcf9f433 --- /dev/null +++ b/patches/Purpur/patches/server/0153-Configurable-default-wolf-collar-color.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Encode42 +Date: Thu, 10 Dec 2020 13:43:28 -0500 +Subject: [PATCH] Configurable default wolf collar color + +This allows for the server to set a default collar color when a wolf is tamed. +Resets to RED when the value is invalid. + +diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java +index 5fe2e9f4bfbdc08690eacd6196e59529dc7953e8..fd62dc51258876275adbe02f750fd88107c38a6b 100644 +--- a/src/main/java/net/minecraft/server/EntityWolf.java ++++ b/src/main/java/net/minecraft/server/EntityWolf.java +@@ -115,6 +115,12 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + this.updatePathfinders(false); + return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound); + } ++ ++ @Override ++ public void tame(EntityHuman entityhuman) { ++ setCollarColor(world.purpurConfig.wolfDefaultCollarColor); ++ super.tame(entityhuman); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 5ce640b76573f59e62000eb721e7333579a55ef1..f04307e70f072506668684dc649d5bd78566435f 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -3,6 +3,7 @@ package net.pl3x.purpur; + import com.destroystokyo.paper.PaperConfig; + import net.minecraft.server.Block; + import net.minecraft.server.Blocks; ++import net.minecraft.server.EnumColor; + import net.minecraft.server.EnumDifficulty; + import net.minecraft.server.Explosion; + import net.minecraft.server.IRegistry; +@@ -1145,12 +1146,18 @@ public class PurpurWorldConfig { + + public boolean wolfRidable = false; + public boolean wolfRidableInWater = false; ++ public EnumColor wolfDefaultCollarColor = EnumColor.RED; + public boolean wolfMilkCuresRabies = true; + public double wolfNaturalRabid = 0.0D; + public int wolfBreedingTicks = 6000; + private void wolfSettings() { + wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); + wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); ++ try { ++ wolfDefaultCollarColor = EnumColor.valueOf(getString("mobs.wolf.default-collar-color", wolfDefaultCollarColor.name())); ++ } catch (IllegalArgumentException ignore) { ++ wolfDefaultCollarColor = EnumColor.RED; ++ } + wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies); + wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid); + wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); diff --git a/patches/Purpur/patches/server/0154-Configurable-entity-base-attributes.patch b/patches/Purpur/patches/server/0154-Configurable-entity-base-attributes.patch new file mode 100644 index 00000000..28f4f4ad --- /dev/null +++ b/patches/Purpur/patches/server/0154-Configurable-entity-base-attributes.patch @@ -0,0 +1,2560 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 10 Dec 2020 16:44:54 -0600 +Subject: [PATCH] Configurable entity base attributes + + +diff --git a/src/main/java/net/minecraft/server/EntityBat.java b/src/main/java/net/minecraft/server/EntityBat.java +index bdf4e798ac8ca27edebd0122b894d15a76ecb9d0..7718ec8ad3c1833e0bcc713c2e96e054e87453ad 100644 +--- a/src/main/java/net/minecraft/server/EntityBat.java ++++ b/src/main/java/net/minecraft/server/EntityBat.java +@@ -54,6 +54,18 @@ public class EntityBat extends EntityAmbient { + setMot(mot.a(0.9D)); + } + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.batMaxHealth); ++ this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(this.world.purpurConfig.batFollowRange); ++ this.getAttributeInstance(GenericAttributes.KNOCKBACK_RESISTANCE).setValue(this.world.purpurConfig.batKnockbackResistance); ++ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.batMovementSpeed); ++ this.getAttributeInstance(GenericAttributes.FLYING_SPEED).setValue(this.world.purpurConfig.batFlyingSpeed); ++ this.getAttributeInstance(GenericAttributes.ARMOR).setValue(this.world.purpurConfig.batArmor); ++ this.getAttributeInstance(GenericAttributes.ARMOR_TOUGHNESS).setValue(this.world.purpurConfig.batArmorToughness); ++ this.getAttributeInstance(GenericAttributes.ATTACK_KNOCKBACK).setValue(this.world.purpurConfig.batAttackKnockback); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityBee.java b/src/main/java/net/minecraft/server/EntityBee.java +index ded4e10f5082fb5aa25368d9035affba287c3345..bef806bfc60792a990209b57f3cd4b9da0c24acc 100644 +--- a/src/main/java/net/minecraft/server/EntityBee.java ++++ b/src/main/java/net/minecraft/server/EntityBee.java +@@ -105,6 +105,11 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB + int getPurpurBreedTime() { + return this.world.purpurConfig.beeBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.beeMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityBlaze.java b/src/main/java/net/minecraft/server/EntityBlaze.java +index dcfad16e06450068d5801fc002c9650102dbf995..90b90fa33b39020189a1d4a5826fa3ab720488cd 100644 +--- a/src/main/java/net/minecraft/server/EntityBlaze.java ++++ b/src/main/java/net/minecraft/server/EntityBlaze.java +@@ -45,6 +45,11 @@ public class EntityBlaze extends EntityMonster { + setMot(mot.a(0.9D)); + } + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.blazeMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCat.java b/src/main/java/net/minecraft/server/EntityCat.java +index 8346db6c8521064ff51445a0da747a2d70d14633..e411da6f0f55b965fe9e303cf3b56201d33cd260 100644 +--- a/src/main/java/net/minecraft/server/EntityCat.java ++++ b/src/main/java/net/minecraft/server/EntityCat.java +@@ -63,6 +63,11 @@ public class EntityCat extends EntityTameableAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.catBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.catMaxHealth); ++ } + // Purpur end + + public MinecraftKey eU() { +diff --git a/src/main/java/net/minecraft/server/EntityCaveSpider.java b/src/main/java/net/minecraft/server/EntityCaveSpider.java +index 2e1f2dec17e7761b6534f29bbec813d135250e4f..adbd665d455dfc1672a5bdf1b3b380d535835cd7 100644 +--- a/src/main/java/net/minecraft/server/EntityCaveSpider.java ++++ b/src/main/java/net/minecraft/server/EntityCaveSpider.java +@@ -18,6 +18,11 @@ public class EntityCaveSpider extends EntitySpider { + public boolean isRidableInWater() { + return world.purpurConfig.caveSpiderRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.caveSpiderMaxHealth); ++ } + // Purpur end + + public static AttributeProvider.Builder m() { +diff --git a/src/main/java/net/minecraft/server/EntityChicken.java b/src/main/java/net/minecraft/server/EntityChicken.java +index 2e1dc047459889aea85a79eaa04e8fe1a80e5b9e..18aed81061a4033627214a65d38cd9509ed0f8e9 100644 +--- a/src/main/java/net/minecraft/server/EntityChicken.java ++++ b/src/main/java/net/minecraft/server/EntityChicken.java +@@ -33,6 +33,7 @@ public class EntityChicken extends EntityAnimal { + if (world.purpurConfig.chickenRetaliate) { + this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(2.0D); + } ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.chickenMaxHealth); + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCod.java b/src/main/java/net/minecraft/server/EntityCod.java +index 039fae4c29648afa85ea1b27d82cfe51c4165315..024a5ffa878d6e6749c1dd95e94ec6d65cf1d629 100644 +--- a/src/main/java/net/minecraft/server/EntityCod.java ++++ b/src/main/java/net/minecraft/server/EntityCod.java +@@ -16,6 +16,11 @@ public class EntityCod extends EntityFishSchool { + public boolean isRidableInWater() { + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.codMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java +index 63497ca0266073dc0a16b7dc22641d08c3eaf400..951531d65709ee3064371cbf4b3a9831a47b12aa 100644 +--- a/src/main/java/net/minecraft/server/EntityCow.java ++++ b/src/main/java/net/minecraft/server/EntityCow.java +@@ -26,6 +26,11 @@ public class EntityCow extends EntityAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.cowBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.cowMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityCreeper.java b/src/main/java/net/minecraft/server/EntityCreeper.java +index d186f257fa3bc613be7ec79cd6a6ff2e747cba78..b48610fefc8c72922a00dbab861de7b34d5ec1cf 100644 +--- a/src/main/java/net/minecraft/server/EntityCreeper.java ++++ b/src/main/java/net/minecraft/server/EntityCreeper.java +@@ -96,6 +96,11 @@ public class EntityCreeper extends EntityMonster { + } + return getForward() == 0 && getStrafe() == 0; // do not jump if standing still + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.creeperMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityDolphin.java b/src/main/java/net/minecraft/server/EntityDolphin.java +index 9d5d143a997b74e0777bb79bf14d341ad5340db9..82021ba825d6a36191174acf83d109a5c74f6db8 100644 +--- a/src/main/java/net/minecraft/server/EntityDolphin.java ++++ b/src/main/java/net/minecraft/server/EntityDolphin.java +@@ -63,6 +63,11 @@ public class EntityDolphin extends EntityWaterAnimal { + } + return false; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.dolphinMaxHealth); ++ } + // Purpur end + + @Nullable +diff --git a/src/main/java/net/minecraft/server/EntityDrowned.java b/src/main/java/net/minecraft/server/EntityDrowned.java +index 638efc67d66001ee085957d4698f51a7daac77fc..a766910663e47b05d1e38908b5db7471199993c7 100644 +--- a/src/main/java/net/minecraft/server/EntityDrowned.java ++++ b/src/main/java/net/minecraft/server/EntityDrowned.java +@@ -46,6 +46,15 @@ public class EntityDrowned extends EntityZombie implements IRangedEntity { + public boolean jockeyTryExistingChickens() { + return world.purpurConfig.drownedJockeyTryExistingChickens; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.drownedMaxHealth); ++ } ++ ++ protected void generateReinforcementsChance() { ++ this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(this.random.nextDouble() * this.world.purpurConfig.drownedSpawnReinforcements); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java +index 17a9217ed2d1677371d12b4ab0552378cd71652f..8e16ae4be41a0f20b057b70e9ef255c548a36f08 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java +@@ -97,6 +97,11 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + public double getMaxY() { + return world.purpurConfig.enderDragonMaxY; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.enderDragonMaxHealth); ++ } + // Purpur end + + public static AttributeProvider.Builder m() { +diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java +index acb2b3ed04ea0bf19335415310ce22cd076dd92a..dfe6175ec8107f684ea1567d932d11de06c46372 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderman.java ++++ b/src/main/java/net/minecraft/server/EntityEnderman.java +@@ -40,6 +40,11 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + public boolean isRidableInWater() { + return world.purpurConfig.endermanRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.endermanMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEndermite.java b/src/main/java/net/minecraft/server/EntityEndermite.java +index 7eef7f523a17434b38492006526920a955fc9120..3f34ebc1c4ac6f6f0b883c3e4c768007c24fc194 100644 +--- a/src/main/java/net/minecraft/server/EntityEndermite.java ++++ b/src/main/java/net/minecraft/server/EntityEndermite.java +@@ -22,6 +22,11 @@ public class EntityEndermite extends EntityMonster { + public boolean isRidableInWater() { + return world.purpurConfig.endermiteRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.endermiteMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEvoker.java b/src/main/java/net/minecraft/server/EntityEvoker.java +index c75dc75611991028e9de6db7c57304e913251a6b..f0ecc6e6ef5843714a6423af5d6619856ef23977 100644 +--- a/src/main/java/net/minecraft/server/EntityEvoker.java ++++ b/src/main/java/net/minecraft/server/EntityEvoker.java +@@ -22,6 +22,11 @@ public class EntityEvoker extends EntityIllagerWizard { + public boolean isRidableInWater() { + return world.purpurConfig.evokerRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.evokerMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityFox.java b/src/main/java/net/minecraft/server/EntityFox.java +index e87f5aeb97fc35ff2b3464f31a2ec18432774cd4..c63878087ff0414602c4e7ed768a62f8e4899a3a 100644 +--- a/src/main/java/net/minecraft/server/EntityFox.java ++++ b/src/main/java/net/minecraft/server/EntityFox.java +@@ -91,6 +91,11 @@ public class EntityFox extends EntityAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.foxBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.foxMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityGhast.java b/src/main/java/net/minecraft/server/EntityGhast.java +index 216506a7b1f97b776ecd4e24f5b2afaf5b79ec2d..2c0148229283f95bed3c8f33cc02a31d6f682191 100644 +--- a/src/main/java/net/minecraft/server/EntityGhast.java ++++ b/src/main/java/net/minecraft/server/EntityGhast.java +@@ -41,6 +41,11 @@ public class EntityGhast extends EntityFlying implements IMonster { + setMot(mot.a(0.9D)); + } + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.ghastMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityGuardian.java b/src/main/java/net/minecraft/server/EntityGuardian.java +index 0a7f51a2a1cb10438c9364faf3e4adc6322fc787..476f692df79e98779271cc5a395276ab9c755a58 100644 +--- a/src/main/java/net/minecraft/server/EntityGuardian.java ++++ b/src/main/java/net/minecraft/server/EntityGuardian.java +@@ -46,6 +46,11 @@ public class EntityGuardian extends EntityMonster { + public boolean isRidableInWater() { + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.guardianMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityGuardianElder.java b/src/main/java/net/minecraft/server/EntityGuardianElder.java +index f6f882746940c9e049106aa9b41591ba27a608ce..7afba8136f9a809e779d9af8c93cda7c0765c095 100644 +--- a/src/main/java/net/minecraft/server/EntityGuardianElder.java ++++ b/src/main/java/net/minecraft/server/EntityGuardianElder.java +@@ -26,6 +26,11 @@ public class EntityGuardianElder extends EntityGuardian { + public boolean isRidableInWater() { + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.elderGuardianMaxHealth); ++ } + // Purpur end + + public static AttributeProvider.Builder m() { +diff --git a/src/main/java/net/minecraft/server/EntityHoglin.java b/src/main/java/net/minecraft/server/EntityHoglin.java +index a1578aade4a535144b5e40277c902f2e9ab9e940..4a3469aca9f9e47d2ea3f3bae6ce77f5f11d6b50 100644 +--- a/src/main/java/net/minecraft/server/EntityHoglin.java ++++ b/src/main/java/net/minecraft/server/EntityHoglin.java +@@ -35,6 +35,11 @@ public class EntityHoglin extends EntityAnimal implements IMonster, IOglin { + int getPurpurBreedTime() { + return this.world.purpurConfig.hoglinBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.hoglinMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorse.java b/src/main/java/net/minecraft/server/EntityHorse.java +index 669bce5d9806c80bddc247fe103ff20dc6aaa8a5..e78e89d187f923239f23e37d5141e2e69b056360 100644 +--- a/src/main/java/net/minecraft/server/EntityHorse.java ++++ b/src/main/java/net/minecraft/server/EntityHorse.java +@@ -22,6 +22,21 @@ public class EntityHorse extends EntityHorseAbstract { + int getPurpurBreedTime() { + return this.world.purpurConfig.horseBreedingTicks; + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.horseMaxHealthMin, this.world.purpurConfig.horseMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.horseJumpStrengthMin, this.world.purpurConfig.horseJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.horseMovementSpeedMin, this.world.purpurConfig.horseMovementSpeedMax); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorseAbstract.java b/src/main/java/net/minecraft/server/EntityHorseAbstract.java +index 7958b69b2d4bee87ff5b38b8f724d248f41dff66..645431cccc8f852fd87f2b3095fd4b19bc480345 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseAbstract.java ++++ b/src/main/java/net/minecraft/server/EntityHorseAbstract.java +@@ -55,6 +55,32 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + public boolean isRidableInWater() { + return false; + } ++ ++ protected double generateMaxHealth(double min, double max) { ++ if (min == max) return min; ++ int diff = MathHelper.floor(max - min); ++ double base = max - diff; ++ int first = MathHelper.floor((double) diff / 2); ++ int rest = diff - first; ++ return base + random.nextInt(first + 1) + random.nextInt(rest + 1); ++ } ++ ++ protected double generateJumpStrength(double min, double max) { ++ if (min == max) return min; ++ return min + (max - min) * this.random.nextDouble(); ++ } ++ ++ protected double generateMovementSpeed(double min, double max) { ++ if (min == max) return min; ++ return min + (max - min) * this.random.nextDouble(); ++ } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.generateMaxHealth()); ++ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.generateMovementSpeed()); ++ this.getAttributeInstance(GenericAttributes.JUMP_STRENGTH).setValue(this.generateJumpStrength()); ++ } + // Purpur end + + @Override +@@ -863,15 +889,15 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + + } + +- protected float fp() { ++ protected float fp() { return generateMaxHealth(); } public float generateMaxHealth() { // Purpur - OBFHELPER + return 15.0F + (float) this.random.nextInt(8) + (float) this.random.nextInt(9); + } + +- protected double fq() { ++ protected double fq() { return generateJumpStrength(); } public double generateJumpStrength() { // Purpur - OBFHELPER + return 0.4000000059604645D + this.random.nextDouble() * 0.2D + this.random.nextDouble() * 0.2D + this.random.nextDouble() * 0.2D; + } + +- protected double fr() { ++ protected double fr() { return generateMovementSpeed(); } public double generateMovementSpeed() { // Purpur - OBFHELPER + return (0.44999998807907104D + this.random.nextDouble() * 0.3D + this.random.nextDouble() * 0.3D + this.random.nextDouble() * 0.3D) * 0.25D; + } + +@@ -994,7 +1020,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven + groupdataentity = new EntityAgeable.a(0.2F); + } + +- this.eK(); ++ //this.eK(); // Purpur + return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, (GroupDataEntity) groupdataentity, nbttagcompound); + } + } +diff --git a/src/main/java/net/minecraft/server/EntityHorseDonkey.java b/src/main/java/net/minecraft/server/EntityHorseDonkey.java +index f6421bb45c5e6adf39fdc085efe2b2f500b76c0c..5b7c0e9c6cce440ed54a97803a4275eea497935a 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseDonkey.java ++++ b/src/main/java/net/minecraft/server/EntityHorseDonkey.java +@@ -18,6 +18,21 @@ public class EntityHorseDonkey extends EntityHorseChestedAbstract { + int getPurpurBreedTime() { + return this.world.purpurConfig.donkeyBreedingTicks; + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.donkeyMaxHealthMin, this.world.purpurConfig.donkeyMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.donkeyJumpStrengthMin, this.world.purpurConfig.donkeyJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.donkeyMovementSpeedMin, this.world.purpurConfig.donkeyMovementSpeedMax); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityHorseMule.java b/src/main/java/net/minecraft/server/EntityHorseMule.java +index 30cbc505d2b0f4d3247edfd271de8daab023eb2a..a32c5347e4d77f5641e4eb3ea4a6ad63d0258bc3 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseMule.java ++++ b/src/main/java/net/minecraft/server/EntityHorseMule.java +@@ -18,7 +18,23 @@ public class EntityHorseMule extends EntityHorseChestedAbstract { + int getPurpurBreedTime() { + return this.world.purpurConfig.muleBreedingTicks; + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.muleMaxHealthMin, this.world.purpurConfig.muleMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.muleJumpStrengthMin, this.world.purpurConfig.muleJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.muleMovementSpeedMin, this.world.purpurConfig.muleMovementSpeedMax); ++ } + // Purpur end ++ + @Override + protected SoundEffect getSoundAmbient() { + super.getSoundAmbient(); +diff --git a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java +index 408db52cacbdfbca8af0a6a8e913b0128a3f5a76..9eac8f39ddfb598f7dfb90625829e2b10d7ec140 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java ++++ b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java +@@ -27,6 +27,21 @@ public class EntityHorseSkeleton extends EntityHorseAbstract { + int getPurpurBreedTime() { + return 6000; + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.skeletonHorseMaxHealthMin, this.world.purpurConfig.skeletonHorseMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.skeletonHorseJumpStrengthMin, this.world.purpurConfig.skeletonHorseJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.skeletonHorseMovementSpeedMin, this.world.purpurConfig.skeletonHorseMovementSpeedMax); ++ } + // Purpur end + + public static AttributeProvider.Builder eL() { +diff --git a/src/main/java/net/minecraft/server/EntityHorseZombie.java b/src/main/java/net/minecraft/server/EntityHorseZombie.java +index 2121a6c979ba2ea7cb596ca6081750d2f8c7df9f..c2dc8211b05b4ee2a2b42435f866f8e55295162d 100644 +--- a/src/main/java/net/minecraft/server/EntityHorseZombie.java ++++ b/src/main/java/net/minecraft/server/EntityHorseZombie.java +@@ -23,6 +23,21 @@ public class EntityHorseZombie extends EntityHorseAbstract { + int getPurpurBreedTime() { + return 6000; + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.zombieHorseMaxHealthMin, this.world.purpurConfig.zombieHorseMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.zombieHorseJumpStrengthMin, this.world.purpurConfig.zombieHorseJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.zombieHorseMovementSpeedMin, this.world.purpurConfig.zombieHorseMovementSpeedMax); ++ } + // Purpur end + + public static AttributeProvider.Builder eL() { +diff --git a/src/main/java/net/minecraft/server/EntityIronGolem.java b/src/main/java/net/minecraft/server/EntityIronGolem.java +index 9ee03b233b71d1b4b85a9a5e1f0ea9feb55dfe43..3e224eeb7bdcd0d1c6bd46012366c9d9878dd2a2 100644 +--- a/src/main/java/net/minecraft/server/EntityIronGolem.java ++++ b/src/main/java/net/minecraft/server/EntityIronGolem.java +@@ -32,6 +32,11 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable { + public boolean isRidableInWater() { + return world.purpurConfig.ironGolemRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.ironGolemMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java +index 3bc6e6df9e0107debe5b15f5f7aad97ad336f304..f46a7cfe832c8cca83738b71882ff0a9819a7f41 100644 +--- a/src/main/java/net/minecraft/server/EntityLlama.java ++++ b/src/main/java/net/minecraft/server/EntityLlama.java +@@ -62,6 +62,21 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn + int getPurpurBreedTime() { + return this.world.purpurConfig.llamaBreedingTicks; + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.llamaMaxHealthMin, this.world.purpurConfig.llamaMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.llamaJumpStrengthMin, this.world.purpurConfig.llamaJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.llamaMovementSpeedMin, this.world.purpurConfig.llamaMovementSpeedMax); ++ } + // Purpur end + + public void setStrength(int i) { +diff --git a/src/main/java/net/minecraft/server/EntityLlamaTrader.java b/src/main/java/net/minecraft/server/EntityLlamaTrader.java +index b6aae5cdee1f8bb842ab8e06c47fb497576b464f..665de0c28e052d195bb30ca13e1b129245eff11c 100644 +--- a/src/main/java/net/minecraft/server/EntityLlamaTrader.java ++++ b/src/main/java/net/minecraft/server/EntityLlamaTrader.java +@@ -26,6 +26,21 @@ public class EntityLlamaTrader extends EntityLlama { + public boolean hasSaddle() { + return super.hasSaddle() || isTamed(); + } ++ ++ @Override ++ public float generateMaxHealth() { ++ return (float) generateMaxHealth(this.world.purpurConfig.llamaTraderMaxHealthMin, this.world.purpurConfig.llamaTraderMaxHealthMax); ++ } ++ ++ @Override ++ public double generateJumpStrength() { ++ return generateJumpStrength(this.world.purpurConfig.llamaTraderJumpStrengthMin, this.world.purpurConfig.llamaTraderJumpStrengthMax); ++ } ++ ++ @Override ++ public double generateMovementSpeed() { ++ return generateMovementSpeed(this.world.purpurConfig.llamaTraderMovementSpeedMin, this.world.purpurConfig.llamaTraderMovementSpeedMax); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityMagmaCube.java b/src/main/java/net/minecraft/server/EntityMagmaCube.java +index cd28463e2bf944d94c121c8f8d6e37221754c168..ac24a9de3b2b24bc8afebf54ea0a947fbdac2157 100644 +--- a/src/main/java/net/minecraft/server/EntityMagmaCube.java ++++ b/src/main/java/net/minecraft/server/EntityMagmaCube.java +@@ -23,6 +23,15 @@ public class EntityMagmaCube extends EntitySlime { + public float getJumpHeight() { + return 0.42F * this.getBlockJumpFactor(); // from EntityLiving + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.generateMaxHealth()); ++ } ++ ++ public String getMaxHealthEquation() { ++ return world.purpurConfig.magmaCubeMaxHealth; ++ } + // Purpur end + + public static AttributeProvider.Builder m() { +diff --git a/src/main/java/net/minecraft/server/EntityMushroomCow.java b/src/main/java/net/minecraft/server/EntityMushroomCow.java +index eb1f95d8bae4bc2580849614ba879dd1a8792ecb..6c26cd909aff83947108e272700d13e6cc9f2b09 100644 +--- a/src/main/java/net/minecraft/server/EntityMushroomCow.java ++++ b/src/main/java/net/minecraft/server/EntityMushroomCow.java +@@ -35,6 +35,11 @@ public class EntityMushroomCow extends EntityCow implements IShearable { + int getPurpurBreedTime() { + return this.world.purpurConfig.mooshroomBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.mooshroomMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityOcelot.java b/src/main/java/net/minecraft/server/EntityOcelot.java +index a5be10dfb0de08b0d97265278b1f11ad1e94b821..b8368cf397c26d06f2827a1a786e1f241b810cce 100644 +--- a/src/main/java/net/minecraft/server/EntityOcelot.java ++++ b/src/main/java/net/minecraft/server/EntityOcelot.java +@@ -31,6 +31,11 @@ public class EntityOcelot extends EntityAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.ocelotBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.ocelotMaxHealth); ++ } + // Purpur end + + private boolean isTrusting() { +diff --git a/src/main/java/net/minecraft/server/EntityPanda.java b/src/main/java/net/minecraft/server/EntityPanda.java +index c70180fddb829419b9cc5188766e9130f9b8a94a..d202624853a1a0a0562b62ad5f3792db0916ae03 100644 +--- a/src/main/java/net/minecraft/server/EntityPanda.java ++++ b/src/main/java/net/minecraft/server/EntityPanda.java +@@ -70,6 +70,12 @@ public class EntityPanda extends EntityAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.pandaBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.pandaMaxHealth); ++ updateAttributes(); ++ } + // Purpur end + + @Override +@@ -542,9 +548,12 @@ public class EntityPanda extends EntityAnimal { + return this.random.nextBoolean() ? this.getMainGene() : this.getHiddenGene(); + } + +- public void fg() { ++ public void fg() { updateAttributes(); } public void updateAttributes() { // Purpur - OBFHELPER + if (this.isWeak()) { +- this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(10.0D); ++ // Purpur start ++ AttributeModifiable maxHealth = this.getAttributeInstance(GenericAttributes.MAX_HEALTH); ++ maxHealth.setValue(maxHealth.getValue() / 2); ++ // Purpur end + } + + if (this.isLazy()) { +diff --git a/src/main/java/net/minecraft/server/EntityParrot.java b/src/main/java/net/minecraft/server/EntityParrot.java +index 398e92bf7053c411bd98626efe4261e15256d3ee..d19f8dda87c97de594ad051a6791d99eec581e95 100644 +--- a/src/main/java/net/minecraft/server/EntityParrot.java ++++ b/src/main/java/net/minecraft/server/EntityParrot.java +@@ -120,6 +120,11 @@ public class EntityParrot extends EntityPerchable implements EntityBird { + int getPurpurBreedTime() { + return 6000; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.parrotMaxHealth); ++ } + // Purpur end + + @Nullable +diff --git a/src/main/java/net/minecraft/server/EntityPhantom.java b/src/main/java/net/minecraft/server/EntityPhantom.java +index f8c3480045e86a18501db223c1b2254cf3298a42..25345d8d585735af407787f2c26fe92674721239 100644 +--- a/src/main/java/net/minecraft/server/EntityPhantom.java ++++ b/src/main/java/net/minecraft/server/EntityPhantom.java +@@ -75,6 +75,11 @@ public class EntityPhantom extends EntityFlying implements IMonster { + world.addEntity(flames); + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.phantomMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPig.java b/src/main/java/net/minecraft/server/EntityPig.java +index 7172e8cabf5b715ae9a1087b0d11e6cee81ea6e4..61ea94586ec60dae8e7b32d91403fe93f81fc246 100644 +--- a/src/main/java/net/minecraft/server/EntityPig.java ++++ b/src/main/java/net/minecraft/server/EntityPig.java +@@ -34,6 +34,11 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable { + int getPurpurBreedTime() { + return this.world.purpurConfig.pigBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.pigMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPigZombie.java b/src/main/java/net/minecraft/server/EntityPigZombie.java +index 57f3358b8dfd53f5b1d2e976d64b809f74bc3ce3..4c050c841f9846cc74fef51d5eb69f4cbb737ef1 100644 +--- a/src/main/java/net/minecraft/server/EntityPigZombie.java ++++ b/src/main/java/net/minecraft/server/EntityPigZombie.java +@@ -46,6 +46,15 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + public boolean jockeyTryExistingChickens() { + return world.purpurConfig.zombifiedPiglinJockeyTryExistingChickens; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.zombifiedPiglinMaxHealth); ++ } ++ ++ protected void generateReinforcementsChance() { ++ this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(this.random.nextDouble() * this.world.purpurConfig.zombifiedPiglinSpawnReinforcements); ++ } + // Purpur end + + @Override +@@ -234,7 +243,7 @@ public class EntityPigZombie extends EntityZombie implements IEntityAngerable { + + @Override + protected void eV() { +- this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(0.0D); ++ generateReinforcementsChance(); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPiglin.java b/src/main/java/net/minecraft/server/EntityPiglin.java +index 334e0f73e67ef2db7e680874faf0646995d9de8a..4849829df1041568a9fcac6d16501fc0606d95da 100644 +--- a/src/main/java/net/minecraft/server/EntityPiglin.java ++++ b/src/main/java/net/minecraft/server/EntityPiglin.java +@@ -35,6 +35,11 @@ public class EntityPiglin extends EntityPiglinAbstract implements ICrossbow { + public boolean isRidableInWater() { + return world.purpurConfig.piglinRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.piglinMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPiglinBrute.java b/src/main/java/net/minecraft/server/EntityPiglinBrute.java +index f6b170811159544dc10b91226e4e54b349472c46..50e42cd8ccd48efe6251a37762953f7f55d18277 100644 +--- a/src/main/java/net/minecraft/server/EntityPiglinBrute.java ++++ b/src/main/java/net/minecraft/server/EntityPiglinBrute.java +@@ -25,6 +25,11 @@ public class EntityPiglinBrute extends EntityPiglinAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.piglinBruteRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.piglinBruteMaxHealth); ++ } + // Purpur end + + public static AttributeProvider.Builder eS() { +diff --git a/src/main/java/net/minecraft/server/EntityPillager.java b/src/main/java/net/minecraft/server/EntityPillager.java +index cf7de0127166f6175a6246062c8664e64959edeb..99b2d50d3958a9341d01108000f2719d271bdfd5 100644 +--- a/src/main/java/net/minecraft/server/EntityPillager.java ++++ b/src/main/java/net/minecraft/server/EntityPillager.java +@@ -23,6 +23,11 @@ public class EntityPillager extends EntityIllagerAbstract implements ICrossbow { + public boolean isRidableInWater() { + return world.purpurConfig.pillagerRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.pillagerMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPolarBear.java b/src/main/java/net/minecraft/server/EntityPolarBear.java +index 40395dd7ea515e51a189d014a3274d15dc1d8ee6..f72a72e6f76c3896a80a75aae255081e2f8bb507 100644 +--- a/src/main/java/net/minecraft/server/EntityPolarBear.java ++++ b/src/main/java/net/minecraft/server/EntityPolarBear.java +@@ -72,6 +72,11 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable { + int getPurpurBreedTime() { + return this.world.purpurConfig.polarBearBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.polarBearMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityPufferFish.java b/src/main/java/net/minecraft/server/EntityPufferFish.java +index 16a4df27c557e2d4a0fd4f48317307b884c2688c..815ba3062e0cbc4400f4dae9f80ce8f6745816f5 100644 +--- a/src/main/java/net/minecraft/server/EntityPufferFish.java ++++ b/src/main/java/net/minecraft/server/EntityPufferFish.java +@@ -27,6 +27,11 @@ public class EntityPufferFish extends EntityFish { + public boolean isRidableInWater() { + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.pufferfishMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java +index b7f4d61f0038d865ef9ee3c14f6dc146f8de5ac4..0583fc55056a29e7629c76b72800c727401a4a83 100644 +--- a/src/main/java/net/minecraft/server/EntityRabbit.java ++++ b/src/main/java/net/minecraft/server/EntityRabbit.java +@@ -35,6 +35,11 @@ public class EntityRabbit extends EntityAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.rabbitBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.rabbitMaxHealth); ++ } + // Purpur end + + // CraftBukkit start - code from constructor +diff --git a/src/main/java/net/minecraft/server/EntityRavager.java b/src/main/java/net/minecraft/server/EntityRavager.java +index a9021458814d84a3a82088f91956db73562c3b10..9e21603cb1681cc702084fdeebb6f93754d87bc8 100644 +--- a/src/main/java/net/minecraft/server/EntityRavager.java ++++ b/src/main/java/net/minecraft/server/EntityRavager.java +@@ -39,6 +39,11 @@ public class EntityRavager extends EntityRaider { + double speed = this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).getBaseValue(); + getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(speed); + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.ravagerMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySalmon.java b/src/main/java/net/minecraft/server/EntitySalmon.java +index 6dfcee2c9b658c2c9ee1179e412389934c066d48..475c375bebe2c7211869af28d23db3107b39fe51 100644 +--- a/src/main/java/net/minecraft/server/EntitySalmon.java ++++ b/src/main/java/net/minecraft/server/EntitySalmon.java +@@ -16,6 +16,11 @@ public class EntitySalmon extends EntityFishSchool { + public boolean isRidableInWater() { + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.salmonMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySheep.java b/src/main/java/net/minecraft/server/EntitySheep.java +index 32130c0681501e3e5a47b199f0bb39daac416ed3..d9fe8cf00088dba516ea2bfd7e9590d95c7e7939 100644 +--- a/src/main/java/net/minecraft/server/EntitySheep.java ++++ b/src/main/java/net/minecraft/server/EntitySheep.java +@@ -71,6 +71,11 @@ public class EntitySheep extends EntityAnimal implements IShearable { + int getPurpurBreedTime() { + return this.world.purpurConfig.sheepBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.sheepMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityShulker.java b/src/main/java/net/minecraft/server/EntityShulker.java +index 30e0e14162cce0c0d228139d4c537243a400ef13..d264b9469c891a0b81986d9be12b2cd8510b6015 100644 +--- a/src/main/java/net/minecraft/server/EntityShulker.java ++++ b/src/main/java/net/minecraft/server/EntityShulker.java +@@ -39,6 +39,11 @@ public class EntityShulker extends EntityGolem implements IMonster { + public boolean isRidableInWater() { + return world.purpurConfig.shulkerRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.shulkerMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySilverfish.java b/src/main/java/net/minecraft/server/EntitySilverfish.java +index ad428e090089a461283445022b33313520585ac5..6bd00f0b5735d694e370cf85fdbf508c31fc7c27 100644 +--- a/src/main/java/net/minecraft/server/EntitySilverfish.java ++++ b/src/main/java/net/minecraft/server/EntitySilverfish.java +@@ -21,6 +21,11 @@ public class EntitySilverfish extends EntityMonster { + public boolean isRidableInWater() { + return world.purpurConfig.silverfishRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.silverfishMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySkeleton.java b/src/main/java/net/minecraft/server/EntitySkeleton.java +index 3f130e03bf8b235360385fd169d4886ffcfa626a..a78e593cbdfa4a74d07fd53c9481534c856d01e6 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeleton.java ++++ b/src/main/java/net/minecraft/server/EntitySkeleton.java +@@ -16,6 +16,11 @@ public class EntitySkeleton extends EntitySkeletonAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.skeletonRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.skeletonMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySkeletonStray.java b/src/main/java/net/minecraft/server/EntitySkeletonStray.java +index d123fb82b635d5271bea9b238554a3011858eeae..e34ab9eda7f610456193bdc0495c2ba478917625 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeletonStray.java ++++ b/src/main/java/net/minecraft/server/EntitySkeletonStray.java +@@ -18,6 +18,11 @@ public class EntitySkeletonStray extends EntitySkeletonAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.strayRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.strayMaxHealth); ++ } + // Purpur end + + public static boolean a(EntityTypes entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { +diff --git a/src/main/java/net/minecraft/server/EntitySkeletonWither.java b/src/main/java/net/minecraft/server/EntitySkeletonWither.java +index 96cb080d940db22330598a8806726088b79a53c1..ee7805dccac9c4dcdf4fa83ab8041953dccdc83b 100644 +--- a/src/main/java/net/minecraft/server/EntitySkeletonWither.java ++++ b/src/main/java/net/minecraft/server/EntitySkeletonWither.java +@@ -19,6 +19,11 @@ public class EntitySkeletonWither extends EntitySkeletonAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.witherSkeletonRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.witherSkeletonMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySlime.java b/src/main/java/net/minecraft/server/EntitySlime.java +index 3c14ed27e5b487451846f6811f189e6fba05d32d..36b384fd8fc29b270bb8c0ccc66ffad15183cf94 100644 +--- a/src/main/java/net/minecraft/server/EntitySlime.java ++++ b/src/main/java/net/minecraft/server/EntitySlime.java +@@ -10,6 +10,7 @@ import com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent; + import com.destroystokyo.paper.event.entity.SlimeSwimEvent; + import com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent; + import com.destroystokyo.paper.event.entity.SlimeWanderEvent; ++import javax.script.ScriptException; + import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Slime; + // Paper end +@@ -24,6 +25,7 @@ import org.bukkit.event.entity.SlimeSplitEvent; + public class EntitySlime extends EntityInsentient implements IMonster { + + private static final DataWatcherObject bo = DataWatcher.a(EntitySlime.class, DataWatcherRegistry.b); ++ private static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur + public float b; + public float c; + public float d; +@@ -63,6 +65,25 @@ public class EntitySlime extends EntityInsentient implements IMonster { + } + return true; // do not jump() in wasd controller, let vanilla controller handle + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.generateMaxHealth()); ++ } ++ ++ public String getMaxHealthEquation() { ++ return world.purpurConfig.slimeMaxHealth; ++ } ++ ++ public double generateMaxHealth() { ++ int size = getSize(); ++ try { ++ scriptEngine.eval("size = " + size); ++ return (double) scriptEngine.eval(getMaxHealthEquation()); ++ } catch (Exception e) { ++ return size * size; ++ } ++ } + // Purpur end + + @Override +@@ -89,7 +110,7 @@ public class EntitySlime extends EntityInsentient implements IMonster { + this.datawatcher.set(EntitySlime.bo, i); + this.af(); + this.updateSize(); +- this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue((double) (i * i)); ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(generateMaxHealth()); // Purpur + this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue((double) (0.2F + 0.1F * (float) i)); + this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue((double) i); + if (flag) { +diff --git a/src/main/java/net/minecraft/server/EntitySnowman.java b/src/main/java/net/minecraft/server/EntitySnowman.java +index e5b3d298f52006f39a36cfdd95097e7b4f89939a..319bb68f27f360bb36897c73a68ddeac64b67a6f 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowman.java ++++ b/src/main/java/net/minecraft/server/EntitySnowman.java +@@ -24,6 +24,11 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + public boolean isRidableInWater() { + return world.purpurConfig.snowGolemRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.snowGolemMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySpider.java b/src/main/java/net/minecraft/server/EntitySpider.java +index 92d74137877d096970bf9d1b4fc91beabb862b9d..9001ca50b8d9f77b0d997511b70c39b2712df67a 100644 +--- a/src/main/java/net/minecraft/server/EntitySpider.java ++++ b/src/main/java/net/minecraft/server/EntitySpider.java +@@ -21,6 +21,11 @@ public class EntitySpider extends EntityMonster { + public boolean isRidableInWater() { + return world.purpurConfig.spiderRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.spiderMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntitySquid.java b/src/main/java/net/minecraft/server/EntitySquid.java +index 70b952f10a2af547f58069977ee135469d02f84d..fab7aa5c9f226873504801d7405a7c58043572f7 100644 +--- a/src/main/java/net/minecraft/server/EntitySquid.java ++++ b/src/main/java/net/minecraft/server/EntitySquid.java +@@ -51,6 +51,11 @@ public class EntitySquid extends EntityWaterAnimal { + vector.setX(cos * x - sine * z); + vector.setZ(sine * x + cos * z); + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.squidMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityStrider.java b/src/main/java/net/minecraft/server/EntityStrider.java +index 9ab1b5af68774fa4d4da89195fdb91172370d43d..a0bb64bea373c678c519e3fae8f808fd36e1ee4f 100644 +--- a/src/main/java/net/minecraft/server/EntityStrider.java ++++ b/src/main/java/net/minecraft/server/EntityStrider.java +@@ -43,6 +43,11 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab + int getPurpurBreedTime() { + return this.world.purpurConfig.striderBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.striderMaxHealth); ++ } + // Purpur end + + public static boolean c(EntityTypes entitytypes, GeneratorAccess generatoraccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { +diff --git a/src/main/java/net/minecraft/server/EntityTropicalFish.java b/src/main/java/net/minecraft/server/EntityTropicalFish.java +index 2c9df356e685ea6f71653023fadcf7e287dcd46e..4bf7b697c18f3d3a1987471571d665ed823c5c77 100644 +--- a/src/main/java/net/minecraft/server/EntityTropicalFish.java ++++ b/src/main/java/net/minecraft/server/EntityTropicalFish.java +@@ -29,6 +29,11 @@ public class EntityTropicalFish extends EntityFishSchool { + public boolean isRidableInWater() { + return true; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.tropicalFishMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java +index 067f7f28b02b388d56b93b1ed8274799757196e6..0233b65a996111af7bdc10e4da2d70e541411c09 100644 +--- a/src/main/java/net/minecraft/server/EntityTurtle.java ++++ b/src/main/java/net/minecraft/server/EntityTurtle.java +@@ -42,6 +42,11 @@ public class EntityTurtle extends EntityAnimal { + int getPurpurBreedTime() { + return this.world.purpurConfig.turtleBreedingTicks; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.turtleMaxHealth); ++ } + // Purpur end + + public void setHomePos(BlockPosition blockposition) { +diff --git a/src/main/java/net/minecraft/server/EntityVex.java b/src/main/java/net/minecraft/server/EntityVex.java +index ac75ed3e2e0e0cd8f91de9ff188e173591443b72..af85dcc824e4ad50eca2ccd957da7b68c05d6b41 100644 +--- a/src/main/java/net/minecraft/server/EntityVex.java ++++ b/src/main/java/net/minecraft/server/EntityVex.java +@@ -56,6 +56,11 @@ public class EntityVex extends EntityMonster { + public boolean b(float f, float f1) { + return false; // no fall damage please + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.vexMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java +index 60962553e4f374b38de82f2cd4d72cef3d956c72..eef51f8e5734b897164ca9514e7b49b2678416e6 100644 +--- a/src/main/java/net/minecraft/server/EntityVillager.java ++++ b/src/main/java/net/minecraft/server/EntityVillager.java +@@ -95,6 +95,11 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + public boolean a(EntityHuman entityhuman) { + return world.purpurConfig.villagerCanBeLeashed && !this.isLeashed(); + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.villagerMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityVillagerTrader.java b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +index 3ea66955df304fd13aac2cf9bb93ea156558ae57..32864e522b63d5d02c73a4df9f996c2ebe1b53ad 100644 +--- a/src/main/java/net/minecraft/server/EntityVillagerTrader.java ++++ b/src/main/java/net/minecraft/server/EntityVillagerTrader.java +@@ -63,6 +63,11 @@ public class EntityVillagerTrader extends EntityVillagerAbstract { + public boolean a(EntityHuman entityhuman) { + return world.purpurConfig.villagerTraderCanBeLeashed && !this.isLeashed(); + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.villagerTraderMaxHealth); ++ } + // Purpur - end + + @Nullable +diff --git a/src/main/java/net/minecraft/server/EntityVindicator.java b/src/main/java/net/minecraft/server/EntityVindicator.java +index f1bc6a4199d788215c2e7d5a835211d16603a6d9..2a137a90828f84de15a7184203c7d0dccc0851b4 100644 +--- a/src/main/java/net/minecraft/server/EntityVindicator.java ++++ b/src/main/java/net/minecraft/server/EntityVindicator.java +@@ -28,6 +28,11 @@ public class EntityVindicator extends EntityIllagerAbstract { + public boolean isRidableInWater() { + return world.purpurConfig.vindicatorRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.vindicatorMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityWitch.java b/src/main/java/net/minecraft/server/EntityWitch.java +index 323d79a99402b0f6952b4fb873170069f3428953..dc846e7bda587a47b1cce9054e587a734179e7fe 100644 +--- a/src/main/java/net/minecraft/server/EntityWitch.java ++++ b/src/main/java/net/minecraft/server/EntityWitch.java +@@ -34,6 +34,11 @@ public class EntityWitch extends EntityRaider implements IRangedEntity { + public boolean isRidableInWater() { + return world.purpurConfig.witchRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.witchMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java +index fd62dc51258876275adbe02f750fd88107c38a6b..03bcbf7c280476ef0e6fe87e3a96edb75544bddb 100644 +--- a/src/main/java/net/minecraft/server/EntityWolf.java ++++ b/src/main/java/net/minecraft/server/EntityWolf.java +@@ -121,6 +121,11 @@ public class EntityWolf extends EntityTameableAnimal implements IEntityAngerable + setCollarColor(world.purpurConfig.wolfDefaultCollarColor); + super.tame(entityhuman); + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.wolfMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityZoglin.java b/src/main/java/net/minecraft/server/EntityZoglin.java +index d92fe8013fb3b43cb7eabdf1c624291b7e881889..bcc4aa1d3f09e43016d0009cd04dd6b797dcfeca 100644 +--- a/src/main/java/net/minecraft/server/EntityZoglin.java ++++ b/src/main/java/net/minecraft/server/EntityZoglin.java +@@ -31,6 +31,11 @@ public class EntityZoglin extends EntityMonster implements IMonster, IOglin { + public boolean isRidableInWater() { + return world.purpurConfig.zoglinRidableInWater; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.zoglinMaxHealth); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java +index 556f7a3ebb5c58a87471b2d098f29ffb216aaa1d..a5699314be3f47ed9b27a5d21a396c5282592c7b 100644 +--- a/src/main/java/net/minecraft/server/EntityZombie.java ++++ b/src/main/java/net/minecraft/server/EntityZombie.java +@@ -68,6 +68,15 @@ public class EntityZombie extends EntityMonster { + public boolean jockeyTryExistingChickens() { + return world.purpurConfig.zombieJockeyTryExistingChickens; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.zombieMaxHealth); ++ } ++ ++ protected void generateReinforcementsChance() { ++ this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(this.random.nextDouble() * this.world.purpurConfig.zombieSpawnReinforcements); ++ } + // Purpur end + + @Override +@@ -547,7 +556,7 @@ public class EntityZombie extends EntityMonster { + } + + protected void eV() { +- this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(this.random.nextDouble() * 0.10000000149011612D); ++ generateReinforcementsChance(); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityZombieHusk.java b/src/main/java/net/minecraft/server/EntityZombieHusk.java +index 02b0ae550a0ed33b5b43beedf3b1405985c58c13..966a14b4122b5ca43832a57fcbc162147f903a21 100644 +--- a/src/main/java/net/minecraft/server/EntityZombieHusk.java ++++ b/src/main/java/net/minecraft/server/EntityZombieHusk.java +@@ -33,6 +33,15 @@ public class EntityZombieHusk extends EntityZombie { + public boolean jockeyTryExistingChickens() { + return world.purpurConfig.huskJockeyTryExistingChickens; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.huskMaxHealth); ++ } ++ ++ protected void generateReinforcementsChance() { ++ this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(this.random.nextDouble() * this.world.purpurConfig.huskSpawnReinforcements); ++ } + // Purpur end + + public static boolean a(EntityTypes entitytypes, WorldAccess worldaccess, EnumMobSpawn enummobspawn, BlockPosition blockposition, Random random) { +diff --git a/src/main/java/net/minecraft/server/EntityZombieVillager.java b/src/main/java/net/minecraft/server/EntityZombieVillager.java +index 0c47477b416980d2e932321730525bf5a8feda4f..dc850677c29c16805f28af00b2a633638a776f0d 100644 +--- a/src/main/java/net/minecraft/server/EntityZombieVillager.java ++++ b/src/main/java/net/minecraft/server/EntityZombieVillager.java +@@ -53,6 +53,15 @@ public class EntityZombieVillager extends EntityZombie implements VillagerDataHo + public boolean jockeyTryExistingChickens() { + return world.purpurConfig.zombieVillagerJockeyTryExistingChickens; + } ++ ++ @Override ++ public void initAttributes() { ++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.zombieVillagerMaxHealth); ++ } ++ ++ protected void generateReinforcementsChance() { ++ this.getAttributeInstance(GenericAttributes.SPAWN_REINFORCEMENTS).setValue(this.random.nextDouble() * this.world.purpurConfig.zombieVillagerSpawnReinforcements); ++ } + // Purpur end + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f04307e70f072506668684dc649d5bd78566435f..e9c7e1b65c054fd262b2b5c2032456871499ace7 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -472,30 +472,58 @@ public class PurpurWorldConfig { + public boolean batRidable = false; + public boolean batRidableInWater = false; + public double batMaxY = 256D; ++ public double batMaxHealth = 6.0D; ++ public double batFollowRange = 16.0D; ++ public double batKnockbackResistance = 0.0D; ++ public double batMovementSpeed = 0.6D; ++ public double batFlyingSpeed = 0.6D; ++ public double batArmor = 0.0D; ++ public double batArmorToughness = 0.0D; ++ public double batAttackKnockback = 0.0D; + private void batSettings() { + batRidable = getBoolean("mobs.bat.ridable", batRidable); + batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater); + batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth); ++ set("mobs.bat.attributes.max-health", null); ++ set("mobs.bat.attributes.max_health", oldValue); ++ } ++ batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth); + } + + public boolean beeRidable = false; + public boolean beeRidableInWater = false; + public double beeMaxY = 256D; + public int beeBreedingTicks = 6000; ++ public double beeMaxHealth = 10.0D; + private void beeSettings() { + beeRidable = getBoolean("mobs.bee.ridable", beeRidable); + beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); + beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); + beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth); ++ set("mobs.bee.attributes.max-health", null); ++ set("mobs.bee.attributes.max_health", oldValue); ++ } ++ beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); + } + + public boolean blazeRidable = false; + public boolean blazeRidableInWater = false; + public double blazeMaxY = 256D; ++ public double blazeMaxHealth = 20.0D; + private void blazeSettings() { + blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); + blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); + blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth); ++ set("mobs.blaze.attributes.max-health", null); ++ set("mobs.blaze.attributes.max_health", oldValue); ++ } ++ blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth); + } + + public boolean catRidable = false; +@@ -504,6 +532,7 @@ public class PurpurWorldConfig { + public int catSpawnSwampHutScanRange = 16; + public int catSpawnVillageScanRange = 48; + public int catBreedingTicks = 6000; ++ public double catMaxHealth = 10.0D; + private void catSettings() { + catRidable = getBoolean("mobs.cat.ridable", catRidable); + catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); +@@ -511,51 +540,92 @@ public class PurpurWorldConfig { + catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); + catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); + catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth); ++ set("mobs.cat.attributes.max-health", null); ++ set("mobs.cat.attributes.max_health", oldValue); ++ } ++ catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth); + } + + public boolean caveSpiderRidable = false; + public boolean caveSpiderRidableInWater = false; ++ public double caveSpiderMaxHealth = 12.0D; + private void caveSpiderSettings() { + caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable); + caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth); ++ set("mobs.cave_spider.attributes.max-health", null); ++ set("mobs.cave_spider.attributes.max_health", oldValue); ++ } ++ caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth); + } + + public boolean chickenRidable = false; + public boolean chickenRidableInWater = false; + public boolean chickenRetaliate = false; + public int chickenBreedingTicks = 6000; ++ public double chickenMaxHealth = 4.0D; + private void chickenSettings() { + chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); + chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); + chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth); ++ set("mobs.chicken.attributes.max-health", null); ++ set("mobs.chicken.attributes.max_health", oldValue); ++ } ++ chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); + } + + public boolean codRidable = false; ++ public double codMaxHealth = 3.0D; + private void codSettings() { + codRidable = getBoolean("mobs.cod.ridable", codRidable); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth); ++ set("mobs.cod.attributes.max-health", null); ++ set("mobs.cod.attributes.max_health", oldValue); ++ } ++ codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth); + } + + public boolean cowRidable = false; + public boolean cowRidableInWater = false; + public int cowFeedMushrooms = 0; + public int cowBreedingTicks = 6000; ++ public double cowMaxHealth = 10.0D; + private void cowSettings() { + cowRidable = getBoolean("mobs.cow.ridable", cowRidable); + cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); + cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth); ++ set("mobs.cow.attributes.max-health", null); ++ set("mobs.cow.attributes.max_health", oldValue); ++ } ++ cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); + } + + public boolean creeperRidable = false; + public boolean creeperRidableInWater = false; + public boolean creeperAllowGriefing = true; + public double creeperChargedChance = 0.0D; ++ public double creeperMaxHealth = 20.0D; + private void creeperSettings() { + creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); + creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); + creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth); ++ set("mobs.creeper.attributes.max-health", null); ++ set("mobs.creeper.attributes.max_health", oldValue); ++ } ++ creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); + } + + public boolean dolphinRidable = false; +@@ -563,19 +633,45 @@ public class PurpurWorldConfig { + public float dolphinSpitSpeed = 1.0F; + public float dolphinSpitDamage = 2.0F; + public boolean dolphinDisableTreasureSearching = false; ++ public double dolphinMaxHealth = 10.0D; + private void dolphinSettings() { + dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); + dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); + dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed); + dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage); + dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth); ++ set("mobs.dolphin.attributes.max-health", null); ++ set("mobs.dolphin.attributes.max_health", oldValue); ++ } ++ dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth); + } + + public boolean donkeyRidableInWater = false; + public int donkeyBreedingTicks = 6000; ++ public double donkeyMaxHealthMin = 15.0D; ++ public double donkeyMaxHealthMax = 30.0D; ++ public double donkeyJumpStrengthMin = 0.5D; ++ public double donkeyJumpStrengthMax = 0.5D; ++ public double donkeyMovementSpeedMin = 0.175D; ++ public double donkeyMovementSpeedMax = 0.175D; + private void donkeySettings() { + donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); + donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin); ++ double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax); ++ set("mobs.donkey.attributes.max-health", null); ++ set("mobs.donkey.attributes.max_health.min", oldMin); ++ set("mobs.donkey.attributes.max_health.max", oldMax); ++ } ++ donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin); ++ donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax); ++ donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin); ++ donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax); ++ donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin); ++ donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax); + } + + public boolean drownedRidable = false; +@@ -583,17 +679,33 @@ public class PurpurWorldConfig { + public boolean drownedJockeyOnlyBaby = true; + public double drownedJockeyChance = 0.05D; + public boolean drownedJockeyTryExistingChickens = true; ++ public double drownedMaxHealth = 20.0D; ++ public double drownedSpawnReinforcements = 0.1D; + private void drownedSettings() { + drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); + drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); + drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); + drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); + drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth); ++ set("mobs.drowned.attributes.max-health", null); ++ set("mobs.drowned.attributes.max_health", oldValue); ++ } ++ drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth); ++ drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements); + } + + public boolean elderGuardianRidable = false; ++ public double elderGuardianMaxHealth = 80.0D; + private void elderGuardianSettings() { + elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth); ++ set("mobs.elder_guardian.attributes.max-health", null); ++ set("mobs.elder_guardian.attributes.max_health", oldValue); ++ } ++ elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth); + } + + public boolean enderDragonRidable = false; +@@ -601,57 +713,103 @@ public class PurpurWorldConfig { + public double enderDragonMaxY = 256D; + public boolean enderDragonAlwaysDropsEggBlock = false; + public boolean enderDragonAlwaysDropsFullExp = false; ++ public double enderDragonMaxHealth = 200.0D; + private void enderDragonSettings() { + enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); + enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); + enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); + enderDragonAlwaysDropsEggBlock = getBoolean("mobs.ender_dragon.always-drop-egg-block", enderDragonAlwaysDropsEggBlock); + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); ++ if (PurpurConfig.version < 8) { ++ double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth); ++ set("mobs.ender_dragon.max-health", null); ++ set("mobs.ender_dragon.attributes.max_health", oldValue); ++ } else if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth); ++ set("mobs.ender_dragon.attributes.max-health", null); ++ set("mobs.ender_dragon.attributes.max_health", oldValue); ++ } ++ enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); + } + + public boolean endermanRidable = false; + public boolean endermanRidableInWater = false; + public boolean endermanAllowGriefing = true; + public boolean endermanDespawnEvenWithBlock = false; ++ public double endermanMaxHealth = 40.0D; + private void endermanSettings() { + endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); + endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); + endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth); ++ set("mobs.enderman.attributes.max-health", null); ++ set("mobs.enderman.attributes.max_health", oldValue); ++ } ++ endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); + } + + public boolean endermiteRidable = false; + public boolean endermiteRidableInWater = false; ++ public double endermiteMaxHealth = 8.0D; + private void endermiteSettings() { + endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); + endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth); ++ set("mobs.endermite.attributes.max-health", null); ++ set("mobs.endermite.attributes.max_health", oldValue); ++ } ++ endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth); + } + + public boolean evokerRidable = false; + public boolean evokerRidableInWater = false; ++ public double evokerMaxHealth = 24.0D; + private void evokerSettings() { + evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); + evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth); ++ set("mobs.evoker.attributes.max-health", null); ++ set("mobs.evoker.attributes.max_health", oldValue); ++ } ++ evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth); + } + + public boolean foxRidable = false; + public boolean foxRidableInWater = false; + public boolean foxTypeChangesWithTulips = false; + public int foxBreedingTicks = 6000; ++ public double foxMaxHealth = 10.0D; + private void foxSettings() { + foxRidable = getBoolean("mobs.fox.ridable", foxRidable); + foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); + foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth); ++ set("mobs.fox.attributes.max-health", null); ++ set("mobs.fox.attributes.max_health", oldValue); ++ } ++ foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); + } + + public boolean ghastRidable = false; + public boolean ghastRidableInWater = false; + public double ghastMaxY = 256D; ++ public double ghastMaxHealth = 10.0D; + private void ghastSettings() { + ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable); + ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater); + ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth); ++ set("mobs.ghast.attributes.max-health", null); ++ set("mobs.ghast.attributes.max_health", oldValue); ++ } ++ ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth); + } + + public boolean giantRidable = false; +@@ -674,31 +832,68 @@ public class PurpurWorldConfig { + giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth); +- set("mobs.giant.attributes.max-health", oldValue); + set("mobs.giant.max-health", null); ++ set("mobs.giant.attributes.max_health", oldValue); ++ } else if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); ++ set("mobs.giant.attributes.max-health", null); ++ set("mobs.giant.attributes.max_health", oldValue); + } +- giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); ++ giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth); + } + + public boolean guardianRidable = false; ++ public double guardianMaxHealth = 30.0D; + private void guardianSettings() { + guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth); ++ set("mobs.guardian.attributes.max-health", null); ++ set("mobs.guardian.attributes.max_health", oldValue); ++ } ++ guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth); + } + + public boolean hoglinRidable = false; + public boolean hoglinRidableInWater = false; + public int hoglinBreedingTicks = 6000; ++ public double hoglinMaxHealth = 40.0D; + private void hoglinSettings() { + hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); + hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); + hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth); ++ set("mobs.hoglin.attributes.max-health", null); ++ set("mobs.hoglin.attributes.max_health", oldValue); ++ } ++ hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth); + } + + public boolean horseRidableInWater = false; + public int horseBreedingTicks = 6000; ++ public double horseMaxHealthMin = 15.0D; ++ public double horseMaxHealthMax = 30.0D; ++ public double horseJumpStrengthMin = 0.4D; ++ public double horseJumpStrengthMax = 1.0D; ++ public double horseMovementSpeedMin = 0.1125D; ++ public double horseMovementSpeedMax = 0.3375D; + private void horseSettings() { + horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); + horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin); ++ double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax); ++ set("mobs.horse.attributes.max-health", null); ++ set("mobs.horse.attributes.max_health.min", oldMin); ++ set("mobs.horse.attributes.max_health.max", oldMax); ++ } ++ horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin); ++ horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax); ++ horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin); ++ horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax); ++ horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin); ++ horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax); + } + + public boolean huskRidable = false; +@@ -706,12 +901,21 @@ public class PurpurWorldConfig { + public boolean huskJockeyOnlyBaby = true; + public double huskJockeyChance = 0.05D; + public boolean huskJockeyTryExistingChickens = true; ++ public double huskMaxHealth = 20.0D; ++ public double huskSpawnReinforcements = 0.1D; + private void huskSettings() { + huskRidable = getBoolean("mobs.husk.ridable", huskRidable); + huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); + huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); + huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); + huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth); ++ set("mobs.husk.attributes.max-health", null); ++ set("mobs.husk.attributes.max_health", oldValue); ++ } ++ huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth); ++ huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements); + } + + public boolean illusionerRidable = false; +@@ -726,85 +930,188 @@ public class PurpurWorldConfig { + illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth); +- set("mobs.illusioner.attributes.max-health", oldValue); + set("mobs.illusioner.max-health", null); ++ set("mobs.illusioner.attributes.max_health", oldValue); ++ } else if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); ++ set("mobs.illusioner.attributes.max-health", null); ++ set("mobs.illusioner.attributes.max_health", oldValue); + } +- illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); ++ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth); + } + + public boolean ironGolemRidable = false; + public boolean ironGolemRidableInWater = false; + public boolean ironGolemCanSwim = false; ++ public double ironGolemMaxHealth = 100.0D; + private void ironGolemSettings() { + ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); + ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth); ++ set("mobs.iron_golem.attributes.max-health", null); ++ set("mobs.iron_golem.attributes.max_health", oldValue); ++ } ++ ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth); + } + + public boolean llamaRidable = false; + public boolean llamaRidableInWater = false; + public int llamaBreedingTicks = 6000; ++ public double llamaMaxHealthMin = 15.0D; ++ public double llamaMaxHealthMax = 30.0D; ++ public double llamaJumpStrengthMin = 0.5D; ++ public double llamaJumpStrengthMax = 0.5D; ++ public double llamaMovementSpeedMin = 0.175D; ++ public double llamaMovementSpeedMax = 0.175D; + private void llamaSettings() { + llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); + llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); + llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin); ++ double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax); ++ set("mobs.llama.attributes.max-health", null); ++ set("mobs.llama.attributes.max_health.min", oldMin); ++ set("mobs.llama.attributes.max_health.max", oldMax); ++ } ++ llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin); ++ llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax); ++ llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin); ++ llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax); ++ llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); ++ llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); + } + + public boolean llamaTraderRidable = false; + public boolean llamaTraderRidableInWater = false; ++ public double llamaTraderMaxHealthMin = 15.0D; ++ public double llamaTraderMaxHealthMax = 30.0D; ++ public double llamaTraderJumpStrengthMin = 0.5D; ++ public double llamaTraderJumpStrengthMax = 0.5D; ++ public double llamaTraderMovementSpeedMin = 0.175D; ++ public double llamaTraderMovementSpeedMax = 0.175D; + private void llamaTraderSettings() { + llamaTraderRidable = getBoolean("mobs.trader_llama.ridable", llamaTraderRidable); + llamaTraderRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", llamaTraderRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", llamaTraderMaxHealthMin); ++ double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", llamaTraderMaxHealthMax); ++ set("mobs.trader_llama.attributes.max-health", null); ++ set("mobs.trader_llama.attributes.max_health.min", oldMin); ++ set("mobs.trader_llama.attributes.max_health.max", oldMax); ++ } ++ llamaTraderMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", llamaTraderMaxHealthMin); ++ llamaTraderMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", llamaTraderMaxHealthMax); ++ llamaTraderJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", llamaTraderJumpStrengthMin); ++ llamaTraderJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", llamaTraderJumpStrengthMax); ++ llamaTraderMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", llamaTraderMovementSpeedMin); ++ llamaTraderMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", llamaTraderMovementSpeedMax); + } + + public boolean magmaCubeRidable = false; + public boolean magmaCubeRidableInWater = false; ++ public String magmaCubeMaxHealth = "size * size"; + private void magmaCubeSettings() { + magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable); + magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater); ++ if (PurpurConfig.version < 10) { ++ String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth); ++ set("mobs.magma_cube.attributes.max-health", null); ++ set("mobs.magma_cube.attributes.max_health", oldValue); ++ } ++ magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth); + } + + public boolean mooshroomRidable = false; + public boolean mooshroomRidableInWater = false; + public int mooshroomBreedingTicks = 6000; ++ public double mooshroomMaxHealth = 10.0D; + private void mooshroomSettings() { + mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); + mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); + mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth); ++ set("mobs.mooshroom.attributes.max-health", null); ++ set("mobs.mooshroom.attributes.max_health", oldValue); ++ } ++ mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth); + } + + public boolean muleRidableInWater = false; + public int muleBreedingTicks = 6000; ++ public double muleMaxHealthMin = 15.0D; ++ public double muleMaxHealthMax = 30.0D; ++ public double muleJumpStrengthMin = 0.5D; ++ public double muleJumpStrengthMax = 0.5D; ++ public double muleMovementSpeedMin = 0.175D; ++ public double muleMovementSpeedMax = 0.175D; + private void muleSettings() { + muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); + muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin); ++ double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax); ++ set("mobs.mule.attributes.max-health", null); ++ set("mobs.mule.attributes.max_health.min", oldMin); ++ set("mobs.mule.attributes.max_health.max", oldMax); ++ } ++ muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin); ++ muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax); ++ muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin); ++ muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax); ++ muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin); ++ muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax); + } + + public boolean ocelotRidable = false; + public boolean ocelotRidableInWater = false; + public int ocelotBreedingTicks = 6000; ++ public double ocelotMaxHealth = 10.0D; + private void ocelotSettings() { + ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); + ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); + ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth); ++ set("mobs.ocelot.attributes.max-health", null); ++ set("mobs.ocelot.attributes.max_health", oldValue); ++ } ++ ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth); + } + + public boolean pandaRidable = false; + public boolean pandaRidableInWater = false; + public int pandaBreedingTicks = 6000; ++ public double pandaMaxHealth = 20.0D; + private void pandaSettings() { + pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); + pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); + pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth); ++ set("mobs.panda.attributes.max-health", null); ++ set("mobs.panda.attributes.max_health", oldValue); ++ } ++ pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth); + } + + public boolean parrotRidable = false; + public boolean parrotRidableInWater = false; + public double parrotMaxY = 256D; ++ public double parrotMaxHealth = 6.0D; + private void parrotSettings() { + parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); + parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); + parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth); ++ set("mobs.parrot.attributes.max-health", null); ++ set("mobs.parrot.attributes.max_health", oldValue); ++ } ++ parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth); + } + + public boolean phantomRidable = false; +@@ -830,6 +1137,7 @@ public class PurpurWorldConfig { + public int phantomBurnInLight = 0; + public boolean phantomIgnorePlayersWithTorch = false; + public boolean phantomBurnInDaylight = true; ++ public double phantomMaxHealth = 20.0D; + private void phantomSettings() { + phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); + phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); +@@ -854,38 +1162,72 @@ public class PurpurWorldConfig { + phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); + phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); + phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.phantom.attributes.max-health", phantomMaxHealth); ++ set("mobs.phantom.attributes.max-health", null); ++ set("mobs.phantom.attributes.max_health", oldValue); ++ } ++ phantomMaxHealth = getDouble("mobs.phantom.attributes.max_health", phantomMaxHealth); + } + + public boolean pigRidable = false; + public boolean pigRidableInWater = false; + public boolean pigGiveSaddleBack = false; + public int pigBreedingTicks = 6000; ++ public double pigMaxHealth = 10.0D; + private void pigSettings() { + pigRidable = getBoolean("mobs.pig.ridable", pigRidable); + pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); + pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth); ++ set("mobs.pig.attributes.max-health", null); ++ set("mobs.pig.attributes.max_health", oldValue); ++ } ++ pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); + } + + public boolean piglinRidable = false; + public boolean piglinRidableInWater = false; ++ public double piglinMaxHealth = 16.0D; + private void piglinSettings() { + piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); + piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth); ++ set("mobs.piglin.attributes.max-health", null); ++ set("mobs.piglin.attributes.max_health", oldValue); ++ } ++ piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); + } + + public boolean piglinBruteRidable = false; + public boolean piglinBruteRidableInWater = false; ++ public double piglinBruteMaxHealth = 50.0D; + private void piglinBruteSettings() { + piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable); + piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth); ++ set("mobs.piglin_brute.attributes.max-health", null); ++ set("mobs.piglin_brute.attributes.max_health", oldValue); ++ } ++ piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth); + } + + public boolean pillagerRidable = false; + public boolean pillagerRidableInWater = false; ++ public double pillagerMaxHealth = 24.0D; + private void pillagerSettings() { + pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); + pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth); ++ set("mobs.pillager.attributes.max-health", null); ++ set("mobs.pillager.attributes.max_health", oldValue); ++ } ++ pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth); + } + + public boolean polarBearRidable = false; +@@ -893,6 +1235,7 @@ public class PurpurWorldConfig { + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; + public int polarBearBreedingTicks = 6000; ++ public double polarBearMaxHealth = 30.0D; + private void polarBearSettings() { + polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); + polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); +@@ -900,11 +1243,24 @@ public class PurpurWorldConfig { + Item item = IRegistry.ITEM.get(new MinecraftKey(polarBearBreedableItemString)); + if (item != Items.AIR) polarBearBreedableItem = item; + polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth); ++ set("mobs.polar_bear.attributes.max-health", null); ++ set("mobs.polar_bear.attributes.max_health", oldValue); ++ } ++ polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth); + } + + public boolean pufferfishRidable = false; ++ public double pufferfishMaxHealth = 3.0D; + private void pufferfishSettings() { + pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth); ++ set("mobs.pufferfish.attributes.max-health", null); ++ set("mobs.pufferfish.attributes.max_health", oldValue); ++ } ++ pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth); + } + + public boolean rabbitRidable = false; +@@ -912,68 +1268,142 @@ public class PurpurWorldConfig { + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + public int rabbitBreedingTicks = 6000; ++ public double rabbitMaxHealth = 3.0D; + private void rabbitSettings() { + rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); + rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth); ++ set("mobs.rabbit.attributes.max-health", null); ++ set("mobs.rabbit.attributes.max_health", oldValue); ++ } ++ rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); + } + + public boolean ravagerRidable = false; + public boolean ravagerRidableInWater = false; ++ public double ravagerMaxHealth = 100.0D; + private void ravagerSettings() { + ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); + ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth); ++ set("mobs.ravager.attributes.max-health", null); ++ set("mobs.ravager.attributes.max_health", oldValue); ++ } ++ ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); + } + + public boolean salmonRidable = false; ++ public double salmonMaxHealth = 3.0D; + private void salmonSettings() { + salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth); ++ set("mobs.salmon.attributes.max-health", null); ++ set("mobs.salmon.attributes.max_health", oldValue); ++ } ++ salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth); + } + + public boolean sheepRidable = false; + public boolean sheepRidableInWater = false; + public int sheepBreedingTicks = 6000; ++ public double sheepMaxHealth = 8.0D; + private void sheepSettings() { + sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); + sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); + sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth); ++ set("mobs.sheep.attributes.max-health", null); ++ set("mobs.sheep.attributes.max_health", oldValue); ++ } ++ sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth); + } + + public boolean shulkerRidable = false; + public boolean shulkerRidableInWater = false; ++ public double shulkerMaxHealth = 30.0D; + private void shulkerSettings() { + shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); + shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth); ++ set("mobs.shulker.attributes.max-health", null); ++ set("mobs.shulker.attributes.max_health", oldValue); ++ } ++ shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth); + } + + public boolean silverfishRidable = false; + public boolean silverfishRidableInWater = false; ++ public double silverfishMaxHealth = 8.0D; + private void silverfishSettings() { + silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); + silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth); ++ set("mobs.silverfish.attributes.max-health", null); ++ set("mobs.silverfish.attributes.max_health", oldValue); ++ } ++ silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth); + } + + public boolean skeletonRidable = false; + public boolean skeletonRidableInWater = false; ++ public double skeletonMaxHealth = 20.0D; + private void skeletonSettings() { + skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); + skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth); ++ set("mobs.skeleton.attributes.max-health", null); ++ set("mobs.skeleton.attributes.max_health", oldValue); ++ } ++ skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth); + } + + public boolean skeletonHorseCanSwim = false; + public boolean skeletonHorseRidableInWater = true; ++ public double skeletonHorseMaxHealthMin = 15.0D; ++ public double skeletonHorseMaxHealthMax = 15.0D; ++ public double skeletonHorseJumpStrengthMin = 0.4D; ++ public double skeletonHorseJumpStrengthMax = 1.0D; ++ public double skeletonHorseMovementSpeedMin = 0.2D; ++ public double skeletonHorseMovementSpeedMax = 0.2D; + private void skeletonHorseSettings() { + skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); + skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin); ++ set("mobs.skeleton_horse.attributes.max-health", null); ++ set("mobs.skeleton_horse.attributes.max_health.min", oldValue); ++ set("mobs.skeleton_horse.attributes.max_health.max", oldValue); ++ } ++ skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin); ++ skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax); ++ skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin); ++ skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax); ++ skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin); ++ skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax); + } + + public boolean slimeRidable = false; + public boolean slimeRidableInWater = false; ++ public String slimeMaxHealth = "size * size"; + private void slimeSettings() { + slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); + slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); ++ if (PurpurConfig.version < 10) { ++ String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth); ++ set("mobs.slime.attributes.max-health", null); ++ set("mobs.slime.attributes.max_health", oldValue); ++ } ++ slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth); + } + + public boolean snowGolemRidable = false; +@@ -985,6 +1415,7 @@ public class PurpurWorldConfig { + public int snowGolemSnowBallMax = 20; + public float snowGolemSnowBallModifier = 10.0F; + public double snowGolemAttackDistance = 1.25D; ++ public double snowGolemMaxHealth = 4.0D; + private void snowGolemSettings() { + snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); + snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); +@@ -995,61 +1426,116 @@ public class PurpurWorldConfig { + snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax); + snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); + snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth); ++ set("mobs.snow_golem.attributes.max-health", null); ++ set("mobs.snow_golem.attributes.max_health", oldValue); ++ } ++ snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth); + } + + public boolean squidRidable = false; + public boolean squidImmuneToEAR = true; + public double squidOffsetWaterCheck = 0.0D; ++ public double squidMaxHealth = 10.0D; + private void squidSettings() { + squidRidable = getBoolean("mobs.squid.ridable", squidRidable); + squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); + squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth); ++ set("mobs.squid.attributes.max-health", null); ++ set("mobs.squid.attributes.max_health", oldValue); ++ } ++ squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); + } + + public boolean spiderRidable = false; + public boolean spiderRidableInWater = false; ++ public double spiderMaxHealth = 16.0D; + private void spiderSettings() { + spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable); + spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth); ++ set("mobs.spider.attributes.max-health", null); ++ set("mobs.spider.attributes.max_health", oldValue); ++ } ++ spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth); + } + + public boolean strayRidable = false; + public boolean strayRidableInWater = false; ++ public double strayMaxHealth = 20.0D; + private void straySettings() { + strayRidable = getBoolean("mobs.stray.ridable", strayRidable); + strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth); ++ set("mobs.stray.attributes.max-health", null); ++ set("mobs.stray.attributes.max_health", oldValue); ++ } ++ strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth); + } + + public boolean striderRidable = false; + public boolean striderRidableInWater = false; + public int striderBreedingTicks = 6000; ++ public double striderMaxHealth = 20.0D; + private void striderSettings() { + striderRidable = getBoolean("mobs.strider.ridable", striderRidable); + striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); + striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth); ++ set("mobs.strider.attributes.max-health", null); ++ set("mobs.strider.attributes.max_health", oldValue); ++ } ++ striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); + } + + public boolean tropicalFishRidable = false; ++ public double tropicalFishMaxHealth = 3.0D; + private void tropicalFishSettings() { + tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth); ++ set("mobs.tropical_fish.attributes.max-health", null); ++ set("mobs.tropical_fish.attributes.max_health", oldValue); ++ } ++ tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth); + } + + public boolean turtleRidable = false; + public boolean turtleRidableInWater = false; + public int turtleBreedingTicks = 6000; ++ public double turtleMaxHealth = 30.0D; + private void turtleSettings() { + turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); + turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); + turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth); ++ set("mobs.turtle.attributes.max-health", null); ++ set("mobs.turtle.attributes.max_health", oldValue); ++ } ++ turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth); + } + + public boolean vexRidable = false; + public boolean vexRidableInWater = false; + public double vexMaxY = 256D; ++ public double vexMaxHealth = 14.0D; + private void vexSettings() { + vexRidable = getBoolean("mobs.vex.ridable", vexRidable); + vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater); + vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth); ++ set("mobs.vex.attributes.max-health", null); ++ set("mobs.vex.attributes.max_health", oldValue); ++ } ++ vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth); + } + + public boolean villagerRidable = false; +@@ -1066,6 +1552,7 @@ public class PurpurWorldConfig { + public int villagerLobotomizeCheck = 60; + public boolean villagerClericsFarmWarts = false; + public boolean villagerClericFarmersThrowWarts = true; ++ public double villagerMaxHealth = 20.0D; + private void villagerSettings() { + villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); + villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); +@@ -1086,33 +1573,60 @@ public class PurpurWorldConfig { + villagerLobotomizeCheck = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheck); + villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); + villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth); ++ set("mobs.villager.attributes.max-health", null); ++ set("mobs.villager.attributes.max_health", oldValue); ++ } ++ villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth); + } + + public boolean villagerTraderRidable = false; + public boolean villagerTraderRidableInWater = false; + public boolean villagerTraderCanBeLeashed = false; + public boolean villagerTraderFollowEmeraldBlock = false; ++ public double villagerTraderMaxHealth = 20.0D; + private void villagerTraderSettings() { + villagerTraderRidable = getBoolean("mobs.wandering_trader.ridable", villagerTraderRidable); + villagerTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", villagerTraderRidableInWater); + villagerTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", villagerTraderCanBeLeashed); + villagerTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", villagerTraderFollowEmeraldBlock); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", villagerTraderMaxHealth); ++ set("mobs.wandering_trader.attributes.max-health", null); ++ set("mobs.wandering_trader.attributes.max_health", oldValue); ++ } ++ villagerTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", villagerTraderMaxHealth); + } + + public boolean vindicatorRidable = false; + public boolean vindicatorRidableInWater = false; + public double vindicatorJohnnySpawnChance = 0D; ++ public double vindicatorMaxHealth = 24.0D; + private void vindicatorSettings() { + vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); + vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); + vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth); ++ set("mobs.vindicator.attributes.max-health", null); ++ set("mobs.vindicator.attributes.max_health", oldValue); ++ } ++ vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth); + } + + public boolean witchRidable = false; + public boolean witchRidableInWater = false; ++ public double witchMaxHealth = 26.0D; + private void witchSettings() { + witchRidable = getBoolean("mobs.witch.ridable", witchRidable); + witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth); ++ set("mobs.witch.attributes.max-health", null); ++ set("mobs.witch.attributes.max_health", oldValue); ++ } ++ witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth); + } + + public boolean witherRidable = false; +@@ -1129,19 +1643,30 @@ public class PurpurWorldConfig { + witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth); ++ set("mobs.wither.max_health", null); + set("mobs.wither.attributes.max-health", oldValue); +- set("mobs.wither.max-health", null); ++ } else if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth); ++ set("mobs.wither.attributes.max-health", null); ++ set("mobs.wither.attributes.max_health", oldValue); + } +- witherMaxHealth = getDouble("mobs.wither.attributes.max-health", witherMaxHealth); ++ witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth); + } + + public boolean witherSkeletonRidable = false; + public boolean witherSkeletonRidableInWater = false; + public boolean witherSkeletonTakesWitherDamage = false; ++ public double witherSkeletonMaxHealth = 20.0D; + private void witherSkeletonSettings() { + witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable); + witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater); + witherSkeletonTakesWitherDamage = getBoolean("mobs.wither_skeleton.takes-wither-damage", witherSkeletonTakesWitherDamage); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth); ++ set("mobs.wither_skeleton.attributes.max-health", null); ++ set("mobs.wither_skeleton.attributes.max_health", oldValue); ++ } ++ witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth); + } + + public boolean wolfRidable = false; +@@ -1150,6 +1675,7 @@ public class PurpurWorldConfig { + public boolean wolfMilkCuresRabies = true; + public double wolfNaturalRabid = 0.0D; + public int wolfBreedingTicks = 6000; ++ public double wolfMaxHealth = 8.0D; + private void wolfSettings() { + wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); + wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); +@@ -1161,13 +1687,26 @@ public class PurpurWorldConfig { + wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies); + wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid); + wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth); ++ set("mobs.wolf.attributes.max-health", null); ++ set("mobs.wolf.attributes.max_health", oldValue); ++ } ++ wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); + } + + public boolean zoglinRidable = false; + public boolean zoglinRidableInWater = false; ++ public double zoglinMaxHealth = 40.0D; + private void zoglinSettings() { + zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable); + zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth); ++ set("mobs.zoglin.attributes.max-health", null); ++ set("mobs.zoglin.attributes.max_health", oldValue); ++ } ++ zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth); + } + + public boolean zombieRidable = false; +@@ -1177,6 +1716,8 @@ public class PurpurWorldConfig { + public boolean zombieJockeyTryExistingChickens = true; + public boolean zombieAggressiveTowardsVillagerWhenLagging = true; + public EnumDifficulty zombieBreakDoorMinDifficulty = EnumDifficulty.HARD; ++ public double zombieMaxHealth = 20.0D; ++ public double zombieSpawnReinforcements = 0.1D; + private void zombieSettings() { + zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); + zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); +@@ -1189,15 +1730,40 @@ public class PurpurWorldConfig { + } catch (IllegalArgumentException ignore) { + zombieBreakDoorMinDifficulty = EnumDifficulty.HARD; + } ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth); ++ set("mobs.zombie.attributes.max-health", null); ++ set("mobs.zombie.attributes.max_health", oldValue); ++ } ++ zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth); ++ zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements); + } + + public boolean zombieHorseCanSwim = false; + public boolean zombieHorseRidableInWater = false; + public double zombieHorseSpawnChance = 0.0D; ++ public double zombieHorseMaxHealthMin = 15.0D; ++ public double zombieHorseMaxHealthMax = 15.0D; ++ public double zombieHorseJumpStrengthMin = 0.4D; ++ public double zombieHorseJumpStrengthMax = 1.0D; ++ public double zombieHorseMovementSpeedMin = 0.2D; ++ public double zombieHorseMovementSpeedMax = 0.2D; + private void zombieHorseSettings() { + zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); + zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin); ++ set("mobs.zombie_horse.attributes.max-health", null); ++ set("mobs.zombie_horse.attributes.max_health.min", oldValue); ++ set("mobs.zombie_horse.attributes.max_health.max", oldValue); ++ } ++ zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin); ++ zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax); ++ zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin); ++ zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax); ++ zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin); ++ zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax); + } + + public boolean zombifiedPiglinRidable = false; +@@ -1206,6 +1772,8 @@ public class PurpurWorldConfig { + public double zombifiedPiglinJockeyChance = 0.05D; + public boolean zombifiedPiglinJockeyTryExistingChickens = true; + public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; ++ public double zombifiedPiglinMaxHealth = 20.0D; ++ public double zombifiedPiglinSpawnReinforcements = 0.0D; + private void zombifiedPiglinSettings() { + zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); + zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); +@@ -1213,6 +1781,13 @@ public class PurpurWorldConfig { + zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); + zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); + zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth); ++ set("mobs.zombified_piglin.attributes.max-health", null); ++ set("mobs.zombified_piglin.attributes.max_health", oldValue); ++ } ++ zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth); ++ zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements); + } + + public boolean zombieVillagerRidable = false; +@@ -1220,11 +1795,20 @@ public class PurpurWorldConfig { + public boolean zombieVillagerJockeyOnlyBaby = true; + public double zombieVillagerJockeyChance = 0.05D; + public boolean zombieVillagerJockeyTryExistingChickens = true; ++ public double zombieVillagerMaxHealth = 20.0D; ++ public double zombieVillagerSpawnReinforcements = 0.1D; + private void zombieVillagerSettings() { + zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); + zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); + zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); + zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); + zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); ++ if (PurpurConfig.version < 10) { ++ double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth); ++ set("mobs.zombie_villager.attributes.max-health", null); ++ set("mobs.zombie_villager.attributes.max_health", oldValue); ++ } ++ zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); ++ zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); + } + } diff --git a/patches/Purpur/patches/server/0155-Phantom-flames-on-swoop.patch b/patches/Purpur/patches/server/0155-Phantom-flames-on-swoop.patch new file mode 100644 index 00000000..a4dceabe --- /dev/null +++ b/patches/Purpur/patches/server/0155-Phantom-flames-on-swoop.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 12 Dec 2020 09:10:59 -0600 +Subject: [PATCH] Phantom flames on swoop + + +diff --git a/src/main/java/net/minecraft/server/EntityPhantom.java b/src/main/java/net/minecraft/server/EntityPhantom.java +index 25345d8d585735af407787f2c26fe92674721239..087a91fedc49aaf6e74b81b90494849c4932c956 100644 +--- a/src/main/java/net/minecraft/server/EntityPhantom.java ++++ b/src/main/java/net/minecraft/server/EntityPhantom.java +@@ -181,6 +181,7 @@ public class EntityPhantom extends EntityFlying implements IMonster { + this.world.addParticle(Particles.MYCELIUM, this.locX() - (double) f2, this.locY() + (double) f4, this.locZ() - (double) f3, 0.0D, 0.0D, 0.0D); + } + ++ if (world.purpurConfig.phantomFlamesOnSwoop && getAttackPhase() == AttackPhase.SWOOP) shoot(); // Purpur + } + + @Override +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index e9c7e1b65c054fd262b2b5c2032456871499ace7..ade63ed61da4dd77f8d86973d120841c717269c5 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1137,6 +1137,7 @@ public class PurpurWorldConfig { + public int phantomBurnInLight = 0; + public boolean phantomIgnorePlayersWithTorch = false; + public boolean phantomBurnInDaylight = true; ++ public boolean phantomFlamesOnSwoop = false; + public double phantomMaxHealth = 20.0D; + private void phantomSettings() { + phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); +@@ -1162,6 +1163,7 @@ public class PurpurWorldConfig { + phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); + phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); + phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); ++ phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.phantom.attributes.max-health", phantomMaxHealth); + set("mobs.phantom.attributes.max-health", null); diff --git a/patches/Purpur/patches/server/0156-Option-for-chests-to-open-even-with-a-solid-block-on.patch b/patches/Purpur/patches/server/0156-Option-for-chests-to-open-even-with-a-solid-block-on.patch new file mode 100644 index 00000000..2ea72a99 --- /dev/null +++ b/patches/Purpur/patches/server/0156-Option-for-chests-to-open-even-with-a-solid-block-on.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: jmp +Date: Sat, 12 Dec 2020 14:34:18 -0800 +Subject: [PATCH] Option for chests to open even with a solid block on top + + +diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java +index c4ff93a6b908c1bd157c7fe45b504909b189d09c..71a32a317e38b9c8f802fd5dd9ae546fb46eb020 100644 +--- a/src/main/java/net/minecraft/server/BlockChest.java ++++ b/src/main/java/net/minecraft/server/BlockChest.java +@@ -262,6 +262,7 @@ public class BlockChest extends BlockChestAbstract implements I + } + + private static boolean a(IBlockAccess iblockaccess, BlockPosition blockposition) { ++ if (iblockaccess instanceof World && ((World) iblockaccess).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur + BlockPosition blockposition1 = blockposition.up(); + + return iblockaccess.getType(blockposition1).isOccluding(iblockaccess, blockposition1); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index ade63ed61da4dd77f8d86973d120841c717269c5..f0894e607b5965b766c445699332f4a1b2c4f0e3 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -369,6 +369,11 @@ public class PurpurWorldConfig { + } + } + ++ public boolean chestOpenWithBlockOnTop = false; ++ private void chestSettings() { ++ chestOpenWithBlockOnTop = getBoolean("blocks.chest.open-with-solid-block-on-top", chestOpenWithBlockOnTop); ++ } ++ + public boolean dispenserApplyCursedArmor = true; + public boolean dispenserPlaceAnvils = false; + private void dispenserSettings() { diff --git a/patches/Purpur/patches/server/0157-Implement-TPSBar.patch b/patches/Purpur/patches/server/0157-Implement-TPSBar.patch new file mode 100644 index 00000000..69ae8e94 --- /dev/null +++ b/patches/Purpur/patches/server/0157-Implement-TPSBar.patch @@ -0,0 +1,196 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 12 Dec 2020 21:19:05 -0600 +Subject: [PATCH] Implement TPSBar + + +diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java +index b5cc099746e9f05ea69bc438bda22a5ac3ebc3c5..bbd17231a4f7ad0ddde6eb5e589a6c403366cc36 100644 +--- a/src/main/java/net/minecraft/server/CommandDispatcher.java ++++ b/src/main/java/net/minecraft/server/CommandDispatcher.java +@@ -109,6 +109,7 @@ public class CommandDispatcher { + CommandWhitelist.a(this.b); + net.pl3x.purpur.command.DemoCommand.register(getDispatcher()); // Purpur + net.pl3x.purpur.command.PingCommand.register(getDispatcher()); // Purpur ++ net.pl3x.purpur.command.TPSBarCommand.register(getDispatcher()); // Purpur + } + + if (commanddispatcher_servertype.d) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 98961e20642e61239a6ad89445f97245aa821919..760799782d0cb01e2b14408a9b085f78034ec78d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -468,6 +468,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant dispatcher) { ++ dispatcher.register(CommandDispatcher.literal("tpsbar") ++ .requires((listener) -> { ++ return listener.hasPermission(2); ++ }) ++ .executes((context) -> { ++ return execute(context.getSource(), context.getSource().getPlayerOrException()); ++ }) ++ ).setPermission("bukkit.command.tpsbar"); ++ } ++ ++ private static int execute(CommandListenerWrapper sender, EntityPlayer player) { ++ if (player != null) { ++ TPSBarTask.togglePlayer(player.getBukkitEntity()); ++ return 1; ++ } ++ return 0; ++ } ++} +diff --git a/src/main/java/net/pl3x/purpur/task/TPSBarTask.java b/src/main/java/net/pl3x/purpur/task/TPSBarTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..170f01516aab72e5b192695a73602ff656ef4ca5 +--- /dev/null ++++ b/src/main/java/net/pl3x/purpur/task/TPSBarTask.java +@@ -0,0 +1,106 @@ ++package net.pl3x.purpur.task; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.ChatColor; ++import org.bukkit.NamespacedKey; ++import org.bukkit.boss.BarColor; ++import org.bukkit.boss.BarStyle; ++import org.bukkit.boss.BossBar; ++import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; ++import org.bukkit.entity.Player; ++import org.bukkit.scheduler.BukkitRunnable; ++ ++public class TPSBarTask extends BukkitRunnable { ++ private final NamespacedKey key; ++ private final BossBar bossbar; ++ private static TPSBarTask instance; ++ ++ public TPSBarTask() { ++ instance = this; ++ ++ this.key = new NamespacedKey("purpur", "tpsbar"); ++ ++ BossBar bossbar = Bukkit.getBossBar(key); ++ if (bossbar == null) { ++ bossbar = Bukkit.createBossBar(key, "TPS: 20.0", BarColor.RED, BarStyle.SEGMENTED_20); ++ } ++ bossbar.setVisible(true); ++ bossbar.setProgress(1.0D); ++ ++ this.bossbar = bossbar; ++ } ++ ++ @Override ++ public void run() { ++ if (bossbar.getPlayers().isEmpty()) { ++ return; ++ } ++ ++ double tps = Bukkit.getTPS()[0]; ++ if (tps > 20.0D) { ++ tps = 20.0D; ++ } else if (tps < 0.0D) { ++ tps = 0.0D; ++ } ++ ++ bossbar.setVisible(true); ++ bossbar.setProgress(Math.max(Math.min(tps / 20.0D, 1.0D), 0.0D)); ++ ++ String tpsColor; ++ if (tps >= 18) { ++ tpsColor = "&2"; ++ bossbar.setColor(BarColor.GREEN); ++ } else if (tps >= 15) { ++ tpsColor = "&e"; ++ bossbar.setColor(BarColor.YELLOW); ++ } else { ++ tpsColor = "&4"; ++ bossbar.setColor(BarColor.RED); ++ } ++ ++ double mspt = Bukkit.getAverageTickTime(); ++ String msptColor; ++ if (mspt < 40) { ++ msptColor = "&2"; ++ } else if (mspt < 50) { ++ msptColor = "&e"; ++ } else { ++ msptColor = "&4"; ++ } ++ ++ bossbar.setTitle(ChatColor.translateAlternateColorCodes('&', "&eTPS&3: " + tpsColor + String.format("%.2f", tps) + " &eMSPT&3: " + msptColor + String.format("%.3f", mspt))); ++ } ++ ++ @Override ++ public void cancel() { ++ super.cancel(); ++ bossbar.setVisible(false); ++ bossbar.removeAll(); ++ Bukkit.removeBossBar(key); ++ } ++ ++ public static void removePlayer(Player player) { ++ instance.bossbar.removePlayer(player); ++ } ++ ++ public static void togglePlayer(Player player) { ++ if (instance.bossbar.getPlayers().contains(player)) { ++ instance.bossbar.removePlayer(player); ++ } else { ++ instance.bossbar.addPlayer(player); ++ instance.run(); ++ } ++ } ++ ++ public static void start() { ++ stop(); ++ instance = new TPSBarTask(); ++ instance.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 20L, 20L); ++ } ++ ++ public static void stop() { ++ if (instance != null) { ++ instance.cancel(); ++ } ++ } ++} diff --git a/patches/Purpur/patches/server/0158-Striders-give-saddle-back.patch b/patches/Purpur/patches/server/0158-Striders-give-saddle-back.patch new file mode 100644 index 00000000..dec8b14f --- /dev/null +++ b/patches/Purpur/patches/server/0158-Striders-give-saddle-back.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ben Kerllenevich +Date: Sun, 13 Dec 2020 20:40:57 -0500 +Subject: [PATCH] Striders give saddle back + + +diff --git a/src/main/java/net/minecraft/server/EntityStrider.java b/src/main/java/net/minecraft/server/EntityStrider.java +index a0bb64bea373c678c519e3fae8f808fd36e1ee4f..11911b10ddc6e2a681f2eda313a6e7c68674a1ac 100644 +--- a/src/main/java/net/minecraft/server/EntityStrider.java ++++ b/src/main/java/net/minecraft/server/EntityStrider.java +@@ -376,6 +376,18 @@ public class EntityStrider extends EntityAnimal implements ISteerable, ISaddleab + + if (!flag && this.hasSaddle() && !this.isVehicle() && !entityhuman.eq()) { + if (!this.world.isClientSide) { ++ // Purpur start ++ if (world.purpurConfig.striderGiveSaddleBack && entityhuman.isSneaking()) { ++ this.saddleStorage.setSaddle(false); ++ if (!entityhuman.abilities.canInstantlyBuild) { ++ ItemStack saddle = new ItemStack(Items.SADDLE); ++ if (!entityhuman.inventory.pickup(saddle)) { ++ entityhuman.drop(saddle, false); ++ } ++ } ++ return EnumInteractionResult.SUCCESS; ++ } ++ // Purpur end + entityhuman.startRiding(this); + } + +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f0894e607b5965b766c445699332f4a1b2c4f0e3..dd325c675b0618e934610bbe8c45754d37e36a29 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1489,6 +1489,7 @@ public class PurpurWorldConfig { + public boolean striderRidableInWater = false; + public int striderBreedingTicks = 6000; + public double striderMaxHealth = 20.0D; ++ public boolean striderGiveSaddleBack = false; + private void striderSettings() { + striderRidable = getBoolean("mobs.strider.ridable", striderRidable); + striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); +@@ -1499,6 +1500,7 @@ public class PurpurWorldConfig { + set("mobs.strider.attributes.max_health", oldValue); + } + striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); ++ striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack); + } + + public boolean tropicalFishRidable = false; diff --git a/patches/Purpur/patches/server/0159-PlayerBookTooLargeEvent.patch b/patches/Purpur/patches/server/0159-PlayerBookTooLargeEvent.patch new file mode 100644 index 00000000..dcf5eacc --- /dev/null +++ b/patches/Purpur/patches/server/0159-PlayerBookTooLargeEvent.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 23 Dec 2020 00:43:59 -0600 +Subject: [PATCH] PlayerBookTooLargeEvent + + +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index e9485684b7d5ddde72fc388d51cfef679178bad3..75d955948a407d94e6f3a88f86afa8b1d6ba33cb 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -941,6 +941,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + NBTTagList pageList = testStack.getTag().getList("pages", 8); + if (pageList.size() > 100) { + PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send a book with too many pages"); ++ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), testStack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur + minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); + return; + } +@@ -953,6 +954,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; + if (byteLength > 256 * 4) { + PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send a book with with a page too large!"); ++ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), testStack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur + minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); + return; + } +@@ -976,6 +978,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + + if (byteTotal > byteAllowed) { + PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); ++ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), testStack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur + minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); + return; + } diff --git a/patches/Purpur/patches/server/0160-Full-netherite-armor-grants-fire-resistance.patch b/patches/Purpur/patches/server/0160-Full-netherite-armor-grants-fire-resistance.patch new file mode 100644 index 00000000..ef8fa93d --- /dev/null +++ b/patches/Purpur/patches/server/0160-Full-netherite-armor-grants-fire-resistance.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 24 Dec 2020 11:00:15 -0600 +Subject: [PATCH] Full netherite armor grants fire resistance + + +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index c973a1f1aecd47f11a12c94325cc18c3307d7ab5..91c2756a8708a2f4154905baec20b9ae484fea0d 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -236,6 +236,16 @@ public abstract class EntityHuman extends EntityLiving { + this.addEffect(new MobEffect(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit + } + ++ // Purpur start ++ if (this.world.purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.world.getTime() % 20 == 0) { ++ if (itemstack.getItem() == Items.NETHERITE_HELMET ++ && this.getEquipment(EnumItemSlot.CHEST).getItem() == Items.NETHERITE_CHESTPLATE ++ && this.getEquipment(EnumItemSlot.LEGS).getItem() == Items.NETHERITE_LEGGINGS ++ && this.getEquipment(EnumItemSlot.FEET).getItem() == Items.NETHERITE_BOOTS) { ++ this.addEffect(new MobEffect(MobEffects.FIRE_RESISTANCE, this.world.purpurConfig.playerNetheriteFireResistanceDuration, this.world.purpurConfig.playerNetheriteFireResistanceAmplifier, this.world.purpurConfig.playerNetheriteFireResistanceAmbient, this.world.purpurConfig.playerNetheriteFireResistanceShowParticles, this.world.purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR); ++ } ++ } ++ // Purpur end + } + + protected ItemCooldown i() { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index dd325c675b0618e934610bbe8c45754d37e36a29..7e564d609cf08184361743023c2fdfba45d03498 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -298,6 +298,19 @@ public class PurpurWorldConfig { + playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); + } + ++ public int playerNetheriteFireResistanceDuration = 0; ++ public int playerNetheriteFireResistanceAmplifier = 0; ++ public boolean playerNetheriteFireResistanceAmbient = false; ++ public boolean playerNetheriteFireResistanceShowParticles = false; ++ public boolean playerNetheriteFireResistanceShowIcon = true; ++ private void playerNetheriteFireResistance() { ++ playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration); ++ playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier); ++ playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient); ++ playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles); ++ playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon); ++ } ++ + public int playerSpawnInvulnerableTicks = 60; + public boolean playerInvulnerableWhileAcceptingResourcePack = false; + private void playerInvulnerabilities() { diff --git a/patches/Purpur/patches/server/0161-Fix-rotating-UP-DOWN-CW-and-CCW.patch b/patches/Purpur/patches/server/0161-Fix-rotating-UP-DOWN-CW-and-CCW.patch new file mode 100644 index 00000000..3aa5aeec --- /dev/null +++ b/patches/Purpur/patches/server/0161-Fix-rotating-UP-DOWN-CW-and-CCW.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 6 Jan 2021 02:19:29 -0600 +Subject: [PATCH] Fix rotating UP/DOWN CW and CCW + + +diff --git a/src/main/java/net/minecraft/server/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java +index c187f04ef657e9012d2fcdc2fd5b4cca69d78c3f..a660033af081cef69e4646c9ed9eaa66cf90217f 100644 +--- a/src/main/java/net/minecraft/server/EnumDirection.java ++++ b/src/main/java/net/minecraft/server/EnumDirection.java +@@ -113,6 +113,12 @@ public enum EnumDirection implements INamable { + return EnumDirection.NORTH; + case EAST: + return EnumDirection.SOUTH; ++ // Purpur start ++ case UP: ++ return EnumDirection.UP; ++ case DOWN: ++ return EnumDirection.DOWN; ++ // Purpur end + default: + throw new IllegalStateException("Unable to get Y-rotated facing of " + this); + } +@@ -129,6 +135,12 @@ public enum EnumDirection implements INamable { + return EnumDirection.SOUTH; + case EAST: + return EnumDirection.NORTH; ++ // Purpur start ++ case UP: ++ return EnumDirection.UP; ++ case DOWN: ++ return EnumDirection.DOWN; ++ // Purpur end + default: + throw new IllegalStateException("Unable to get CCW facing of " + this); + } diff --git a/patches/Purpur/patches/server/0162-Add-mobGriefing-bypass-to-everything-affected.patch b/patches/Purpur/patches/server/0162-Add-mobGriefing-bypass-to-everything-affected.patch new file mode 100644 index 00000000..5ba7492b --- /dev/null +++ b/patches/Purpur/patches/server/0162-Add-mobGriefing-bypass-to-everything-affected.patch @@ -0,0 +1,560 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Encode42 +Date: Tue, 5 Jan 2021 22:21:56 -0500 +Subject: [PATCH] Add mobGriefing bypass to everything affected + +This adds the "bypass-mob-griefing" world config option to everything that is affected by the gamerule. + +diff --git a/src/main/java/net/minecraft/server/BlockCampfire.java b/src/main/java/net/minecraft/server/BlockCampfire.java +index 99decab11b3561edb0fea8964bfb97ccc997b772..e4cc567d28a5368f975e29c7d18dba007d914584 100644 +--- a/src/main/java/net/minecraft/server/BlockCampfire.java ++++ b/src/main/java/net/minecraft/server/BlockCampfire.java +@@ -139,7 +139,7 @@ public class BlockCampfire extends BlockTileEntity implements IBlockWaterlogged + public void a(World world, IBlockData iblockdata, MovingObjectPositionBlock movingobjectpositionblock, IProjectile iprojectile) { + if (!world.isClientSide && iprojectile.isBurning()) { + Entity entity = iprojectile.getShooter(); +- boolean flag = entity == null || entity instanceof EntityHuman || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); ++ boolean flag = entity == null || entity instanceof EntityHuman || world.purpurConfig.fireballsBypassMobGriefing || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // Purpur + + if (flag && !(Boolean) iblockdata.get(BlockCampfire.LIT) && !(Boolean) iblockdata.get(BlockCampfire.d)) { + BlockPosition blockposition = movingobjectpositionblock.getBlockPosition(); +diff --git a/src/main/java/net/minecraft/server/BlockCrops.java b/src/main/java/net/minecraft/server/BlockCrops.java +index 0fdc960fa3bbe6506185480a7a86d5009d70b385..d49c309d7c31b3be0a21544ba23c0fd663f3317b 100644 +--- a/src/main/java/net/minecraft/server/BlockCrops.java ++++ b/src/main/java/net/minecraft/server/BlockCrops.java +@@ -144,7 +144,7 @@ public class BlockCrops extends BlockPlant implements IBlockFragilePlantElement + + @Override + public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Entity entity) { +- if (entity instanceof EntityRavager && !CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, Blocks.AIR.getBlockData(), !world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)).isCancelled()) { // CraftBukkit ++ if (entity instanceof EntityRavager && !CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, Blocks.AIR.getBlockData(), (!world.purpurConfig.ravagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.MOB_GRIEFING))).isCancelled()) { // CraftBukkit // Purpur + world.a(blockposition, true, entity); + } + +diff --git a/src/main/java/net/minecraft/server/BlockSoil.java b/src/main/java/net/minecraft/server/BlockSoil.java +index 099e0d3df219408ebe2a741a02e53eb9f7def28e..73dc0f499c456c21d298013fbab8c79ebcdecd6b 100644 +--- a/src/main/java/net/minecraft/server/BlockSoil.java ++++ b/src/main/java/net/minecraft/server/BlockSoil.java +@@ -76,7 +76,7 @@ public class BlockSoil extends Block { + @Override + public void fallOn(World world, BlockPosition blockposition, Entity entity, float f) { + super.fallOn(world, blockposition, entity, f); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. +- if (!world.isClientSide && world.random.nextFloat() < f - 0.5F && entity instanceof EntityLiving && (entity instanceof EntityHuman || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) && entity.getWidth() * entity.getWidth() * entity.getHeight() > 0.512F) { ++ if (!world.isClientSide && world.random.nextFloat() < f - 0.5F && entity instanceof EntityLiving && (entity instanceof EntityHuman || world.purpurConfig.farmlandBypassMobGriefing || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) && entity.getWidth() * entity.getWidth() * entity.getHeight() > 0.512F) { // Purpur + // CraftBukkit start - Interact soil + org.bukkit.event.Cancellable cancellable; + if (entity instanceof EntityHuman) { +diff --git a/src/main/java/net/minecraft/server/BlockTurtleEgg.java b/src/main/java/net/minecraft/server/BlockTurtleEgg.java +index 92cca6c44f12a9283988b84681aac760f1c38d7e..31f2ca273eb35aa389caf328abe1b0e87c8846f8 100644 +--- a/src/main/java/net/minecraft/server/BlockTurtleEgg.java ++++ b/src/main/java/net/minecraft/server/BlockTurtleEgg.java +@@ -177,7 +177,7 @@ public class BlockTurtleEgg extends Block { + return false; + } + if (entity instanceof EntityLiving && !(entity instanceof EntityHuman)) { +- return world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); ++ return world.purpurConfig.turtleEggsBypassMobGriefing || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // Purpur + } + return true; + // Purpur end +diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java +index 8e16ae4be41a0f20b057b70e9ef255c548a36f08..87b691afd226ec9c59685261c20454c2d312ca17 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java ++++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java +@@ -489,7 +489,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { + Block block = iblockdata.getBlock(); + + if (!iblockdata.isAir() && iblockdata.getMaterial() != Material.FIRE) { +- if (this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) && !TagsBlock.DRAGON_IMMUNE.isTagged(block)) { ++ if ((this.world.purpurConfig.enderDragonBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) && !TagsBlock.DRAGON_IMMUNE.isTagged(block)) { // Purpur + // CraftBukkit start - Add blocks to list rather than destroying them + // flag1 = this.world.a(blockposition, false) || flag1; + flag1 = true; +diff --git a/src/main/java/net/minecraft/server/EntityEnderman.java b/src/main/java/net/minecraft/server/EntityEnderman.java +index dfe6175ec8107f684ea1567d932d11de06c46372..beee80c3d8277f2d784fb6b8a4152a871ee020b0 100644 +--- a/src/main/java/net/minecraft/server/EntityEnderman.java ++++ b/src/main/java/net/minecraft/server/EntityEnderman.java +@@ -392,7 +392,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + @Override + public boolean a() { + if (!enderman.world.purpurConfig.endermanAllowGriefing) return false; // Purpur +- return this.enderman.getCarried() != null ? false : (!this.enderman.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.enderman.getRandom().nextInt(20) == 0); ++ return this.enderman.getCarried() != null ? false : (!this.enderman.world.purpurConfig.endermanBypassMobGriefing && !this.enderman.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.enderman.getRandom().nextInt(20) == 0); // Purpur + } + + @Override +@@ -435,7 +435,7 @@ public class EntityEnderman extends EntityMonster implements IEntityAngerable { + @Override + public boolean a() { + if (!getEnderman().world.purpurConfig.endermanAllowGriefing) return false; // Purpur +- return this.a.getCarried() == null ? false : (!this.a.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.a.getRandom().nextInt(2000) == 0); ++ return this.a.getCarried() == null ? false : (!this.a.world.purpurConfig.endermanBypassMobGriefing && !this.a.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.a.getRandom().nextInt(2000) == 0); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityEvoker.java b/src/main/java/net/minecraft/server/EntityEvoker.java +index f0ecc6e6ef5843714a6423af5d6619856ef23977..5f24c36bf45a656e220475449113786732a47c56 100644 +--- a/src/main/java/net/minecraft/server/EntityEvoker.java ++++ b/src/main/java/net/minecraft/server/EntityEvoker.java +@@ -134,7 +134,7 @@ public class EntityEvoker extends EntityIllagerWizard { + return false; + } else if (EntityEvoker.this.ticksLived < this.c) { + return false; +- } else if (!EntityEvoker.this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ } else if (!EntityEvoker.this.world.purpurConfig.evokerBypassMobGriefing && !EntityEvoker.this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + return false; + } else { + List list = EntityEvoker.this.world.a(EntitySheep.class, this.e, EntityEvoker.this, EntityEvoker.this.getBoundingBox().grow(16.0D, 4.0D, 16.0D)); +diff --git a/src/main/java/net/minecraft/server/EntityFox.java b/src/main/java/net/minecraft/server/EntityFox.java +index c63878087ff0414602c4e7ed768a62f8e4899a3a..18d14aca159db9c82a2f56020f503b5d3c4aae59 100644 +--- a/src/main/java/net/minecraft/server/EntityFox.java ++++ b/src/main/java/net/minecraft/server/EntityFox.java +@@ -1002,7 +1002,7 @@ public class EntityFox extends EntityAnimal { + } + + protected void n() { +- if (EntityFox.this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (EntityFox.this.world.purpurConfig.foxBypassMobGriefing || EntityFox.this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + IBlockData iblockdata = EntityFox.this.world.getType(this.e); + + if (iblockdata.a(Blocks.SWEET_BERRY_BUSH)) { +diff --git a/src/main/java/net/minecraft/server/EntityLargeFireball.java b/src/main/java/net/minecraft/server/EntityLargeFireball.java +index d12de20cf4bb2345c616d3cc0b9f50bddb5135ee..3f3be1b2ded6ad118ae7860c1231c7affc0715b6 100644 +--- a/src/main/java/net/minecraft/server/EntityLargeFireball.java ++++ b/src/main/java/net/minecraft/server/EntityLargeFireball.java +@@ -8,19 +8,19 @@ public class EntityLargeFireball extends EntityFireballFireball { + + public EntityLargeFireball(EntityTypes entitytypes, World world) { + super(entitytypes, world); +- isIncendiary = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // CraftBukkit ++ isIncendiary = this.world.purpurConfig.fireballsBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // CraftBukkit // Purpur + } + + public EntityLargeFireball(World world, EntityLiving entityliving, double d0, double d1, double d2) { + super(EntityTypes.FIREBALL, entityliving, d0, d1, d2, world); +- isIncendiary = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // CraftBukkit ++ isIncendiary = this.world.purpurConfig.fireballsBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // CraftBukkit // Purpur + } + + @Override + protected void a(MovingObjectPosition movingobjectposition) { + super.a(movingobjectposition); + if (!this.world.isClientSide) { +- boolean flag = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); ++ boolean flag = this.world.purpurConfig.fireballsBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // Purpur + + // CraftBukkit start - fire ExplosionPrimeEvent + ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index 3836d772d07efb7e9a9e7ecc7141330f0cd15801..8bceb152d9c7a3227c4bd3bb47964cf69abea0b4 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -1472,7 +1472,7 @@ public abstract class EntityLiving extends Entity { + boolean flag = false; + + if (this.killed && entityliving instanceof EntityWither) { // Paper +- if (this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (this.world.purpurConfig.witherBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + BlockPosition blockposition = this.getChunkCoordinates(); + IBlockData iblockdata = Blocks.WITHER_ROSE.getBlockData(); + +diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java +index 0583fc55056a29e7629c76b72800c727401a4a83..6559dcafac6cb9673dccdbd44f18e9802b3ac3c2 100644 +--- a/src/main/java/net/minecraft/server/EntityRabbit.java ++++ b/src/main/java/net/minecraft/server/EntityRabbit.java +@@ -462,7 +462,7 @@ public class EntityRabbit extends EntityAnimal { + @Override + public boolean a() { + if (this.c <= 0) { +- if (!this.entity.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (!this.entity.world.purpurConfig.rabbitBypassMobGriefing && !this.entity.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + return false; + } + +diff --git a/src/main/java/net/minecraft/server/EntityRaider.java b/src/main/java/net/minecraft/server/EntityRaider.java +index ad2a2c27f0ef064064ded28cc049a6856d476808..e07f224289a937689a26d58639899abbfd6a6302 100644 +--- a/src/main/java/net/minecraft/server/EntityRaider.java ++++ b/src/main/java/net/minecraft/server/EntityRaider.java +@@ -496,7 +496,7 @@ public abstract class EntityRaider extends EntityMonsterPatrolling { + + @Override + public boolean a() { +- if (!getRaider().world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) || !getRaider().canPickupLoot()) return false; // Paper - respect game and entity rules for picking up items ++ if ((!getRaider().world.purpurConfig.pillagerBypassMobGriefing && !getRaider().world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) || !getRaider().canPickupLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur + Raid raid = this.b.fa(); + + if (this.b.fb() && !this.b.fa().a() && this.b.eN() && !ItemStack.matches(this.b.getEquipment(EnumItemSlot.HEAD), Raid.s())) { +diff --git a/src/main/java/net/minecraft/server/EntityRavager.java b/src/main/java/net/minecraft/server/EntityRavager.java +index 9e21603cb1681cc702084fdeebb6f93754d87bc8..f9df1389189962ce08e50647baffd9eecb00968a 100644 +--- a/src/main/java/net/minecraft/server/EntityRavager.java ++++ b/src/main/java/net/minecraft/server/EntityRavager.java +@@ -137,7 +137,7 @@ public class EntityRavager extends EntityRaider { + this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(MathHelper.d(0.1D, d1, d0)); + } + +- if (this.positionChanged && this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (this.positionChanged && (this.world.purpurConfig.ravagerBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING))) { // Purpur + boolean flag = false; + AxisAlignedBB axisalignedbb = this.getBoundingBox().g(0.2D); + Iterator iterator = BlockPosition.b(MathHelper.floor(axisalignedbb.minX), MathHelper.floor(axisalignedbb.minY), MathHelper.floor(axisalignedbb.minZ), MathHelper.floor(axisalignedbb.maxX), MathHelper.floor(axisalignedbb.maxY), MathHelper.floor(axisalignedbb.maxZ)).iterator(); +diff --git a/src/main/java/net/minecraft/server/EntitySilverfish.java b/src/main/java/net/minecraft/server/EntitySilverfish.java +index 6bd00f0b5735d694e370cf85fdbf508c31fc7c27..9ccb7a805adf8c1e429f3fab2bc261c4dad5eda8 100644 +--- a/src/main/java/net/minecraft/server/EntitySilverfish.java ++++ b/src/main/java/net/minecraft/server/EntitySilverfish.java +@@ -144,7 +144,7 @@ public class EntitySilverfish extends EntityMonster { + } else { + Random random = this.a.getRandom(); + +- if (this.a.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) && random.nextInt(10) == 0) { ++ if ((this.a.world.purpurConfig.silverfishBypassMobGriefing || this.a.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) && random.nextInt(10) == 0) { // Purpur + this.h = EnumDirection.a(random); + BlockPosition blockposition = (new BlockPosition(this.a.locX(), this.a.locY() + 0.5D, this.a.locZ())).shift(this.h); + IBlockData iblockdata = this.a.world.getType(blockposition); +@@ -232,7 +232,7 @@ public class EntitySilverfish extends EntityMonster { + continue; + } + // CraftBukkit end +- if (world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (world.purpurConfig.silverfishBypassMobGriefing || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + world.a(blockposition1, true, this.silverfish); + } else { + world.setTypeAndData(blockposition1, ((BlockMonsterEggs) block).c().getBlockData(), 3); +diff --git a/src/main/java/net/minecraft/server/EntitySmallFireball.java b/src/main/java/net/minecraft/server/EntitySmallFireball.java +index 4ed7a20bfed267776628457a4b33178bac7d1972..e7e12ce2338102b875444c3f3cc767858de38dca 100644 +--- a/src/main/java/net/minecraft/server/EntitySmallFireball.java ++++ b/src/main/java/net/minecraft/server/EntitySmallFireball.java +@@ -12,7 +12,7 @@ public class EntitySmallFireball extends EntityFireballFireball { + super(EntityTypes.SMALL_FIREBALL, entityliving, d0, d1, d2, world); + // CraftBukkit start + if (this.getShooter() != null && this.getShooter() instanceof EntityInsentient) { +- isIncendiary = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); ++ isIncendiary = this.world.purpurConfig.fireballsBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING); // Purpur + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/server/EntitySnowman.java b/src/main/java/net/minecraft/server/EntitySnowman.java +index 319bb68f27f360bb36897c73a68ddeac64b67a6f..550decf12140596e63b57a7ee5940e4f7b7dbc0b 100644 +--- a/src/main/java/net/minecraft/server/EntitySnowman.java ++++ b/src/main/java/net/minecraft/server/EntitySnowman.java +@@ -86,7 +86,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt + this.damageEntity(CraftEventFactory.MELTING, 1.0F); // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING + } + +- if (!this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (!this.world.purpurConfig.snowGolemBypassMobGriefing && !this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + return; + } + +diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java +index 510797225d50de1565288c8df484384874050172..d77ef303562ca255d51cf527d7dc8be6a4b8facc 100644 +--- a/src/main/java/net/minecraft/server/EntityWither.java ++++ b/src/main/java/net/minecraft/server/EntityWither.java +@@ -322,7 +322,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + if (this.getInvul() > 0) { + i = this.getInvul() - 1; + if (i <= 0) { +- Explosion.Effect explosion_effect = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? Explosion.Effect.DESTROY : Explosion.Effect.NONE; ++ Explosion.Effect explosion_effect = (this.world.purpurConfig.witherBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) ? Explosion.Effect.DESTROY : Explosion.Effect.NONE; // Purpur + // CraftBukkit start + // this.world.createExplosion(this, this.locX(), this.getHeadY(), this.locZ(), 7.0F, false, explosion_effect); + ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false); +@@ -434,7 +434,7 @@ public class EntityWither extends EntityMonster implements IRangedEntity { + + if (this.bw > 0) { + --this.bw; +- if (this.bw == 0 && this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (this.bw == 0 && (this.world.purpurConfig.witherBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING))) { // Purpur + i = MathHelper.floor(this.locY()); + j = MathHelper.floor(this.locX()); + int j1 = MathHelper.floor(this.locZ()); +diff --git a/src/main/java/net/minecraft/server/EntityWitherSkull.java b/src/main/java/net/minecraft/server/EntityWitherSkull.java +index 4a97a7517dc1a2a25c578d9e168240cc19ab0831..fedc78e4111864d1413e6fcd588b9951595b463a 100644 +--- a/src/main/java/net/minecraft/server/EntityWitherSkull.java ++++ b/src/main/java/net/minecraft/server/EntityWitherSkull.java +@@ -73,7 +73,7 @@ public class EntityWitherSkull extends EntityFireball { + protected void a(MovingObjectPosition movingobjectposition) { + super.a(movingobjectposition); + if (!this.world.isClientSide) { +- Explosion.Effect explosion_effect = this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? Explosion.Effect.DESTROY : Explosion.Effect.NONE; ++ Explosion.Effect explosion_effect = (this.world.purpurConfig.witherBypassMobGriefing || this.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) ? Explosion.Effect.DESTROY : Explosion.Effect.NONE; // Purpur + + // CraftBukkit start + // this.world.createExplosion(this, this.locX(), this.locY(), this.locZ(), 1.0F, false, explosion_effect); +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java b/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java +index 23870a271b759a953a095df835e08ea2a09f4218..c31bacf786ea750e922c938c042656515d8b0041 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalBreakDoor.java +@@ -27,7 +27,7 @@ public class PathfinderGoalBreakDoor extends PathfinderGoalDoorInteract { + + @Override + public boolean a() { +- return !super.a() ? false : (!this.entity.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING) ? false : this.a(this.entity.world.getDifficulty()) && !this.g()); ++ return !super.a() ? false : ((!this.entity.world.purpurConfig.zombieBypassMobGriefing && !this.entity.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) ? false : this.a(this.entity.world.getDifficulty()) && !this.g()); // Purpur + } + + @Override +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalEatTile.java b/src/main/java/net/minecraft/server/PathfinderGoalEatTile.java +index b7e1e7e35a33cf6a476b11284ebdb0cdda524af2..9e8f720ee0a39ccc27d671df4ae83ef404788a2f 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalEatTile.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalEatTile.java +@@ -11,7 +11,7 @@ public class PathfinderGoalEatTile extends PathfinderGoal { + + private static final Predicate a = BlockStatePredicate.a(Blocks.GRASS); + private final EntityInsentient b; +- private final World c; ++ private final World c; private final World getWorld() { return c; } // Purpur - OBFHELPER + private int d; + + public PathfinderGoalEatTile(EntityInsentient entityinsentient) { +@@ -60,7 +60,7 @@ public class PathfinderGoalEatTile extends PathfinderGoal { + + if (PathfinderGoalEatTile.a.test(this.c.getType(blockposition))) { + // CraftBukkit +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.b, blockposition, Blocks.AIR.getBlockData(), !this.c.getGameRules().getBoolean(GameRules.MOB_GRIEFING)).isCancelled()) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.b, blockposition, Blocks.AIR.getBlockData(), !this.getWorld().purpurConfig.sheepBypassMobGriefing && !this.c.getGameRules().getBoolean(GameRules.MOB_GRIEFING)).isCancelled()) { // Purpur + this.c.b(blockposition, false); + } + +@@ -70,7 +70,7 @@ public class PathfinderGoalEatTile extends PathfinderGoal { + + if (this.c.getType(blockposition1).a(Blocks.GRASS_BLOCK)) { + // CraftBukkit +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.b, blockposition, Blocks.AIR.getBlockData(), !this.c.getGameRules().getBoolean(GameRules.MOB_GRIEFING)).isCancelled()) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.b, blockposition, Blocks.AIR.getBlockData(), !this.getWorld().purpurConfig.sheepBypassMobGriefing && !this.c.getGameRules().getBoolean(GameRules.MOB_GRIEFING)).isCancelled()) { // Purpur + this.c.triggerEffect(2001, blockposition1, Block.getCombinedId(Blocks.GRASS_BLOCK.getBlockData())); + this.c.setTypeAndData(blockposition1, Blocks.DIRT.getBlockData(), 2); + } +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalRemoveBlock.java b/src/main/java/net/minecraft/server/PathfinderGoalRemoveBlock.java +index c03ebbc933197be3e7097ea3f7b7cd08c90db7bb..37c1d1ac1bfcaf84d00135ad3c9d9e9b213fdaea 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalRemoveBlock.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalRemoveBlock.java +@@ -23,7 +23,7 @@ public class PathfinderGoalRemoveBlock extends PathfinderGoalGotoTarget { + + @Override + public boolean a() { +- if (!this.entity.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { ++ if (!this.entity.world.purpurConfig.zombieBypassMobGriefing && !this.entity.world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) { // Purpur + return false; + } else if (this.c > 0) { + --this.c; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 7e564d609cf08184361743023c2fdfba45d03498..7093eac2f097fa9d50d9b52ca3b6159b7e5bd076 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -240,6 +240,7 @@ public class PurpurWorldConfig { + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean entitiesCanUsePortals = true; ++ public boolean fireballsBypassMobGriefing = false; + public boolean milkCuresBadOmen = true; + public boolean persistentTileEntityDisplayNames = false; + public boolean persistentDroppableEntityDisplayNames = false; +@@ -254,6 +255,7 @@ public class PurpurWorldConfig { + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); ++ fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); + persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); +@@ -394,9 +396,11 @@ public class PurpurWorldConfig { + dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); + } + ++ public boolean farmlandBypassMobGriefing = false; + public boolean farmlandGetsMoistFromBelow = false; + public boolean farmlandAlpha = false; + private void farmlandSettings() { ++ farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing); + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); + farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); + } +@@ -455,10 +459,12 @@ public class PurpurWorldConfig { + stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); + } + ++ public boolean turtleEggsBypassMobGriefing = false; + public boolean turtleEggsBreakFromExpOrbs = true; + public boolean turtleEggsBreakFromItems = true; + public boolean turtleEggsBreakFromMinecarts = true; + private void turtleEggSettings() { ++ turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing); + turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); + turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); +@@ -731,6 +737,7 @@ public class PurpurWorldConfig { + public double enderDragonMaxY = 256D; + public boolean enderDragonAlwaysDropsEggBlock = false; + public boolean enderDragonAlwaysDropsFullExp = false; ++ public boolean enderDragonBypassMobGriefing = false; + public double enderDragonMaxHealth = 200.0D; + private void enderDragonSettings() { + enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); +@@ -738,6 +745,7 @@ public class PurpurWorldConfig { + enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); + enderDragonAlwaysDropsEggBlock = getBoolean("mobs.ender_dragon.always-drop-egg-block", enderDragonAlwaysDropsEggBlock); + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); ++ enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.max-health", null); +@@ -753,12 +761,14 @@ public class PurpurWorldConfig { + public boolean endermanRidable = false; + public boolean endermanRidableInWater = false; + public boolean endermanAllowGriefing = true; ++ public boolean endermanBypassMobGriefing = false; + public boolean endermanDespawnEvenWithBlock = false; + public double endermanMaxHealth = 40.0D; + private void endermanSettings() { + endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); + endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); ++ endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing); + endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth); +@@ -784,10 +794,12 @@ public class PurpurWorldConfig { + + public boolean evokerRidable = false; + public boolean evokerRidableInWater = false; ++ public boolean evokerBypassMobGriefing = false; + public double evokerMaxHealth = 24.0D; + private void evokerSettings() { + evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); + evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); ++ evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth); + set("mobs.evoker.attributes.max-health", null); +@@ -798,12 +810,14 @@ public class PurpurWorldConfig { + + public boolean foxRidable = false; + public boolean foxRidableInWater = false; ++ public boolean foxBypassMobGriefing = false; + public boolean foxTypeChangesWithTulips = false; + public int foxBreedingTicks = 6000; + public double foxMaxHealth = 10.0D; + private void foxSettings() { + foxRidable = getBoolean("mobs.fox.ridable", foxRidable); + foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); ++ foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); + foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); + if (PurpurConfig.version < 10) { +@@ -1238,10 +1252,12 @@ public class PurpurWorldConfig { + + public boolean pillagerRidable = false; + public boolean pillagerRidableInWater = false; ++ public boolean pillagerBypassMobGriefing = false; + public double pillagerMaxHealth = 24.0D; + private void pillagerSettings() { + pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); + pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); ++ pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth); + set("mobs.pillager.attributes.max-health", null); +@@ -1285,6 +1301,7 @@ public class PurpurWorldConfig { + + public boolean rabbitRidable = false; + public boolean rabbitRidableInWater = false; ++ public boolean rabbitBypassMobGriefing = false; + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + public int rabbitBreedingTicks = 6000; +@@ -1292,6 +1309,7 @@ public class PurpurWorldConfig { + private void rabbitSettings() { + rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); + rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); ++ rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); +@@ -1305,10 +1323,12 @@ public class PurpurWorldConfig { + + public boolean ravagerRidable = false; + public boolean ravagerRidableInWater = false; ++ public boolean ravagerBypassMobGriefing = false; + public double ravagerMaxHealth = 100.0D; + private void ravagerSettings() { + ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); + ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); ++ ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth); + set("mobs.ravager.attributes.max-health", null); +@@ -1332,11 +1352,13 @@ public class PurpurWorldConfig { + public boolean sheepRidable = false; + public boolean sheepRidableInWater = false; + public int sheepBreedingTicks = 6000; ++ public boolean sheepBypassMobGriefing = false; + public double sheepMaxHealth = 8.0D; + private void sheepSettings() { + sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); + sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); + sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); ++ sheepBypassMobGriefing = getBoolean("mobs.sheep.bypass-mob-griefing", sheepBypassMobGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth); + set("mobs.sheep.attributes.max-health", null); +@@ -1361,10 +1383,12 @@ public class PurpurWorldConfig { + + public boolean silverfishRidable = false; + public boolean silverfishRidableInWater = false; ++ public boolean silverfishBypassMobGriefing = false; + public double silverfishMaxHealth = 8.0D; + private void silverfishSettings() { + silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); + silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); ++ silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth); + set("mobs.silverfish.attributes.max-health", null); +@@ -1429,6 +1453,7 @@ public class PurpurWorldConfig { + public boolean snowGolemRidable = false; + public boolean snowGolemRidableInWater = false; + public boolean snowGolemLeaveTrailWhenRidden = false; ++ public boolean snowGolemBypassMobGriefing = false; + public boolean snowGolemDropsPumpkin = false; + public boolean snowGolemPutPumpkinBack = false; + public int snowGolemSnowBallMin = 20; +@@ -1440,6 +1465,7 @@ public class PurpurWorldConfig { + snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); + snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); + snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden); ++ snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing); + snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); + snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); + snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin); +@@ -1654,6 +1680,7 @@ public class PurpurWorldConfig { + public boolean witherRidable = false; + public boolean witherRidableInWater = false; + public double witherMaxY = 256D; ++ public boolean witherBypassMobGriefing = false; + public float witherHealthRegenAmount = 1.0f; + public int witherHealthRegenDelay = 20; + public double witherMaxHealth = 300.0D; +@@ -1661,6 +1688,7 @@ public class PurpurWorldConfig { + witherRidable = getBoolean("mobs.wither.ridable", witherRidable); + witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); + witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY); ++ witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing); + witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); + witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); + if (PurpurConfig.version < 8) { +@@ -1733,6 +1761,7 @@ public class PurpurWorldConfig { + + public boolean zombieRidable = false; + public boolean zombieRidableInWater = false; ++ public boolean zombieBypassMobGriefing = false; + public boolean zombieJockeyOnlyBaby = true; + public double zombieJockeyChance = 0.05D; + public boolean zombieJockeyTryExistingChickens = true; +@@ -1743,6 +1772,7 @@ public class PurpurWorldConfig { + private void zombieSettings() { + zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); + zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); ++ zombieBypassMobGriefing = getBoolean("mobs.zombie.bypass-mob-griefing", zombieBypassMobGriefing); + zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); + zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); + zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); diff --git a/patches/Purpur/patches/server/0163-Config-to-allow-Note-Block-sounds-when-blocked.patch b/patches/Purpur/patches/server/0163-Config-to-allow-Note-Block-sounds-when-blocked.patch new file mode 100644 index 00000000..4b473d3b --- /dev/null +++ b/patches/Purpur/patches/server/0163-Config-to-allow-Note-Block-sounds-when-blocked.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Encode42 +Date: Fri, 8 Jan 2021 16:07:32 -0500 +Subject: [PATCH] Config to allow Note Block sounds when blocked + +Allows for Note Blocks to ignore whether or not there's air above them to play. + +Normally, the sounds will only play when the block directly above is air. +With this patch enabled, players can place any block above the Note Block and it will still work. + +diff --git a/src/main/java/net/minecraft/server/BlockNote.java b/src/main/java/net/minecraft/server/BlockNote.java +index df69d00d3a38417e53f433cd1eb1f6cf3ec9b55b..6bb6e229c8734d7b9f4e3cd3dd1b4b646bca1777 100644 +--- a/src/main/java/net/minecraft/server/BlockNote.java ++++ b/src/main/java/net/minecraft/server/BlockNote.java +@@ -37,7 +37,7 @@ public class BlockNote extends Block { + } + + private void play(World world, BlockPosition blockposition, IBlockData data) { // CraftBukkit +- if (world.getType(blockposition.up()).isAir()) { ++ if (world.purpurConfig.noteBlockIgnoreAbove || world.getType(blockposition.up()).isAir()) { + // CraftBukkit start + org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.get(BlockNote.INSTRUMENT), data.get(BlockNote.NOTE)); + if (!event.isCancelled()) { +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 7093eac2f097fa9d50d9b52ca3b6159b7e5bd076..f162c1745ebc4861bb56266bd7ff3a63da117e0f 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -242,6 +242,7 @@ public class PurpurWorldConfig { + public boolean entitiesCanUsePortals = true; + public boolean fireballsBypassMobGriefing = false; + public boolean milkCuresBadOmen = true; ++ public boolean noteBlockIgnoreAbove = false; + public boolean persistentTileEntityDisplayNames = false; + public boolean persistentDroppableEntityDisplayNames = false; + public double tridentLoyaltyVoidReturnHeight = 0.0D; +@@ -257,6 +258,7 @@ public class PurpurWorldConfig { + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); + fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); ++ noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove); + persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); + persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); diff --git a/patches/Purpur/patches/server/0164-Add-EntityTeleportHinderedEvent.patch b/patches/Purpur/patches/server/0164-Add-EntityTeleportHinderedEvent.patch new file mode 100644 index 00000000..239f6da1 --- /dev/null +++ b/patches/Purpur/patches/server/0164-Add-EntityTeleportHinderedEvent.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 9 Jan 2021 15:27:46 +0100 +Subject: [PATCH] Add EntityTeleportHinderedEvent + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index b40f5167d2a9772658c115091f13706fbb4959b7..657885cdaa086293f6b5aa6f3058acd16df0ba35 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -250,6 +250,7 @@ public abstract class BlockBase { + return 0; + } + ++ @Deprecated public final void collideWithBlock(IBlockData iblockdata, World world, BlockPosition blockposition, Entity entity) { this.a(iblockdata, world, blockposition, entity); } // Purpur - OBFHELPER + @Deprecated + public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Entity entity) {} + +diff --git a/src/main/java/net/minecraft/server/BlockEnderPortal.java b/src/main/java/net/minecraft/server/BlockEnderPortal.java +index e731b6d811d179f07f84278d2cce75e0d98092ab..f0e5c378c4612192c1d4c74aa81e598f9370b8b8 100644 +--- a/src/main/java/net/minecraft/server/BlockEnderPortal.java ++++ b/src/main/java/net/minecraft/server/BlockEnderPortal.java +@@ -25,7 +25,15 @@ public class BlockEnderPortal extends BlockTileEntity { + + @Override + public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Entity entity) { +- if (world instanceof WorldServer && !entity.isPassenger() && !entity.isVehicle() && entity.canPortal() && VoxelShapes.c(VoxelShapes.a(entity.getBoundingBox().d((double) (-blockposition.getX()), (double) (-blockposition.getY()), (double) (-blockposition.getZ()))), iblockdata.getShape(world, blockposition), OperatorBoolean.AND)) { ++ // Purpur start ++ if (world instanceof WorldServer && /*!entity.isPassenger() && !entity.isVehicle() &&*/ entity.canPortal() && VoxelShapes.c(VoxelShapes.a(entity.getBoundingBox().d((double) (-blockposition.getX()), (double) (-blockposition.getY()), (double) (-blockposition.getZ()))), iblockdata.getShape(world, blockposition), OperatorBoolean.AND)) { ++ if (entity.isPassenger() || entity.isVehicle()) { ++ if (new net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { ++ this.collideWithBlock(iblockdata, world, blockposition, entity); ++ } ++ return; ++ } ++ // Purpur end + ResourceKey resourcekey = world.getTypeKey() == DimensionManager.THE_END ? World.OVERWORLD : World.THE_END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends + WorldServer worldserver = ((WorldServer) world).getMinecraftServer().getWorldServer(resourcekey); + +diff --git a/src/main/java/net/minecraft/server/BlockPortal.java b/src/main/java/net/minecraft/server/BlockPortal.java +index 4132cd4c6f13cfa1c0cda43daaa908ff3c07f32b..6dab6ee15f34c401bfe71041f46a9d5b9732770b 100644 +--- a/src/main/java/net/minecraft/server/BlockPortal.java ++++ b/src/main/java/net/minecraft/server/BlockPortal.java +@@ -63,7 +63,15 @@ public class BlockPortal extends Block { + + @Override + public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Entity entity) { +- if (!entity.isPassenger() && !entity.isVehicle() && entity.canPortal()) { ++ // Purpur start ++ if (/*!entity.isPassenger() && !entity.isVehicle() &&*/ entity.canPortal()) { ++ if (entity.isPassenger() || entity.isVehicle()) { ++ if (new net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) { ++ this.collideWithBlock(iblockdata, world, blockposition, entity); ++ } ++ return; ++ } ++ // Purpur end + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ())); + world.getServer().getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/server/TileEntityEndGateway.java b/src/main/java/net/minecraft/server/TileEntityEndGateway.java +index ed8e91bf6c8b9d410d439bdddd5067d346a20a7e..127b5d8215f2bb4c5c523c5a77ebccfbf5fc25ff 100644 +--- a/src/main/java/net/minecraft/server/TileEntityEndGateway.java ++++ b/src/main/java/net/minecraft/server/TileEntityEndGateway.java +@@ -125,9 +125,18 @@ public class TileEntityEndGateway extends TileEntityEnderPortal implements ITick + } + } + ++ public final void teleportEntity(Entity entity) { this.b(entity); } // Purpur - OBFHELPER + public void b(Entity entity) { + if (this.world instanceof WorldServer && !this.f()) { + if (!entity.canPortal()) return; // Purpur ++ // Purpur start ++ if (this.world.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) { ++ if (new net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent()) { ++ this.teleportEntity(entity); ++ } ++ return; ++ } ++ // Purpur end + this.c = 100; + if (this.exitPortal == null && this.world.getTypeKey() == DimensionManager.THE_END) { // CraftBukkit - work in alternate worlds + this.a((WorldServer) this.world); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index f162c1745ebc4861bb56266bd7ff3a63da117e0f..580c786f93621667b4ef57e951169305158c24f1 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1865,4 +1865,9 @@ public class PurpurWorldConfig { + zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); + zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); + } ++ ++ public boolean imposeTeleportRestrictionsOnGateways = false; ++ private void imposeTeleportRestrictionsOnGateways() { ++ imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways); ++ } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index b56ca054b37f5887e13b481baad8132f1d28638b..eb0ce05d25ba33626d2dd3e3380d805c560bfe58 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -521,6 +521,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + location.checkFinite(); + + if (entity.isVehicle() || entity.dead) { ++ // Purpur start ++ if (!entity.dead && new net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) ++ return teleport(location, cause); ++ // Purpur end + return false; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 95e548eea5acbece0e7036f23d79b6fc61e6786f..70ed64ed22ffde672a4ecb0a7f7ac2a7b3f30112 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -116,6 +116,7 @@ import org.bukkit.entity.EntityType; + import org.bukkit.entity.Player; + import org.bukkit.event.player.PlayerRegisterChannelEvent; + import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + import org.bukkit.event.player.PlayerUnregisterChannelEvent; + import org.bukkit.inventory.InventoryView.Property; + import org.bukkit.inventory.ItemStack; +@@ -791,6 +792,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + if (entity.isVehicle()) { ++ // Purpur start ++ if (new net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), net.pl3x.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) ++ return teleport(location, cause); ++ // Purpur end + return false; + } + diff --git a/patches/Purpur/patches/server/0165-Add-StructureGenerateEvent.patch b/patches/Purpur/patches/server/0165-Add-StructureGenerateEvent.patch new file mode 100644 index 00000000..90a1be64 --- /dev/null +++ b/patches/Purpur/patches/server/0165-Add-StructureGenerateEvent.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nahuel +Date: Sat, 9 Jan 2021 15:36:59 +0100 +Subject: [PATCH] Add StructureGenerateEvent + +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 . + +Co-authored-by: Mariell Hoversholm + +diff --git a/src/main/java/net/minecraft/server/ChunkGenerator.java b/src/main/java/net/minecraft/server/ChunkGenerator.java +index 17cf00dfe8b24adf2fb66eca4710ab7888a894e3..6223d1d9048e31fb10cec7f485d1556aec2c7d5e 100644 +--- a/src/main/java/net/minecraft/server/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/server/ChunkGenerator.java +@@ -251,6 +251,14 @@ public abstract class ChunkGenerator { + if (structuresettingsfeature != null) { + StructureStart structurestart1 = structurefeature.a(iregistrycustom, this, this.b, definedstructuremanager, i, chunkcoordintpair, biomebase, j, structuresettingsfeature); + ++ // Purpur start ++ if (new net.pl3x.purpur.event.world.StructureGenerateEvent( ++ structuremanager.getWorld().getWorld(), ++ org.bukkit.StructureType.getStructureTypes().get(structurefeature.getFeature().getRegistryKey().toLowerCase()), ++ chunkcoordintpair.x, ++ chunkcoordintpair.z ++ ).callEvent()) ++ // Purpur end + structuremanager.a(SectionPosition.a(ichunkaccess.getPos(), 0), structurefeature.d, structurestart1, ichunkaccess); + } + +diff --git a/src/main/java/net/minecraft/server/StructureFeature.java b/src/main/java/net/minecraft/server/StructureFeature.java +index 8e3c0c3783b767c2ba603b3b50200ac76a7fc33e..379665ae55e76afb03b20d1b81c57347af1137db 100644 +--- a/src/main/java/net/minecraft/server/StructureFeature.java ++++ b/src/main/java/net/minecraft/server/StructureFeature.java +@@ -11,7 +11,7 @@ public class StructureFeature>> b = RegistryFileCodec.a(IRegistry.av, StructureFeature.a); + public static final Codec>>> c = RegistryFileCodec.b(IRegistry.av, StructureFeature.a); +- public final F d; ++ public final F d; public final F getFeature() { return this.d; } // Purpur - OBFHELPER + public final FC e; + + public StructureFeature(F f0, FC fc) { +diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java +index a62c87bceab2c9700a7b3925f208b0ffa2b9b393..8fc283f014783b76afda83097201bb7938a1f9fa 100644 +--- a/src/main/java/net/minecraft/server/StructureGenerator.java ++++ b/src/main/java/net/minecraft/server/StructureGenerator.java +@@ -235,6 +235,7 @@ public abstract class StructureGenerator + + public abstract StructureGenerator.a a(); + ++ public final String getRegistryKey() { return this.i(); } // Purpur - OBFHELPER + public String i() { + return (String) StructureGenerator.a.inverse().get(this); + } diff --git a/patches/Purpur/patches/server/0166-Farmland-trampling-changes.patch b/patches/Purpur/patches/server/0166-Farmland-trampling-changes.patch new file mode 100644 index 00000000..2a14f6ac --- /dev/null +++ b/patches/Purpur/patches/server/0166-Farmland-trampling-changes.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 9 Jan 2021 16:06:40 +0100 +Subject: [PATCH] Farmland trampling changes + +This lets us choose if only players may trample farmland. + +This lets us choose if entities can stop trampling if they fall a +distance equal to their feather falling level, plus the extra block +necessary to trample in the first place. Feather Falling 1 requires you +to fall over 3+ blocks to trample. FF 2 requires 4+, etc. + +diff --git a/src/main/java/net/minecraft/server/BlockSoil.java b/src/main/java/net/minecraft/server/BlockSoil.java +index 73dc0f499c456c21d298013fbab8c79ebcdecd6b..6b65a4b1845c6770f92ceebd04827595725632b5 100644 +--- a/src/main/java/net/minecraft/server/BlockSoil.java ++++ b/src/main/java/net/minecraft/server/BlockSoil.java +@@ -97,6 +97,12 @@ public class BlockSoil extends Block { + return; + } + } ++ if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof EntityPlayer)) return; ++ if (world.purpurConfig.farmlandTramplingFeatherFalling) { ++ Iterator armor = entity.getArmorItems().iterator(); ++ if (armor.hasNext() && EnchantmentManager.getEnchantmentLevel(Enchantments.PROTECTION_FALL, armor.next()) >= (int) entity.fallDistance) ++ return; ++ } + // Purpur end + if (CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, Blocks.DIRT.getBlockData()).isCancelled()) { + return; +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 580c786f93621667b4ef57e951169305158c24f1..83a84163982bacc2bb1d475ee5a2d18085fe47b6 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -401,10 +401,14 @@ public class PurpurWorldConfig { + public boolean farmlandBypassMobGriefing = false; + public boolean farmlandGetsMoistFromBelow = false; + public boolean farmlandAlpha = false; ++ public boolean farmlandTramplingOnlyPlayers = false; ++ public boolean farmlandTramplingFeatherFalling = false; + private void farmlandSettings() { + farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing); + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); + farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); ++ farmlandTramplingOnlyPlayers = getBoolean("blocks.farmland.only-players-trample", farmlandTramplingOnlyPlayers); ++ farmlandTramplingFeatherFalling = getBoolean("blocks.farmland.feather-fall-distance-affects-trampling", farmlandTramplingFeatherFalling); + } + + public boolean furnaceInfiniteFuel = false; diff --git a/patches/Purpur/patches/server/0167-Movement-options-for-armour-stands.patch b/patches/Purpur/patches/server/0167-Movement-options-for-armour-stands.patch new file mode 100644 index 00000000..de7f1a0f --- /dev/null +++ b/patches/Purpur/patches/server/0167-Movement-options-for-armour-stands.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 9 Jan 2021 22:22:59 +0100 +Subject: [PATCH] Movement options for armour stands + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index cb4856e3bbb25e1077f5b4832f359549d57acd7e..41844f30a2a98eb14559b3ab0885ae738b54520c 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -1360,7 +1360,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return this.isInWater() || flag; + } + +- void aL() { ++ void aL() { // Purpur - diff on change; this is `updateInWaterStateAndDoWaterCurrentPushing()V` + if (this.getVehicle() instanceof EntityBoat) { + this.inWater = false; + } else if (this.a((Tag) TagsFluid.WATER, 0.014D)) { +diff --git a/src/main/java/net/minecraft/server/EntityArmorStand.java b/src/main/java/net/minecraft/server/EntityArmorStand.java +index e6de89e7f57c3c130dedb8407cd4cd577d394b9a..91df44d2e4f09fb612ae7bcb6c6a3dbb99aaba41 100644 +--- a/src/main/java/net/minecraft/server/EntityArmorStand.java ++++ b/src/main/java/net/minecraft/server/EntityArmorStand.java +@@ -52,10 +52,12 @@ public class EntityArmorStand extends EntityLiving { + private boolean noTickPoseDirty = false; + private boolean noTickEquipmentDirty = false; + // Paper end ++ public boolean canMovementTick = true; // Purpur + + public EntityArmorStand(EntityTypes entitytypes, World world) { + super(entitytypes, world); + if (world != null) this.canTick = world.paperConfig.armorStandTick; // Paper - armour stand ticking ++ if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur + this.handItems = NonNullList.a(2, ItemStack.b); + this.armorItems = NonNullList.a(4, ItemStack.b); + this.headPose = EntityArmorStand.bj; +@@ -897,4 +899,18 @@ public class EntityArmorStand extends EntityLiving { + return true; + } + // Paper end ++ ++ // Purpur start ++ @Override ++ void aL() { ++ if (this.world.purpurConfig.armorstandWaterMovement && ++ (this.world.purpurConfig.armorstandWaterFence || !(world.getType(getBlockLocation().down()).getBlock() instanceof BlockFence))) ++ super.aL(); ++ } ++ ++ @Override ++ public void movementTick() { ++ if (this.canMovementTick && this.canMove) super.movementTick(); ++ } ++ // Purpur end + } +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 83a84163982bacc2bb1d475ee5a2d18085fe47b6..18c1a6bec8cf71fed4a8058cd396d8834d95b120 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -111,10 +111,16 @@ public class PurpurWorldConfig { + public boolean armorstandSetNameVisible = false; + public boolean armorstandFixNametags = false; + public float armorstandStepHeight = 0.0F; ++ public boolean armorstandMovement = true; ++ public boolean armorstandWaterMovement = true; ++ public boolean armorstandWaterFence = true; + private void armorstandSettings() { + armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); + armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags); + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); ++ armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement); ++ armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement); ++ armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence); + } + + public boolean controllableMinecarts = false; diff --git a/patches/Purpur/patches/server/0168-Fix-stuck-in-portals.patch b/patches/Purpur/patches/server/0168-Fix-stuck-in-portals.patch new file mode 100644 index 00000000..6a4f3093 --- /dev/null +++ b/patches/Purpur/patches/server/0168-Fix-stuck-in-portals.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 14 Jan 2021 16:48:10 -0600 +Subject: [PATCH] Fix stuck in portals + + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 41844f30a2a98eb14559b3ab0885ae738b54520c..63eb29635957d4e6ce1274ee17a59af62d442d4e 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -2411,12 +2411,15 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return new Vec2F(this.pitch, this.yaw); + } + ++ public BlockPosition portalPos = BlockPosition.ZERO; // Purpur + public void d(BlockPosition blockposition) { + if (this.ai()) { ++ if (!(world.purpurConfig.playerFixStuckPortal && this instanceof EntityPlayer && !blockposition.equals(portalPos))) // Purpur + this.resetPortalCooldown(); + } else if (world.purpurConfig.entitiesCanUsePortals || this instanceof EntityPlayer) { // Purpur + if (!this.world.isClientSide && !blockposition.equals(this.ac)) { + this.ac = blockposition.immutableCopy(); ++ portalPos = BlockPosition.ZERO; // Purpur + } + + this.inPortal = true; +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 670b3d72c1d339f2dd05efbd243e7c2e896e2f79..7596eaf605bf73dd44c06b66bcc0e5a36242fe7a 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1180,6 +1180,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + playerlist.d(this); + worldserver1.removePlayer(this); + this.dead = false; ++ this.portalPos = MCUtil.toBlockPosition(exit); // Purpur + + // CraftBukkit end + this.spawnIn(worldserver); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index 18c1a6bec8cf71fed4a8058cd396d8834d95b120..7c9ea94960e8147d5d193b40c17178f8c28b4acf 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -338,6 +338,11 @@ public class PurpurWorldConfig { + }); + } + ++ public boolean playerFixStuckPortal = false; ++ private void playerFixStuckPortal() { ++ playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); ++ } ++ + public boolean teleportIfOutsideBorder = false; + private void teleportIfOutsideBorder() { + teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); diff --git a/patches/Purpur/server.txt b/patches/Purpur/server.txt deleted file mode 100644 index 04471a27..00000000 --- a/patches/Purpur/server.txt +++ /dev/null @@ -1 +0,0 @@ -Purpur-config-files&Timings-stuff&Add-component-util&Barrels-and-enderchests-6-rows&Lagging-threshold&Configurable-villager-brain-ticks&Alternative-Keepalive-Handling&MC-168772-Fix-Add-turtle-egg-block-options&Fix-vanilla-command-permission-handler&Fix-outdated-server-showing-in-ping-before-server-fu&Dont-send-useless-entity-packets&MC-147659-Fix-non-black-cats-spawning-in-swamp-huts&Cows-eat-mushrooms&Snowman-drop-and-put-back-pumpkin&Signs-editable-on-right-click&Signs-allow-color-codes&Allow-soil-to-moisten-from-water-directly-under-it&Option-to-toggle-milk-curing-bad-omen&Fix-the-dead-lagging-the-server&Skip-events-if-there-s-no-listeners&Add-permission-for-F3-N-debug&Allow-leashing-villagers&Implement-infinite-lava&Make-lava-flow-speed-configurable&Add-5-second-tps-average-in-tps&Entity-lifespan&Squid-EAR-immunity&Allow-anvil-colors&Add-no-tick-block-list&Add-option-to-disable-dolphin-treasure-searching&Stop-squids-floating-on-top-of-water&Despawn-rate-config-options-per-projectile-type&PaperPR-Add-hex-color-code-support-for-console-loggi&Persistent-TileEntity-Lore-and-DisplayName&Infinity-bow-settings&Allow-infinite-and-mending-enchantments-together&Add-twisting-and-weeping-vines-growth-rates&Config-migration-disable-saving-projectiles-to-disk-&Spread-out-and-optimise-player-list-ticks&Implement-TPSBar&Fix-rotating-UP-DOWN-CW-and-CCW&Fix-stuck-in-portals \ No newline at end of file diff --git a/patches/Tuinity/PATCHES-LICENSE b/patches/Tuinity/PATCHES-LICENSE new file mode 100644 index 00000000..4e3e94f3 --- /dev/null +++ b/patches/Tuinity/PATCHES-LICENSE @@ -0,0 +1,13 @@ +Copyright 2018-2020 Robert Norman + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/patches/Tuinity/patches/api/0001-Tuinity-POM-Changes.patch b/patches/Tuinity/patches/api/0001-Tuinity-POM-Changes.patch new file mode 100644 index 00000000..9766ed55 --- /dev/null +++ b/patches/Tuinity/patches/api/0001-Tuinity-POM-Changes.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 14 Dec 2018 21:52:29 -0800 +Subject: [PATCH] Tuinity POM Changes + + +diff --git a/pom.xml b/pom.xml +index 6c6ae6b48094c86c74cd4e9f4214056f63e6ac68..c441d455e2273b63d2cec136872a5025182d75d6 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -3,18 +3,18 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + +- com.destroystokyo.paper +- paper-parent ++ com.tuinity ++ tuinity-parent + dev-SNAPSHOT ++ ../pom.xml + + +- com.destroystokyo.paper +- paper-api ++ tuinity-api + 1.16.5-R0.1-SNAPSHOT + jar + +- Paper-API +- https://github.com/PaperMC/Paper ++ Tuinity-API ++ https://github.com/Spottedleaf/Tuinity + An enhanced plugin API for Minecraft servers. + + diff --git a/patches/Tuinity/patches/api/0002-Tuinity-config.patch b/patches/Tuinity/patches/api/0002-Tuinity-config.patch new file mode 100644 index 00000000..5892dc79 --- /dev/null +++ b/patches/Tuinity/patches/api/0002-Tuinity-config.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 21 Mar 2020 20:12:48 -0700 +Subject: [PATCH] Tuinity config + +API to retrieve raw YamlConfiguration + timing exports + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 8e01e1bbb7cfd98b47dbdb3a0e132dafd1413de6..b45ad8df8b7a44c9e6d12326e5ea85e8d166a16c 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1482,6 +1482,14 @@ public interface Server extends PluginMessageRecipient { + } + // Paper end + ++ // Tuinity start - add config to timings report ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getTuinityConfig() ++ { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Tuinity end - add config to timings report ++ + /** + * Sends the component to the player + * diff --git a/patches/Tuinity/patches/server/0001-Tuinity-POM-Changes.patch b/patches/Tuinity/patches/server/0001-Tuinity-POM-Changes.patch new file mode 100644 index 00000000..01de272b --- /dev/null +++ b/patches/Tuinity/patches/server/0001-Tuinity-POM-Changes.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 14 Dec 2018 21:53:58 -0800 +Subject: [PATCH] Tuinity POM Changes + + +diff --git a/pom.xml b/pom.xml +index 9ba379b7e3ee3bc8c6d2c8ec46213c404c73d682..650c6b91440d4ce1c5f82517a00056673750e74e 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -1,11 +1,11 @@ + + 4.0.0 +- paper ++ tuinity + jar + 1.16.5-R0.1-SNAPSHOT +- Paper +- https://papermc.io ++ Tuinity-Server ++ https://github.com/Spottedleaf/Tuinity + + + +@@ -19,16 +19,16 @@ + + + +- com.destroystokyo.paper +- paper-parent ++ com.tuinity ++ tuinity-parent + dev-SNAPSHOT + ../pom.xml + + + + +- com.destroystokyo.paper +- paper-api ++ com.tuinity ++ tuinity-api + ${project.version} + compile + +@@ -173,15 +173,15 @@ + + + +- paper-${minecraft.version} +- clean install ++ tuinity-${minecraft.version} ++ install + + + com.lukegb.mojo + gitdescribe-maven-plugin + 1.3 + +- git-Paper- ++ git-Tuinity- + .. + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +index 674096cab190d62622f9947853b056f57d43a2a5..001b1e5197eaa51bfff9031aa6c69876c9a47960 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/com.destroystokyo.paper/paper-api/pom.properties"); ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.tuinity/tuinity-api/pom.properties"); // Tuinity + Properties properties = new Properties(); + + if (stream != null) { diff --git a/patches/Tuinity/patches/server/0002-Brand-changes.patch b/patches/Tuinity/patches/server/0002-Brand-changes.patch new file mode 100644 index 00000000..e411a0f0 --- /dev/null +++ b/patches/Tuinity/patches/server/0002-Brand-changes.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 18 Dec 2018 06:27:02 -0800 +Subject: [PATCH] Brand changes + + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +index dee00aac05f1acf050f05d4db557a08dd0f301c8..52c0ab1ce46e1f3233ef746d9bc699356fa9fae4 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("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); ++ Metrics metrics = new Metrics("Tuinity", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page + + 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() || PaperConfig.isProxyOnlineMode() ? "online" : "offline")); +- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); ++ metrics.addCustomChart(new Metrics.SimplePie("tuinity_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page + + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); +diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java +index 550232cb3819138b3bae0fa1c51429485e8bc593..229c3b0f0c650b501f31147adaa17194af57fedd 100644 +--- a/src/main/java/net/minecraft/server/EULA.java ++++ b/src/main/java/net/minecraft/server/EULA.java +@@ -70,7 +70,7 @@ public class EULA { + Properties properties = new Properties(); + + properties.setProperty("eula", "false"); +- properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; ++ properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); // Paper - fix lag; // Tuinity - Tacos are disgusting + } catch (Throwable throwable1) { + throwable = throwable1; + throw throwable1; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 8c384171ca56a0989ffef1813ad0a9ee3ea31d29..d19f944c774ad241626b268771df8e7e86cc71ae 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1448,7 +1448,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // Spigot - Spigot > // CraftBukkit - cb > vanilla! ++ return "Tuinity"; // Tuinity //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + } + + public CrashReport b(CrashReport crashreport) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 9ad6ea5ef331c3c057f39115d00f855ca57e219b..f16432ae1cd9d5ccec56892a08952ec78003d066 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -232,7 +232,7 @@ import javax.annotation.Nullable; // Paper + import javax.annotation.Nonnull; // Paper + + public final class CraftServer implements Server { +- private final String serverName = "Paper"; // Paper ++ private final String serverName = "Tuinity"; // Paper // Tuinity + private final String serverVersion; + private final String bukkitVersion = Versioning.getBukkitVersion(); + private final Logger logger = Logger.getLogger("Minecraft"); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 22bde395939f97086e411cef190bb2b1e7ede79a..ac32e19afe7255fedecc2cadbc47d2a332b80a90 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -252,7 +252,7 @@ public class Main { + if (buildDate.before(deadline.getTime())) { + // Paper start - This is some stupid bullshit + System.err.println("*** Warning, you've not updated in a while! ***"); +- System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper ++ System.err.println("*** Please download a new build ***"); // Paper // Tuinity + //System.err.println("*** Server will start in 20 seconds ***"); + //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); + // Paper End diff --git a/patches/Tuinity/patches/server/0003-MC-Dev-fixes.patch b/patches/Tuinity/patches/server/0003-MC-Dev-fixes.patch new file mode 100644 index 00000000..2e8cfb80 --- /dev/null +++ b/patches/Tuinity/patches/server/0003-MC-Dev-fixes.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 19 Jun 2019 13:21:17 -0700 +Subject: [PATCH] MC-Dev fixes + + +diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java +index 068b92c5c4ae112771757626ea75694e59f3d255..14ddb2a8949ce18a0c42e17a82d0d7a13ac325fe 100644 +--- a/src/main/java/net/minecraft/server/HeightMap.java ++++ b/src/main/java/net/minecraft/server/HeightMap.java +@@ -137,7 +137,7 @@ public class HeightMap { + private final String h; + private final HeightMap.Use i; + private final Predicate j; +- private static final Map k = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { ++ private static final Map k = (Map) SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Tuinity - decompile fix + HeightMap.Type[] aheightmap_type = values(); + int i = aheightmap_type.length; + +@@ -149,7 +149,7 @@ public class HeightMap { + + }); + +- private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { ++ private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { // Tuinity - decompile fix + this.h = s; + this.i = heightmap_use; + this.j = predicate; diff --git a/patches/Tuinity/patches/server/0004-Util-patch.patch b/patches/Tuinity/patches/server/0004-Util-patch.patch new file mode 100644 index 00000000..9c79c899 --- /dev/null +++ b/patches/Tuinity/patches/server/0004-Util-patch.patch @@ -0,0 +1,2605 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 18 May 2019 12:25:19 -0700 +Subject: [PATCH] Util patch + + +diff --git a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..20150ad0750a648d349701a09b31881be124e46c +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java +@@ -0,0 +1,488 @@ ++package com.tuinity.tuinity.chunk; ++ ++import co.aikar.timings.MinecraftTimings; ++import co.aikar.timings.Timing; ++import com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.WorldServer; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.EnumMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Function; ++ ++public final class SingleThreadChunkRegionManager & SingleThreadChunkRegionManager.RegionDataCreator> { ++ ++ static final int REGION_SECTION_MERGE_RADIUS = 1; ++ // if this becomes > 8, then the RegionSection needs to be properly modified (see bitset) ++ public static final int REGION_CHUNK_SIZE = 32; ++ public static final int REGION_CHUNK_SIZE_SHIFT = 5; // log2(REGION_CHUNK_SIZE) ++ ++ public final WorldServer world; ++ public final Class dataClass; ++ public final String name; ++ ++ public final Timing addChunkTimings; ++ public final Timing removeChunkTimings; ++ public final Timing regionRecalculateTimings; ++ ++ protected final Long2ObjectOpenHashMap> regionsBySection = new Long2ObjectOpenHashMap<>(); ++ protected final ReferenceLinkedOpenHashSet> needsRecalculation = new ReferenceLinkedOpenHashSet<>(); ++ protected final int minSectionRecalcCount; ++ protected final double maxDeadRegionPercent; ++ ++ public SingleThreadChunkRegionManager(final WorldServer world, final Class enumClass, ++ final int minSectionRecalcCount, final double maxDeadRegionPercent, ++ final String name) { ++ this.world = world; ++ this.dataClass = enumClass; ++ this.name = name; ++ this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount); ++ this.maxDeadRegionPercent = maxDeadRegionPercent; ++ ++ String prefix = world.getWorld().getName() + " - Region Manager - " + name + " - "; ++ this.addChunkTimings = MinecraftTimings.getInternalTaskName(prefix.concat("add")); ++ this.removeChunkTimings = MinecraftTimings.getInternalTaskName(prefix.concat("remove")); ++ this.regionRecalculateTimings = MinecraftTimings.getInternalTaskName(prefix.concat("recalculate")); ++ } ++ ++ // tested via https://gist.github.com/Spottedleaf/aa7ade3451c37b4cac061fc77074db2f ++ ++ /* ++ protected void check() { ++ ReferenceOpenHashSet> checked = new ReferenceOpenHashSet<>(); ++ ++ for (RegionSection section : this.regionsBySection.values()) { ++ if (!checked.add(section.region)) { ++ section.region.check(); ++ } ++ } ++ for (Region region : this.needsRecalculation) { ++ region.check(); ++ } ++ } ++ */ ++ ++ protected void addToRecalcQueue(final Region region) { ++ this.needsRecalculation.add(region); ++ } ++ ++ protected void removeFromRecalcQueue(final Region region) { ++ this.needsRecalculation.remove(region); ++ } ++ ++ public RegionSection getRegionSection(final int chunkX, final int chunkZ) { ++ return this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT)); ++ } ++ ++ public Region getRegion(final int chunkX, final int chunkZ) { ++ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT)); ++ return section != null ? section.region : null; ++ } ++ ++ private final List> toMerge = new ArrayList<>((2 * REGION_SECTION_MERGE_RADIUS + 1) * (2 * REGION_SECTION_MERGE_RADIUS + 1)); ++ ++ protected RegionSection getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection force) { ++ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ); ++ ++ if (force == null) { ++ RegionSection region = this.regionsBySection.get(sectionKey); ++ if (region != null) { ++ return region; ++ } ++ } ++ ++ int mergeCandidateSectionSize = -1; ++ Region mergeIntoCandidate = null; ++ ++ // find optimal candidate to merge into ++ ++ final int minX = sectionX - REGION_SECTION_MERGE_RADIUS; ++ final int maxX = sectionX + REGION_SECTION_MERGE_RADIUS; ++ final int minZ = sectionZ - REGION_SECTION_MERGE_RADIUS; ++ final int maxZ = sectionZ + REGION_SECTION_MERGE_RADIUS; ++ for (int currX = minX; currX <= maxX; ++currX) { ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ)); ++ if (section == null) { ++ continue; ++ } ++ final Region region = section.region; ++ if (region.dead) { ++ throw new IllegalStateException("Dead region should not be in live region manager state: " + region); ++ } ++ final int sections = region.sections.size(); ++ ++ if (sections > mergeCandidateSectionSize) { ++ mergeCandidateSectionSize = sections; ++ mergeIntoCandidate = region; ++ } ++ this.toMerge.add(region); ++ } ++ } ++ ++ // merge ++ if (mergeIntoCandidate != null) { ++ for (int i = 0; i < this.toMerge.size(); ++i) { ++ final Region region = this.toMerge.get(i); ++ if (region.dead || mergeIntoCandidate == region) { ++ continue; ++ } ++ region.mergeInto(mergeIntoCandidate); ++ } ++ this.toMerge.clear(); ++ } else { ++ mergeIntoCandidate = new Region<>(this); ++ } ++ ++ final RegionSection section; ++ if (force == null) { ++ this.regionsBySection.put(sectionKey, section = new RegionSection<>(sectionKey, this)); ++ } else { ++ final RegionSection existing = this.regionsBySection.putIfAbsent(sectionKey, force); ++ if (existing != null) { ++ throw new IllegalStateException("Attempting to override section '" + existing.toStringWithRegion() + ++ ", with " + force.toStringWithRegion()); ++ } ++ ++ section = force; ++ } ++ ++ section.region = mergeIntoCandidate; ++ mergeIntoCandidate.sections.add(section); ++ //mergeIntoCandidate.check(); ++ //this.check(); ++ ++ return section; ++ } ++ ++ public void addChunk(final int chunkX, final int chunkZ) { ++ this.addChunkTimings.startTiming(); ++ try { ++ this.getOrCreateAndMergeSection(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT, null).addChunk(chunkX, chunkZ); ++ } finally { ++ this.addChunkTimings.stopTiming(); ++ } ++ } ++ ++ public void removeChunk(final int chunkX, final int chunkZ) { ++ this.removeChunkTimings.startTiming(); ++ try { ++ final RegionSection section = this.regionsBySection.get( ++ MCUtil.getCoordinateKey(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT) ++ ); ++ if (section != null) { ++ section.removeChunk(chunkX, chunkZ); ++ } else { ++ throw new IllegalStateException("Cannot remove chunk at (" + chunkX + "," + chunkZ + ") from region state, section does not exist"); ++ } ++ } finally { ++ this.removeChunkTimings.stopTiming(); ++ } ++ } ++ ++ public void recalculateRegions() { ++ for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) { ++ final Region region = this.needsRecalculation.removeFirst(); ++ ++ this.recalculateRegion(region); ++ //this.check(); ++ } ++ } ++ ++ protected void recalculateRegion(final Region region) { ++ this.regionRecalculateTimings.startTiming(); ++ try { ++ region.markedForRecalc = false; ++ //region.check(); ++ // clear unused regions ++ for (final Iterator> iterator = region.deadSections.iterator(); iterator.hasNext();) { ++ final RegionSection deadSection = iterator.next(); ++ ++ if (deadSection.hasChunks()) { ++ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!"); ++ } ++ if (!region.sections.remove(deadSection)) { ++ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); ++ } ++ if (!this.regionsBySection.remove(deadSection.regionCoordinate, deadSection)) { ++ throw new IllegalStateException("Cannot remove dead section '" + ++ deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " + ++ this.regionsBySection.get(deadSection.regionCoordinate)); ++ } ++ } ++ region.deadSections.clear(); ++ ++ // implicitly cover cases where size == 0 ++ if (region.sections.size() < this.minSectionRecalcCount) { ++ //region.check(); ++ return; ++ } ++ ++ // run a test to see if we actually need to recalculate ++ // TODO ++ ++ // destroy and rebuild the region ++ region.dead = true; ++ ++ // destroy region state ++ for (final Iterator> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final RegionSection aliveSection = iterator.next(); ++ if (!aliveSection.hasChunks()) { ++ throw new IllegalStateException("Alive section '" + aliveSection.toStringWithRegion() + "' has no chunks!"); ++ } ++ if (!this.regionsBySection.remove(aliveSection.regionCoordinate, aliveSection)) { ++ throw new IllegalStateException("Cannot remove alive section '" + ++ aliveSection.toStringWithRegion() + "' from section state! State at section coordinate: " + ++ this.regionsBySection.get(aliveSection.regionCoordinate)); ++ } ++ } ++ ++ // rebuild regions ++ for (final Iterator> iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final RegionSection aliveSection = iterator.next(); ++ this.getOrCreateAndMergeSection(aliveSection.getSectionX(), aliveSection.getSectionZ(), aliveSection); ++ } ++ } finally { ++ this.regionRecalculateTimings.stopTiming(); ++ } ++ } ++ ++ public static final class Region & SingleThreadChunkRegionManager.RegionDataCreator> { ++ protected final IteratorSafeOrderedReferenceSet> sections = new IteratorSafeOrderedReferenceSet<>(true); ++ protected final ReferenceOpenHashSet> deadSections = new ReferenceOpenHashSet<>(16, 0.7f); ++ protected boolean dead; ++ protected boolean markedForRecalc; ++ ++ public final SingleThreadChunkRegionManager regionManager; ++ ++ protected Region(final SingleThreadChunkRegionManager regionManager) { ++ this.regionManager = regionManager; ++ } ++ ++ public IteratorSafeOrderedReferenceSet.Iterator> getSections() { ++ return this.sections.iterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); ++ } ++ ++ protected final double getDeadSectionPercent() { ++ return (double)this.deadSections.size() / (double)this.sections.size(); ++ } ++ ++ /* ++ protected void check() { ++ if (this.dead) { ++ throw new IllegalStateException("Dead region!"); ++ } ++ for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final RegionSection section = iterator.next(); ++ if (section.region != this) { ++ throw new IllegalStateException("Region section must point to us!"); ++ } ++ if (this.regionManager.regionsBySection.get(section.regionCoordinate) != section) { ++ throw new IllegalStateException("Region section must match the regionmanager state!"); ++ } ++ } ++ } ++ */ ++ ++ protected void mergeInto(final Region mergeTarget) { ++ if (this == mergeTarget) { ++ throw new IllegalStateException("Cannot merge a region onto itself"); ++ } ++ if (this.dead) { ++ throw new IllegalStateException("Source region is dead! Source " + this + ", target " + mergeTarget); ++ } else if (mergeTarget.dead) { ++ throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget); ++ } ++ this.dead = true; ++ if (this.markedForRecalc) { ++ this.regionManager.removeFromRecalcQueue(this); ++ } ++ ++ for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final RegionSection section = iterator.next(); ++ ++ if (!mergeTarget.sections.add(section)) { ++ throw new IllegalStateException("Target cannot contain source's sections! Source " + this + ", target " + mergeTarget); ++ } ++ ++ section.region = mergeTarget; ++ } ++ ++ for (final RegionSection deadSection : this.deadSections) { ++ if (!this.sections.contains(deadSection)) { ++ throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this); ++ } ++ mergeTarget.deadSections.add(deadSection); ++ } ++ //mergeTarget.check(); ++ } ++ ++ protected void markSectionAlive(final RegionSection section) { ++ this.deadSections.remove(section); ++ if (this.markedForRecalc && (this.sections.size() < this.regionManager.minSectionRecalcCount || this.getDeadSectionPercent() < this.regionManager.maxDeadRegionPercent)) { ++ this.regionManager.removeFromRecalcQueue(this); ++ this.markedForRecalc = false; ++ } ++ } ++ ++ protected void markSectionDead(final RegionSection section) { ++ this.deadSections.add(section); ++ if (!this.markedForRecalc && (this.sections.size() >= this.regionManager.minSectionRecalcCount || this.sections.size() == this.deadSections.size()) && this.getDeadSectionPercent() >= this.regionManager.maxDeadRegionPercent) { ++ this.regionManager.addToRecalcQueue(this); ++ this.markedForRecalc = true; ++ } ++ } ++ ++ @Override ++ public String toString() { ++ final StringBuilder ret = new StringBuilder(128); ++ ++ ret.append("Region{"); ++ ret.append("dead=").append(this.dead).append(','); ++ ret.append("markedForRecalc=").append(this.markedForRecalc).append(','); ++ ++ ret.append("sectionCount=").append(this.sections.size()).append(','); ++ ret.append("sections=["); ++ for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final RegionSection section = iterator.next(); ++ ret.append(section); ++ if (iterator.hasNext()) { ++ ret.append(','); ++ } ++ } ++ ret.append(']'); ++ ++ ret.append('}'); ++ return ret.toString(); ++ } ++ } ++ ++ public static final class RegionSection & SingleThreadChunkRegionManager.RegionDataCreator> { ++ protected final long regionCoordinate; ++ protected final long[] chunksBitset = new long[Math.max(1, REGION_CHUNK_SIZE * REGION_CHUNK_SIZE / Long.SIZE)]; ++ protected int chunkCount; ++ protected Region region; ++ protected final EnumMap data; ++ protected final Function createIfAbsentFunction; ++ ++ public final SingleThreadChunkRegionManager regionManager; ++ ++ protected RegionSection(final long regionCoordinate, final SingleThreadChunkRegionManager regionManager) { ++ this.regionCoordinate = regionCoordinate; ++ this.data = new EnumMap<>(regionManager.dataClass); ++ this.regionManager = regionManager; ++ this.createIfAbsentFunction = (final T keyInMap) -> { ++ return keyInMap.createData(RegionSection.this, regionManager); ++ }; ++ } ++ ++ public int getSectionX() { ++ return MCUtil.getCoordinateX(this.regionCoordinate); ++ } ++ ++ public int getSectionZ() { ++ return MCUtil.getCoordinateZ(this.regionCoordinate); ++ } ++ ++ public Region getRegion() { ++ return this.region; ++ } ++ ++ public Object getData(final T key) { ++ return this.data.get(key); ++ } ++ ++ public Object getOrCreateData(final T key) { ++ return this.data.computeIfAbsent(key, this.createIfAbsentFunction); ++ } ++ ++ public Object removeData(final T key) { ++ return this.data.remove(key); ++ } ++ ++ public void setData(final T key, final Object data) { ++ this.data.put(key, data); ++ } ++ ++ private static int getChunkIndex(final int chunkX, final int chunkZ) { ++ return (chunkX & (REGION_CHUNK_SIZE - 1)) | ((chunkZ & (REGION_CHUNK_SIZE - 1)) << REGION_CHUNK_SIZE_SHIFT); ++ } ++ ++ protected boolean hasChunks() { ++ return this.chunkCount != 0; ++ } ++ ++ protected void addChunk(final int chunkX, final int chunkZ) { ++ final int index = getChunkIndex(chunkX, chunkZ); ++ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE ++ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1))); ++ if (after == bitset) { ++ throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString()); ++ } ++ if (++this.chunkCount != 1) { ++ return; ++ } ++ this.region.markSectionAlive(this); ++ } ++ ++ protected void removeChunk(final int chunkX, final int chunkZ) { ++ final int index = getChunkIndex(chunkX, chunkZ); ++ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE ++ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1))); ++ if (before == bitset) { ++ throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkCoordIntPair(chunkX, chunkZ).toString()); ++ } ++ if (--this.chunkCount != 0) { ++ return; ++ } ++ this.region.markSectionDead(this); ++ } ++ ++ @Override ++ public String toString() { ++ return "RegionSection{" + ++ "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," + ++ "chunkCount=" + this.chunkCount + "," + ++ "chunksBitset=" + toString(this.chunksBitset) + "," + ++ "hash=" + this.hashCode() + ++ "}"; ++ } ++ ++ public String toStringWithRegion() { ++ return "RegionSection{" + ++ "regionCoordinate=" + new ChunkCoordIntPair(this.regionCoordinate).toString() + "," + ++ "chunkCount=" + this.chunkCount + "," + ++ "chunksBitset=" + toString(this.chunksBitset) + "," + ++ "hash=" + this.hashCode() + "," + ++ "region=" + this.region + ++ "}"; ++ } ++ ++ private static String toString(final long[] array) { ++ StringBuilder ret = new StringBuilder(); ++ for (long value : array) { ++ // zero pad the hex string ++ char[] zeros = new char[Long.SIZE / 4]; ++ Arrays.fill(zeros, '0'); ++ String string = Long.toHexString(value); ++ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length()); ++ ++ ret.append(zeros); ++ } ++ ++ return ret.toString(); ++ } ++ } ++ ++ public static interface RegionDataCreator & RegionDataCreator> { ++ ++ Object createData(final RegionSection section, ++ final SingleThreadChunkRegionManager regionManager); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d2c7d2c7920324d7207225ed19484e804368489d +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java +@@ -0,0 +1,100 @@ ++package com.tuinity.tuinity.util; ++ ++public final class IntervalledCounter { ++ ++ protected long[] times; ++ protected final long interval; ++ protected long minTime; ++ protected int sum; ++ protected int head; // inclusive ++ protected int tail; // exclusive ++ ++ public IntervalledCounter(final long interval) { ++ this.times = new long[8]; ++ this.interval = interval; ++ } ++ ++ public void updateCurrentTime() { ++ this.updateCurrentTime(System.nanoTime()); ++ } ++ ++ public void updateCurrentTime(final long currentTime) { ++ int sum = this.sum; ++ int head = this.head; ++ final int tail = this.tail; ++ final long minTime = currentTime - this.interval; ++ ++ final int arrayLen = this.times.length; ++ ++ // guard against overflow by using subtraction ++ while (head != tail && this.times[head] - minTime < 0) { ++ head = (head + 1) % arrayLen; ++ --sum; ++ } ++ ++ this.sum = sum; ++ this.head = head; ++ this.minTime = minTime; ++ } ++ ++ public void addTime(final long currTime) { ++ // guard against overflow by using subtraction ++ if (currTime - this.minTime < 0) { ++ return; ++ } ++ int nextTail = (this.tail + 1) % this.times.length; ++ if (nextTail == this.head) { ++ this.resize(); ++ nextTail = (this.tail + 1) % this.times.length; ++ } ++ ++ this.times[this.tail] = currTime; ++ this.tail = nextTail; ++ } ++ ++ public void updateAndAdd(final int count) { ++ final long currTime = System.nanoTime(); ++ this.updateCurrentTime(currTime); ++ for (int i = 0; i < count; ++i) { ++ this.addTime(currTime); ++ } ++ } ++ ++ public void updateAndAdd(final int count, final long currTime) { ++ this.updateCurrentTime(currTime); ++ for (int i = 0; i < count; ++i) { ++ this.addTime(currTime); ++ } ++ } ++ ++ private void resize() { ++ final long[] oldElements = this.times; ++ final long[] newElements = new long[this.times.length * 2]; ++ this.times = newElements; ++ ++ final int head = this.head; ++ final int tail = this.tail; ++ final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head)); ++ this.head = 0; ++ this.tail = size; ++ ++ if (tail >= head) { ++ System.arraycopy(oldElements, head, newElements, 0, size); ++ } else { ++ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); ++ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); ++ } ++ } ++ ++ // returns in units per second ++ public double getRate() { ++ return this.size() / (this.interval * 1.0e-9); ++ } ++ ++ public int size() { ++ final int head = this.head; ++ final int tail = this.tail; ++ ++ return tail >= head ? (tail - head) : (tail + (this.times.length - head)); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..be408aebbccbda46e8aa82ef337574137cfa0096 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java +@@ -0,0 +1,335 @@ ++package com.tuinity.tuinity.util.maplist; ++ ++import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2IntMap; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import org.bukkit.Bukkit; ++import java.util.Arrays; ++import java.util.NoSuchElementException; ++ ++public final class IteratorSafeOrderedReferenceSet { ++ ++ public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0; ++ ++ protected final Reference2IntLinkedOpenHashMap indexMap; ++ protected int firstInvalidIndex = -1; ++ ++ /* list impl */ ++ protected E[] listElements; ++ protected int listSize; ++ ++ protected final double maxFragFactor; ++ ++ protected int iteratorCount; ++ ++ private final boolean threadRestricted; ++ ++ public IteratorSafeOrderedReferenceSet() { ++ this(16, 0.75f, 16, 0.2); ++ } ++ ++ public IteratorSafeOrderedReferenceSet(final boolean threadRestricted) { ++ this(16, 0.75f, 16, 0.2, threadRestricted); ++ } ++ ++ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, ++ final double maxFragFactor) { ++ this(setCapacity, setLoadFactor, arrayCapacity, maxFragFactor, false); ++ } ++ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, ++ final double maxFragFactor, final boolean threadRestricted) { ++ this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor); ++ this.indexMap.defaultReturnValue(-1); ++ this.maxFragFactor = maxFragFactor; ++ this.listElements = (E[])new Object[arrayCapacity]; ++ this.threadRestricted = threadRestricted; ++ } ++ ++ /* ++ public void check() { ++ int iterated = 0; ++ ReferenceOpenHashSet check = new ReferenceOpenHashSet<>(); ++ if (this.listElements != null) { ++ for (int i = 0; i < this.listSize; ++i) { ++ Object obj = this.listElements[i]; ++ if (obj != null) { ++ iterated++; ++ if (!check.add((E)obj)) { ++ throw new IllegalStateException("contains duplicate"); ++ } ++ if (!this.contains((E)obj)) { ++ throw new IllegalStateException("desync"); ++ } ++ } ++ } ++ } ++ ++ if (iterated != this.size()) { ++ throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size()); ++ } ++ ++ check.clear(); ++ iterated = 0; ++ for (final java.util.Iterator iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ final E element = iterator.next(); ++ iterated++; ++ if (!check.add(element)) { ++ throw new IllegalStateException("contains duplicate (iterator is wrong)"); ++ } ++ if (!this.contains(element)) { ++ throw new IllegalStateException("desync (iterator is wrong)"); ++ } ++ } ++ ++ if (iterated != this.size()) { ++ throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size()); ++ } ++ } ++ */ ++ ++ protected final boolean allowSafeIteration() { ++ return !this.threadRestricted || Bukkit.isPrimaryThread(); ++ } ++ ++ protected final double getFragFactor() { ++ return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); ++ } ++ ++ public int createRawIterator() { ++ if (this.allowSafeIteration()) { ++ ++this.iteratorCount; ++ } ++ if (this.indexMap.isEmpty()) { ++ return -1; ++ } else { ++ return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0; ++ } ++ } ++ ++ public int advanceRawIterator(final int index) { ++ final E[] elements = this.listElements; ++ int ret = index + 1; ++ for (int len = this.listSize; ret < len; ++ret) { ++ if (elements[ret] != null) { ++ return ret; ++ } ++ } ++ ++ return -1; ++ } ++ ++ public void finishRawIterator() { ++ if (this.allowSafeIteration() && --this.iteratorCount == 0) { ++ if (this.getFragFactor() >= this.maxFragFactor) { ++ this.defrag(); ++ } ++ } ++ } ++ ++ public boolean remove(final E element) { ++ final int index = this.indexMap.removeInt(element); ++ if (index >= 0) { ++ if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) { ++ this.firstInvalidIndex = index; ++ } ++ if (this.listElements[index] != element) { ++ throw new IllegalStateException(); ++ } ++ this.listElements[index] = null; ++ if (this.allowSafeIteration() && this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { ++ this.defrag(); ++ } ++ //this.check(); ++ return true; ++ } ++ return false; ++ } ++ ++ public boolean contains(final E element) { ++ return this.indexMap.containsKey(element); ++ } ++ ++ public boolean add(final E element) { ++ final int listSize = this.listSize; ++ ++ final int previous = this.indexMap.putIfAbsent(element, listSize); ++ if (previous != -1) { ++ return false; ++ } ++ ++ if (listSize >= this.listElements.length) { ++ this.listElements = Arrays.copyOf(this.listElements, listSize * 2); ++ } ++ this.listElements[listSize] = element; ++ this.listSize = listSize + 1; ++ ++ //this.check(); ++ return true; ++ } ++ ++ protected void defrag() { ++ if (this.firstInvalidIndex < 0) { ++ return; // nothing to do ++ } ++ ++ if (this.indexMap.isEmpty()) { ++ Arrays.fill(this.listElements, 0, this.listSize, null); ++ this.listSize = 0; ++ this.firstInvalidIndex = -1; ++ //this.check(); ++ return; ++ } ++ ++ final E[] backingArray = this.listElements; ++ ++ int lastValidIndex; ++ java.util.Iterator> iterator; ++ ++ if (this.firstInvalidIndex == 0) { ++ iterator = this.indexMap.reference2IntEntrySet().fastIterator(); ++ lastValidIndex = 0; ++ } else { ++ lastValidIndex = this.firstInvalidIndex; ++ final E key = backingArray[lastValidIndex - 1]; ++ iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry() { ++ @Override ++ public int getIntValue() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int setValue(int i) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public E getKey() { ++ return key; ++ } ++ }); ++ } ++ ++ while (iterator.hasNext()) { ++ final Reference2IntMap.Entry entry = iterator.next(); ++ ++ final int newIndex = lastValidIndex++; ++ backingArray[newIndex] = entry.getKey(); ++ entry.setValue(newIndex); ++ } ++ ++ // cleanup end ++ Arrays.fill(backingArray, lastValidIndex, this.listSize, null); ++ this.listSize = lastValidIndex; ++ this.firstInvalidIndex = -1; ++ //this.check(); ++ } ++ ++ public E rawGet(final int index) { ++ return this.listElements[index]; ++ } ++ ++ public int size() { ++ // always returns the correct amount - listSize can be different ++ return this.indexMap.size(); ++ } ++ ++ public IteratorSafeOrderedReferenceSet.Iterator iterator() { ++ return this.iterator(0); ++ } ++ ++ public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { ++ if (this.allowSafeIteration()) { ++ ++this.iteratorCount; ++ } ++ return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); ++ } ++ ++ public java.util.Iterator unsafeIterator() { ++ return this.unsafeIterator(0); ++ } ++ public java.util.Iterator unsafeIterator(final int flags) { ++ return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); ++ } ++ ++ public static interface Iterator extends java.util.Iterator { ++ ++ public void finishedIterating(); ++ ++ } ++ ++ protected static final class BaseIterator implements IteratorSafeOrderedReferenceSet.Iterator { ++ ++ protected final IteratorSafeOrderedReferenceSet set; ++ protected final boolean canFinish; ++ protected final int maxIndex; ++ protected int nextIndex; ++ protected E pendingValue; ++ protected boolean finished; ++ protected E lastReturned; ++ ++ protected BaseIterator(final IteratorSafeOrderedReferenceSet set, final boolean canFinish, final int maxIndex) { ++ this.set = set; ++ this.canFinish = canFinish; ++ this.maxIndex = maxIndex; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ if (this.finished) { ++ return false; ++ } ++ if (this.pendingValue != null) { ++ return true; ++ } ++ ++ final E[] elements = this.set.listElements; ++ int index, len; ++ for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) { ++ final E element = elements[index]; ++ if (element != null) { ++ this.pendingValue = element; ++ this.nextIndex = index + 1; ++ return true; ++ } ++ } ++ ++ this.nextIndex = index; ++ return false; ++ } ++ ++ @Override ++ public E next() { ++ if (!this.hasNext()) { ++ throw new NoSuchElementException(); ++ } ++ final E ret = this.pendingValue; ++ ++ this.pendingValue = null; ++ this.lastReturned = ret; ++ ++ return ret; ++ } ++ ++ @Override ++ public void remove() { ++ final E lastReturned = this.lastReturned; ++ if (lastReturned == null) { ++ throw new IllegalStateException(); ++ } ++ this.lastReturned = null; ++ this.set.remove(lastReturned); ++ } ++ ++ @Override ++ public void finishedIterating() { ++ if (this.finished || !this.canFinish) { ++ throw new IllegalStateException(); ++ } ++ this.lastReturned = null; ++ this.finished = true; ++ if (this.set.allowSafeIteration()) { ++ this.set.finishRawIterator(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java +new file mode 100644 +index 0000000000000000000000000000000000000000..155d10994f2d7df9ac927d955d99016fe304360f +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java +@@ -0,0 +1,295 @@ ++package com.tuinity.tuinity.util.misc; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import net.minecraft.server.MCUtil; ++ ++public final class Delayed26WayDistancePropagator3D { ++ ++ // this map is considered "stale" unless updates are propagated. ++ protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f); ++ ++ // this map is never stale ++ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); ++ ++ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when ++ // propagating updates ++ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); ++ ++ @FunctionalInterface ++ public static interface LevelChangeCallback { ++ ++ /** ++ * This can be called for intermediate updates. So do not rely on newLevel being close to or ++ * the exact level that is expected after a full propagation has occured. ++ */ ++ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); ++ ++ } ++ ++ protected final LevelChangeCallback changeCallback; ++ ++ public Delayed26WayDistancePropagator3D() { ++ this(null); ++ } ++ ++ public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) { ++ this.changeCallback = changeCallback; ++ } ++ ++ public int getLevel(final long pos) { ++ return this.levels.get(pos); ++ } ++ ++ public int getLevel(final int x, final int y, final int z) { ++ return this.levels.get(MCUtil.getSectionKey(x, y, z)); ++ } ++ ++ public void setSource(final int x, final int y, final int z, final int level) { ++ this.setSource(MCUtil.getSectionKey(x, y, z), level); ++ } ++ ++ public void setSource(final long coordinate, final int level) { ++ if ((level & 63) != level || level == 0) { ++ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); ++ } ++ ++ final byte byteLevel = (byte)level; ++ final byte oldLevel = this.sources.put(coordinate, byteLevel); ++ ++ if (oldLevel == byteLevel) { ++ return; // nothing to do ++ } ++ ++ // queue to update later ++ this.updatedSources.add(coordinate); ++ } ++ ++ public void removeSource(final int x, final int y, final int z) { ++ this.removeSource(MCUtil.getSectionKey(x, y, z)); ++ } ++ ++ public void removeSource(final long coordinate) { ++ if (this.sources.remove(coordinate) != 0) { ++ this.updatedSources.add(coordinate); ++ } ++ } ++ ++ // queues used for BFS propagating levels ++ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { ++ this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); ++ } ++ } ++ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { ++ this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); ++ } ++ } ++ protected long levelIncreaseWorkQueueBitset; ++ protected long levelRemoveWorkQueueBitset; ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << level); ++ } ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << index); ++ } ++ ++ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelRemoveWorkQueueBitset |= (1L << level); ++ } ++ ++ public void propagateUpdates() { ++ if (this.updatedSources.isEmpty()) { ++ return; ++ } ++ ++ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { ++ final long coordinate = iterator.nextLong(); ++ ++ final byte currentLevel = this.levels.get(coordinate); ++ final byte updatedSource = this.sources.get(coordinate); ++ ++ if (currentLevel == updatedSource) { ++ continue; ++ } ++ ++ if (updatedSource > currentLevel) { ++ // level increase ++ this.addToIncreaseWorkQueue(coordinate, updatedSource); ++ } else { ++ // level decrease ++ this.addToRemoveWorkQueue(coordinate, currentLevel); ++ // if the current coordinate is a source, then the decrease propagation will detect that and queue ++ // the source propagation ++ } ++ } ++ ++ this.updatedSources.clear(); ++ ++ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions ++ // make the removes remove less) ++ this.propagateIncreases(); ++ ++ // now we propagate the decreases (which will then re-propagate clobbered sources) ++ this.propagateDecreases(); ++ } ++ ++ protected void propagateIncreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); ++ this.levelIncreaseWorkQueueBitset != 0L; ++ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { ++ ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final boolean neighbourCheck = level < 0; ++ ++ final byte currentLevel; ++ if (neighbourCheck) { ++ level = (byte)-level; ++ currentLevel = this.levels.get(coordinate); ++ } else { ++ currentLevel = this.levels.putIfGreater(coordinate, level); ++ } ++ ++ if (neighbourCheck) { ++ // used when propagating from decrease to indicate that this level needs to check its neighbours ++ // this means the level at coordinate could be equal, but would still need neighbours checked ++ ++ if (currentLevel != level) { ++ // something caused the level to change, which means something propagated to it (which means ++ // us propagating here is redundant), or something removed the level (which means we ++ // cannot propagate further) ++ continue; ++ } ++ } else if (currentLevel >= level) { ++ // something higher/equal propagated ++ continue; ++ } ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); ++ } ++ ++ if (level == 1) { ++ // can't propagate 0 to neighbours ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = MCUtil.getSectionX(coordinate); ++ final int y = MCUtil.getSectionY(coordinate); ++ final int z = MCUtil.getSectionZ(coordinate); ++ ++ for (int dy = -1; dy <= 1; ++dy) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if ((dy | dz | dx) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z); ++ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected void propagateDecreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); ++ this.levelRemoveWorkQueueBitset != 0L; ++ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { ++ ++ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ final byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); ++ if (currentLevel == 0) { ++ // something else removed ++ continue; ++ } ++ ++ if (currentLevel > level) { ++ // something higher propagated here or we hit the propagation of another source ++ // in the second case we need to re-propagate because we could have just clobbered another source's ++ // propagation ++ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking ++ continue; ++ } ++ ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); ++ } ++ ++ final byte source = this.sources.get(coordinate); ++ if (source != 0) { ++ // must re-propagate source later ++ this.addToIncreaseWorkQueue(coordinate, source); ++ } ++ ++ if (level == 0) { ++ // can't propagate -1 to neighbours ++ // we have to check neighbours for removing 1 just in case the neighbour is 2 ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = MCUtil.getSectionX(coordinate); ++ final int y = MCUtil.getSectionY(coordinate); ++ final int z = MCUtil.getSectionZ(coordinate); ++ ++ for (int dy = -1; dy <= 1; ++dy) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if ((dy | dz | dx) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getSectionKey(dx + x, dy + y, dz + z); ++ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered in the process ++ this.propagateIncreases(); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java +new file mode 100644 +index 0000000000000000000000000000000000000000..606417a8aeaca2682595f417bba8e9d411999da9 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java +@@ -0,0 +1,713 @@ ++package com.tuinity.tuinity.util.misc; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import net.minecraft.server.MCUtil; ++ ++public final class Delayed8WayDistancePropagator2D { ++ ++ // Test ++ /* ++ protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference, Delayed8WayDistancePropagator2D test) { ++ int got = test.getLevel(x, z); ++ ++ int expect = 0; ++ Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet(); ++ if (nearest != null) { ++ for (Object _obj : nearest) { ++ if (_obj instanceof Ticket) { ++ Ticket ticket = (Ticket)_obj; ++ long ticketCoord = reference.getLastCoordinate(ticket); ++ int viewDistance = reference.getLastViewDistance(ticket); ++ int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x), ++ com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z)); ++ int level = viewDistance - distance; ++ if (level > expect) { ++ expect = level; ++ } ++ } ++ } ++ } ++ ++ if (expect != got) { ++ throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got); ++ } ++ } ++ ++ static class Ticket { ++ ++ int x; ++ int z; ++ ++ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty ++ = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); ++ ++ } ++ ++ public static void main(final String[] args) { ++ com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap() { ++ @Override ++ protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(Ticket object) { ++ return object.empty; ++ } ++ }; ++ Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D(); ++ ++ final int maxDistance = 64; ++ // test origin ++ { ++ Ticket originTicket = new Ticket(); ++ int originDistance = 31; ++ // test single source ++ reference.add(originTicket, 0, 0, originDistance); ++ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ // test single source decrease ++ reference.update(originTicket, 0, 0, originDistance/2); ++ test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ // test source increase ++ originDistance = 2*originDistance; ++ reference.update(originTicket, 0, 0, originDistance); ++ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { ++ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ reference.remove(originTicket); ++ test.removeSource(0, 0); test.propagateUpdates(); ++ } ++ ++ // test multiple sources at origin ++ { ++ int originDistance = 31; ++ java.util.List list = new java.util.ArrayList<>(); ++ for (int i = 0; i < 10; ++i) { ++ Ticket a = new Ticket(); ++ list.add(a); ++ a.x = (i & 1) == 1 ? -i : i; ++ a.z = (i & 1) == 1 ? -i : i; ++ } ++ for (Ticket ticket : list) { ++ reference.add(ticket, ticket.x, ticket.z, originDistance); ++ test.setSource(ticket.x, ticket.z, originDistance); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level decrease ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance/2); ++ test.setSource(ticket.x, ticket.z, originDistance/2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level increase ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance*2); ++ test.setSource(ticket.x, ticket.z, originDistance*2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket remove ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ if ((i & 3) != 0) { ++ continue; ++ } ++ Ticket ticket = list.get(i); ++ reference.remove(ticket); ++ test.removeSource(ticket.x, ticket.z); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ } ++ ++ // now test at coordinate offsets ++ // test offset ++ { ++ Ticket originTicket = new Ticket(); ++ int originDistance = 31; ++ int offX = 54432; ++ int offZ = -134567; ++ // test single source ++ reference.add(originTicket, offX, offZ, originDistance); ++ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx + offX, dz + offZ, reference, test); ++ } ++ } ++ // test single source decrease ++ reference.update(originTicket, offX, offZ, originDistance/2); ++ test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate ++ for (int dx = -originDistance; dx <= originDistance; ++dx) { ++ for (int dz = -originDistance; dz <= originDistance; ++dz) { ++ test(dx + offX, dz + offZ, reference, test); ++ } ++ } ++ // test source increase ++ originDistance = 2*originDistance; ++ reference.update(originTicket, offX, offZ, originDistance); ++ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate ++ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { ++ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { ++ test(dx + offX, dz + offZ, reference, test); ++ } ++ } ++ ++ reference.remove(originTicket); ++ test.removeSource(offX, offZ); test.propagateUpdates(); ++ } ++ ++ // test multiple sources at origin ++ { ++ int originDistance = 31; ++ int offX = 54432; ++ int offZ = -134567; ++ java.util.List list = new java.util.ArrayList<>(); ++ for (int i = 0; i < 10; ++i) { ++ Ticket a = new Ticket(); ++ list.add(a); ++ a.x = offX + ((i & 1) == 1 ? -i : i); ++ a.z = offZ + ((i & 1) == 1 ? -i : i); ++ } ++ for (Ticket ticket : list) { ++ reference.add(ticket, ticket.x, ticket.z, originDistance); ++ test.setSource(ticket.x, ticket.z, originDistance); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level decrease ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance/2); ++ test.setSource(ticket.x, ticket.z, originDistance/2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { ++ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket level increase ++ ++ for (Ticket ticket : list) { ++ reference.update(ticket, ticket.x, ticket.z, originDistance*2); ++ test.setSource(ticket.x, ticket.z, originDistance*2); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ ++ // test ticket remove ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ if ((i & 3) != 0) { ++ continue; ++ } ++ Ticket ticket = list.get(i); ++ reference.remove(ticket); ++ test.removeSource(ticket.x, ticket.z); ++ } ++ test.propagateUpdates(); ++ ++ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { ++ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { ++ test(dx, dz, reference, test); ++ } ++ } ++ } ++ } ++ */ ++ ++ // this map is considered "stale" unless updates are propagated. ++ protected final LevelMap levels = new LevelMap(8192*2, 0.6f); ++ ++ // this map is never stale ++ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); ++ ++ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when ++ // propagating updates ++ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); ++ ++ @FunctionalInterface ++ public static interface LevelChangeCallback { ++ ++ /** ++ * This can be called for intermediate updates. So do not rely on newLevel being close to or ++ * the exact level that is expected after a full propagation has occured. ++ */ ++ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); ++ ++ } ++ ++ protected final LevelChangeCallback changeCallback; ++ ++ public Delayed8WayDistancePropagator2D() { ++ this(null); ++ } ++ ++ public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) { ++ this.changeCallback = changeCallback; ++ } ++ ++ public int getLevel(final long pos) { ++ return this.levels.get(pos); ++ } ++ ++ public int getLevel(final int x, final int z) { ++ return this.levels.get(MCUtil.getCoordinateKey(x, z)); ++ } ++ ++ public void setSource(final int x, final int z, final int level) { ++ this.setSource(MCUtil.getCoordinateKey(x, z), level); ++ } ++ ++ public void setSource(final long coordinate, final int level) { ++ if ((level & 63) != level || level == 0) { ++ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); ++ } ++ ++ final byte byteLevel = (byte)level; ++ final byte oldLevel = this.sources.put(coordinate, byteLevel); ++ ++ if (oldLevel == byteLevel) { ++ return; // nothing to do ++ } ++ ++ // queue to update later ++ this.updatedSources.add(coordinate); ++ } ++ ++ public void removeSource(final int x, final int z) { ++ this.removeSource(MCUtil.getCoordinateKey(x, z)); ++ } ++ ++ public void removeSource(final long coordinate) { ++ if (this.sources.remove(coordinate) != 0) { ++ this.updatedSources.add(coordinate); ++ } ++ } ++ ++ // queues used for BFS propagating levels ++ protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { ++ this.levelIncreaseWorkQueues[i] = new WorkQueue(); ++ } ++ } ++ protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64]; ++ { ++ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { ++ this.levelRemoveWorkQueues[i] = new WorkQueue(); ++ } ++ } ++ protected long levelIncreaseWorkQueueBitset; ++ protected long levelRemoveWorkQueueBitset; ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { ++ final WorkQueue queue = this.levelIncreaseWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << level); ++ } ++ ++ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { ++ final WorkQueue queue = this.levelIncreaseWorkQueues[index]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelIncreaseWorkQueueBitset |= (1L << index); ++ } ++ ++ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { ++ final WorkQueue queue = this.levelRemoveWorkQueues[level]; ++ queue.queuedCoordinates.enqueue(coordinate); ++ queue.queuedLevels.enqueue(level); ++ ++ this.levelRemoveWorkQueueBitset |= (1L << level); ++ } ++ ++ public void propagateUpdates() { ++ if (this.updatedSources.isEmpty()) { ++ return; ++ } ++ ++ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { ++ final long coordinate = iterator.nextLong(); ++ ++ final byte currentLevel = this.levels.get(coordinate); ++ final byte updatedSource = this.sources.get(coordinate); ++ ++ if (currentLevel == updatedSource) { ++ continue; ++ } ++ ++ if (updatedSource > currentLevel) { ++ // level increase ++ this.addToIncreaseWorkQueue(coordinate, updatedSource); ++ } else { ++ // level decrease ++ this.addToRemoveWorkQueue(coordinate, currentLevel); ++ // if the current coordinate is a source, then the decrease propagation will detect that and queue ++ // the source propagation ++ } ++ } ++ ++ this.updatedSources.clear(); ++ ++ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions ++ // make the removes remove less) ++ this.propagateIncreases(); ++ ++ // now we propagate the decreases (which will then re-propagate clobbered sources) ++ this.propagateDecreases(); ++ } ++ ++ protected void propagateIncreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); ++ this.levelIncreaseWorkQueueBitset != 0L; ++ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { ++ ++ final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final boolean neighbourCheck = level < 0; ++ ++ final byte currentLevel; ++ if (neighbourCheck) { ++ level = (byte)-level; ++ currentLevel = this.levels.get(coordinate); ++ } else { ++ currentLevel = this.levels.putIfGreater(coordinate, level); ++ } ++ ++ if (neighbourCheck) { ++ // used when propagating from decrease to indicate that this level needs to check its neighbours ++ // this means the level at coordinate could be equal, but would still need neighbours checked ++ ++ if (currentLevel != level) { ++ // something caused the level to change, which means something propagated to it (which means ++ // us propagating here is redundant), or something removed the level (which means we ++ // cannot propagate further) ++ continue; ++ } ++ } else if (currentLevel >= level) { ++ // something higher/equal propagated ++ continue; ++ } ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); ++ } ++ ++ if (level == 1) { ++ // can't propagate 0 to neighbours ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = (int)coordinate; ++ final int z = (int)(coordinate >>> 32); ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ if ((dx | dz) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); ++ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ } ++ ++ protected void propagateDecreases() { ++ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); ++ this.levelRemoveWorkQueueBitset != 0L; ++ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { ++ ++ final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; ++ while (!queue.queuedLevels.isEmpty()) { ++ final long coordinate = queue.queuedCoordinates.removeFirstLong(); ++ final byte level = queue.queuedLevels.removeFirstByte(); ++ ++ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); ++ if (currentLevel == 0) { ++ // something else removed ++ continue; ++ } ++ ++ if (currentLevel > level) { ++ // something higher propagated here or we hit the propagation of another source ++ // in the second case we need to re-propagate because we could have just clobbered another source's ++ // propagation ++ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking ++ continue; ++ } ++ ++ if (this.changeCallback != null) { ++ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); ++ } ++ ++ final byte source = this.sources.get(coordinate); ++ if (source != 0) { ++ // must re-propagate source later ++ this.addToIncreaseWorkQueue(coordinate, source); ++ } ++ ++ if (level == 0) { ++ // can't propagate -1 to neighbours ++ // we have to check neighbours for removing 1 just in case the neighbour is 2 ++ continue; ++ } ++ ++ // propagate to neighbours ++ final byte neighbourLevel = (byte)(level - 1); ++ final int x = (int)coordinate; ++ final int z = (int)(coordinate >>> 32); ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ if ((dx | dz) == 0) { ++ // already propagated to coordinate ++ continue; ++ } ++ ++ // sure we can check the neighbour level in the map right now and avoid a propagation, ++ // but then we would still have to recheck it when popping the value off of the queue! ++ // so just avoid the double lookup ++ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); ++ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered in the process ++ this.propagateIncreases(); ++ } ++ ++ protected static final class LevelMap extends Long2ByteOpenHashMap { ++ public LevelMap() { ++ super(); ++ } ++ ++ public LevelMap(final int expected, final float loadFactor) { ++ super(expected, loadFactor); ++ } ++ ++ // copied from superclass ++ private int find(final long k) { ++ if (k == 0L) { ++ return this.containsNullKey ? this.n : -(this.n + 1); ++ } else { ++ final long[] key = this.key; ++ long curr; ++ int pos; ++ if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) { ++ return -(pos + 1); ++ } else if (k == curr) { ++ return pos; ++ } else { ++ while((curr = key[pos = pos + 1 & this.mask]) != 0L) { ++ if (k == curr) { ++ return pos; ++ } ++ } ++ ++ return -(pos + 1); ++ } ++ } ++ } ++ ++ // copied from superclass ++ private void insert(final int pos, final long k, final byte v) { ++ if (pos == this.n) { ++ this.containsNullKey = true; ++ } ++ ++ this.key[pos] = k; ++ this.value[pos] = v; ++ if (this.size++ >= this.maxFill) { ++ this.rehash(HashCommon.arraySize(this.size + 1, this.f)); ++ } ++ } ++ ++ // copied from superclass ++ public byte putIfGreater(final long key, final byte value) { ++ final int pos = this.find(key); ++ if (pos < 0) { ++ if (this.defRetValue < value) { ++ this.insert(-pos - 1, key, value); ++ } ++ return this.defRetValue; ++ } else { ++ final byte curr = this.value[pos]; ++ if (value > curr) { ++ this.value[pos] = value; ++ return curr; ++ } ++ return curr; ++ } ++ } ++ ++ // copied from superclass ++ private void removeEntry(final int pos) { ++ --this.size; ++ this.shiftKeys(pos); ++ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { ++ this.rehash(this.n / 2); ++ } ++ } ++ ++ // copied from superclass ++ private void removeNullEntry() { ++ this.containsNullKey = false; ++ --this.size; ++ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { ++ this.rehash(this.n / 2); ++ } ++ } ++ ++ // copied from superclass ++ public byte removeIfGreaterOrEqual(final long key, final byte value) { ++ if (key == 0L) { ++ if (!this.containsNullKey) { ++ return this.defRetValue; ++ } ++ final byte current = this.value[this.n]; ++ if (value >= current) { ++ this.removeNullEntry(); ++ return current; ++ } ++ return current; ++ } else { ++ long[] keys = this.key; ++ byte[] values = this.value; ++ long curr; ++ int pos; ++ if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) { ++ return this.defRetValue; ++ } else if (key == curr) { ++ final byte current = values[pos]; ++ if (value >= current) { ++ this.removeEntry(pos); ++ return current; ++ } ++ return current; ++ } else { ++ while((curr = keys[pos = pos + 1 & this.mask]) != 0L) { ++ if (key == curr) { ++ final byte current = values[pos]; ++ if (value >= current) { ++ this.removeEntry(pos); ++ return current; ++ } ++ return current; ++ } ++ } ++ ++ return this.defRetValue; ++ } ++ } ++ } ++ } ++ ++ protected static final class WorkQueue { ++ ++ public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); ++ public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); ++ ++ } ++ ++ protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue { ++ ++ /** ++ * Assumes non-empty. If empty, undefined behaviour. ++ */ ++ public long removeFirstLong() { ++ // copied from superclass ++ long t = this.array[this.start]; ++ if (++this.start == this.length) { ++ this.start = 0; ++ } ++ ++ return t; ++ } ++ } ++ ++ protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue { ++ ++ /** ++ * Assumes non-empty. If empty, undefined behaviour. ++ */ ++ public byte removeFirstByte() { ++ // copied from superclass ++ byte t = this.array[this.start]; ++ if (++this.start == this.length) { ++ this.start = 0; ++ } ++ ++ return t; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index 1f334d63282bd5c23dc3b275a220f09e60c34537..85f60b56b5689b77ba3d9e99e29b4f734df5d91e 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -295,14 +295,14 @@ public abstract class BlockBase { + + public abstract static class BlockData extends IBlockDataHolder { + +- private final int b; +- private final boolean e; ++ private final int b; public final int getEmittedLight() { return this.b; } // Tuinity - OBFHELPER ++ private final boolean e; public final boolean isTransparentOnSomeFaces() { return this.e; } // Tuinity - OBFHELPER + private final boolean f; + private final Material g; + private final MaterialMapColor h; + public final float strength; + private final boolean j; +- private final boolean k; ++ private final boolean k; public final boolean isOpaque() { return this.k; } // Tuinity - OBFHELPER + private final BlockBase.e l; + private final BlockBase.e m; + private final BlockBase.e n; +@@ -343,12 +343,20 @@ public abstract class BlockBase { + protected Fluid fluid; + // Paper end + ++ // Tuinity start - micro the hell out of this call ++ protected boolean shapeExceedsCube = true; ++ public final boolean shapeExceedsCube() { ++ return this.shapeExceedsCube; ++ } ++ // Tuinity end ++ + public void a() { + this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid() + this.isTicking = this.getBlock().isTicking(this.p()); // Paper - moved from isTicking() + if (!this.getBlock().o()) { + this.a = new BlockBase.BlockData.Cache(this.p()); + } ++ this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here + + } + +@@ -372,10 +380,12 @@ public abstract class BlockBase { + return this.a != null ? this.a.g : this.getBlock().b(this.p(), iblockaccess, blockposition); + } + ++ public final int getOpacity(IBlockAccess iblockaccess, BlockPosition blockposition) { return this.b(iblockaccess, blockposition); } // Tuinity - OBFHELPER + public int b(IBlockAccess iblockaccess, BlockPosition blockposition) { + return this.a != null ? this.a.h : this.getBlock().f(this.p(), iblockaccess, blockposition); + } + ++ public final VoxelShape getCullingFace(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { return this.a(iblockaccess, blockposition, enumdirection); } // Tuinity - OBFHELPER + public VoxelShape a(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { + return this.a != null && this.a.i != null ? this.a.i[enumdirection.ordinal()] : VoxelShapes.a(this.c(iblockaccess, blockposition), enumdirection); + } +@@ -385,7 +395,7 @@ public abstract class BlockBase { + } + + public final boolean d() { // Paper +- return this.a == null || this.a.c; ++ return this.shapeExceedsCube; // Tuinity - moved into shape cache init + } + + public final boolean e() { // Paper +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 75d25576d68ec95a14372f8530f4916f2bd7c3c5..5e2168fa0958325358a9c41478d70c78accb53d1 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -112,7 +112,7 @@ public class ChunkProviderServer extends IChunkProvider { + return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true); + } + +- private long chunkFutureAwaitCounter; ++ long chunkFutureAwaitCounter; // Tuinity - private -> package private + + public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { + if (Thread.currentThread() != this.serverThread) { +@@ -201,6 +201,165 @@ public class ChunkProviderServer extends IChunkProvider { + } + // Paper end - rewrite ticklistserver + ++ // Tuinity start ++ // this will try to avoid chunk neighbours for lighting ++ public final IChunkAccess getFullStatusChunkAt(int chunkX, int chunkZ) { ++ Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ ++ IChunkAccess empty = this.getChunkAt(chunkX, chunkZ, ChunkStatus.EMPTY, true); ++ if (empty != null && empty.getChunkStatus() == ChunkStatus.FULL) { ++ return empty; ++ } ++ return this.getChunkAt(chunkX, chunkZ, ChunkStatus.FULL, true); ++ } ++ ++ public final IChunkAccess getFullStatusChunkAtIfLoaded(int chunkX, int chunkZ) { ++ Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ ++ IChunkAccess ret = this.getChunkAtImmediately(chunkX, chunkZ); ++ if (ret != null && ret.getChunkStatus() == ChunkStatus.FULL) { ++ return ret; ++ } else { ++ return null; ++ } ++ } ++ ++ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel, ++ java.util.function.Consumer consumer) { ++ this.getChunkAtAsynchronously(chunkX, chunkZ, ticketLevel, (PlayerChunk playerChunk) -> { ++ if (ticketLevel <= 33) { ++ return (CompletableFuture)playerChunk.getFullChunkFuture(); ++ } else { ++ return playerChunk.getOrCreateFuture(PlayerChunk.getChunkStatus(ticketLevel), ChunkProviderServer.this.playerChunkMap); ++ } ++ }, consumer); ++ } ++ ++ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel, ++ java.util.function.Function>> function, ++ java.util.function.Consumer consumer) { ++ if (Thread.currentThread() != this.serverThread) { ++ throw new IllegalStateException(); ++ } ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(chunkX, chunkZ); ++ Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++); ++ this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); ++ this.tickDistanceManager(); ++ ++ PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair()); ++ ++ if (chunk == null) { ++ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'"); ++ } ++ ++ CompletableFuture> future = function.apply(chunk); ++ ++ future.whenCompleteAsync((either, throwable) -> { ++ try { ++ if (throwable != null) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", throwable); ++ } else if (either.right().isPresent()) { ++ MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString()); ++ } ++ ++ try { ++ if (consumer != null) { ++ consumer.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. ++ } ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr); ++ return; ++ } ++ } finally { ++ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. ++ ChunkProviderServer.this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); ++ ChunkProviderServer.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); ++ } ++ }, this.serverThreadQueue); ++ } ++ ++ void chunkLoadAccept(int chunkX, int chunkZ, IChunkAccess chunk, java.util.function.Consumer consumer) { ++ try { ++ consumer.accept(chunk); ++ } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ MinecraftServer.LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.world.getWorld().getName() + "' threw an exception", throwable); ++ } ++ } ++ ++ public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer onLoad) { ++ // try to fire sync ++ int chunkStatusTicketLevel = 33 + ChunkStatus.getTicketLevelOffset(status); ++ PlayerChunk playerChunk = this.playerChunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ if (playerChunk != null) { ++ ChunkStatus holderStatus = playerChunk.getChunkHolderStatus(); ++ IChunkAccess immediate = playerChunk.getAvailableChunkNow(); ++ if (immediate != null) { ++ if (allowSubTicketLevel ? immediate.getChunkStatus().isAtLeastStatus(status) : (playerChunk.getTicketLevel() <= chunkStatusTicketLevel && holderStatus != null && holderStatus.isAtLeastStatus(status))) { ++ this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad); ++ return; ++ } else { ++ if (gen || (!allowSubTicketLevel && immediate.getChunkStatus().isAtLeastStatus(status))) { ++ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); ++ return; ++ } else { ++ this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); ++ return; ++ } ++ } ++ } ++ } ++ ++ // need to fire async ++ ++ if (gen && !allowSubTicketLevel) { ++ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); ++ return; ++ } ++ ++ this.getChunkAtAsynchronously(chunkX, chunkZ, MCUtil.getTicketLevelFor(ChunkStatus.EMPTY), (IChunkAccess chunk) -> { ++ if (chunk == null) { ++ throw new IllegalStateException("Chunk cannot be null"); ++ } ++ ++ if (!chunk.getChunkStatus().isAtLeastStatus(status)) { ++ if (gen) { ++ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); ++ return; ++ } else { ++ ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); ++ return; ++ } ++ } else { ++ if (allowSubTicketLevel) { ++ ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad); ++ return; ++ } else { ++ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); ++ return; ++ } ++ } ++ }); ++ } ++ ++ final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet tickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); ++ final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet entityTickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); ++ // Tuinity end ++ + public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier supplier) { + this.world = worldserver; + this.serverThreadQueue = new ChunkProviderServer.a(worldserver); +@@ -591,8 +750,8 @@ public class ChunkProviderServer extends IChunkProvider { + return !this.a(playerchunk, k); + } + +- @Override +- public IBlockAccess c(int i, int j) { ++ public final IBlockAccess getFeaturesReadyChunk(int x, int z) { return this.c(x, z); } // Tuinity - OBFHELPER ++ @Override public IBlockAccess c(int i, int j) { // Tuinity - OBFHELPER + long k = ChunkCoordIntPair.pair(i, j); + PlayerChunk playerchunk = this.getChunk(k); + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index f307a6361144c7e315b2e0ea45df27527cdb26ca..5c8dd000af238ea703c9f84a4621472f17955060 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -207,6 +207,14 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + // CraftBukkit end + ++ // Tuinity start ++ public final AxisAlignedBB getBoundingBoxAt(double x, double y, double z) { ++ double widthHalf = (double)this.size.width / 2.0; ++ double height = (double)this.size.height; ++ return new AxisAlignedBB(x - widthHalf, y, z - widthHalf, x + widthHalf, y + height, z + widthHalf); ++ } ++ // Tuinity end ++ + // Paper start - optimise entity tracking + final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); + +diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java +index 14ddb2a8949ce18a0c42e17a82d0d7a13ac325fe..476da43b9f0ef35b4985f88e4784b1f8c5222af3 100644 +--- a/src/main/java/net/minecraft/server/HeightMap.java ++++ b/src/main/java/net/minecraft/server/HeightMap.java +@@ -101,6 +101,7 @@ public class HeightMap { + } + } + ++ public final int get(int x, int z) { return this.a(x, z); } // Tuinity - OBFHELPER + public int a(int i, int j) { + return this.a(c(i, j)); + } +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index ff74be14512a947e81b62d53e616131ca7d7f609..2371557d083446b17ffebdae576b1cc39e939eb1 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -221,6 +221,63 @@ public final class MCUtil { + return getBlockKey(getBlockCoordinate(entity.locX()), getBlockCoordinate(entity.locY()), getBlockCoordinate(entity.locZ())); + } + ++ // Tuinity start ++ ++ static final int SECTION_X_BITS = 22; ++ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; ++ static final int SECTION_Y_BITS = 20; ++ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; ++ static final int SECTION_Z_BITS = 22; ++ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; ++ // format is y,z,x (in order of LSB to MSB) ++ static final int SECTION_Y_SHIFT = 0; ++ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; ++ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; ++ static final int SECTION_TO_BLOCK_SHIFT = 4; ++ ++ public static long getSectionKey(final int x, final int y, final int z) { ++ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getSectionKey(final SectionPosition pos) { ++ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getSectionKey(final ChunkCoordIntPair pos, final int y) { ++ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getSectionKey(final BlockPosition pos) { ++ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static long getSectionKey(final Entity entity) { ++ return ((MCUtil.fastFloor(entity.locX()) & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((MCUtil.fastFloor(entity.locY()) & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((MCUtil.fastFloor(entity.locZ()) & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static int getSectionX(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); ++ } ++ ++ public static int getSectionY(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); ++ } ++ ++ public static int getSectionZ(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); ++ } ++ // Tuinity end ++ + // assumes the sets have the same comparator, and if this comparator is null then assume T is Comparable + public static void mergeSortedSets(final java.util.function.Consumer consumer, final java.util.Comparator comparator, final java.util.SortedSet...sets) { + final ObjectRBTreeSet all = new ObjectRBTreeSet<>(comparator); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d19f944c774ad241626b268771df8e7e86cc71ae..49fae9a579d31e298c3d2c351bf0a330bf3aaf5d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -151,6 +151,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + public boolean serverAutoSave = false; // Paper +@@ -968,6 +969,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant> getOrCreateFuture(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { return this.a(chunkstatus, playerchunkmap); } // Tuinity - OBFHELPER + public CompletableFuture> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { + int i = chunkstatus.c(); + CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(i); +@@ -674,6 +675,9 @@ public class PlayerChunk { + // Paper start - rewrite ticklistserver + PlayerChunk.this.chunkMap.world.onChunkSetTicking(PlayerChunk.this.location.x, PlayerChunk.this.location.z); + // Paper end - rewrite ticklistserver ++ // Tuinity start - ticking chunk set ++ PlayerChunk.this.chunkMap.world.getChunkProvider().tickingChunks.add(tickingChunk); ++ // Tuinity end - ticking chunk set + + } + }); +@@ -684,6 +688,12 @@ public class PlayerChunk { + if (flag4 && !flag5) { + this.tickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage + this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; ++ // Tuinity start - ticking chunk set ++ Chunk chunkIfCached = this.getFullChunkIfCached(); ++ if (chunkIfCached != null) { ++ this.chunkMap.world.getChunkProvider().tickingChunks.remove(chunkIfCached); ++ } ++ // Tuinity end - ticking chunk set + } + + boolean flag6 = playerchunk_state.isAtLeast(PlayerChunk.State.ENTITY_TICKING); +@@ -701,7 +711,9 @@ public class PlayerChunk { + Chunk entityTickingChunk = either.left().get(); + PlayerChunk.this.isEntityTickingReady = true; + +- ++ // Tuinity start - entity ticking chunk set ++ PlayerChunk.this.chunkMap.world.getChunkProvider().entityTickingChunks.add(entityTickingChunk); ++ // Tuinity end - entity ticking chunk set + + + } +@@ -713,6 +725,12 @@ public class PlayerChunk { + if (flag6 && !flag7) { + this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage + this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; ++ // Tuinity start - entity ticking chunk set ++ Chunk chunkIfCached = this.getFullChunkIfCached(); ++ if (chunkIfCached != null) { ++ this.chunkMap.world.getChunkProvider().entityTickingChunks.remove(chunkIfCached); ++ } ++ // Tuinity end - entity ticking chunk set + } + + // Paper start - raise IO/load priority if priority changes, use our preferred priority +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 49008cdec739b19409fdaf1b0ed806a6c0e93200..fb46fdeb22a7c31c8d132bdc80d657ae82c191be 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -277,6 +277,21 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + // Paper end + ++ // Tuinity start ++ public static enum RegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionDataCreator { ++ ++ ; ++ ++ @Override ++ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager regionManager) { ++ throw new AbstractMethodError(); ++ } ++ } ++ ++ public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager dataRegionManager; ++ // Tuiniy end ++ + private final java.util.concurrent.ExecutorService lightThread; + public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i, boolean flag) { + super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag); +@@ -444,6 +459,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded + }); + // Paper end - no-tick view distance ++ // Tuinity start ++ this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 2, (1.0 / 3.0), "Data"); ++ // Tuinity end + } + // Paper start - Chunk Prioritization + public void queueHolderUpdate(PlayerChunk playerchunk) { +@@ -778,6 +796,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + playerchunk.a(j); + } else { + playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this); ++ this.dataRegionManager.addChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity + } + + this.updatingChunks.put(i, playerchunk); +@@ -1020,7 +1039,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (completablefuture1 != completablefuture) { + this.a(i, playerchunk); + } else { +- if (this.pendingUnload.remove(i, playerchunk) && ichunkaccess != null) { ++ // Tuinity start ++ boolean removed; ++ if ((removed = this.pendingUnload.remove(i, playerchunk)) && ichunkaccess != null) { // Tuinity end + if (ichunkaccess instanceof Chunk) { + ((Chunk) ichunkaccess).setLoaded(false); + } +@@ -1044,6 +1065,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.lightEngine.queueUpdate(); + this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null); + } ++ if (removed) this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity + + } + }; +@@ -1710,6 +1732,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow(); + } + // Paper end ++ // Tuinity start ++ public PlayerChunk getUnloadingPlayerChunk(int chunkX, int chunkZ) { ++ return this.pendingUnload.get(ChunkCoordIntPair.pair(chunkX, chunkZ)); ++ } ++ // Tuinity end + + + // Paper start - async io +diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java +index e21c747b6c39155c44bf30860681d67b0b29fb12..9f4f9df09968dc45878ad59f5ee45672a3f08fbd 100644 +--- a/src/main/java/net/minecraft/server/VoxelShapes.java ++++ b/src/main/java/net/minecraft/server/VoxelShapes.java +@@ -314,6 +314,7 @@ public final class VoxelShapes { + } + } + ++ public static boolean combinationOccludes(VoxelShape voxelshape, VoxelShape voxelshape1) { return b(voxelshape, voxelshape1); } // Tuinity - OBFHELPER + public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) { + return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; + } +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index fbe7f43f6c1010e7a34114f8afb0e64934744335..603935fef6211bd4addcd3bac58ebc0729e387c3 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -55,6 +55,7 @@ import org.bukkit.event.server.MapInitializeEvent; + import org.bukkit.event.weather.LightningStrikeEvent; + import org.bukkit.event.world.TimeSkipEvent; + // CraftBukkit end ++import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity + + public class WorldServer extends World implements GeneratorAccessSeed { + +@@ -205,6 +206,96 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + // Paper end - rewrite ticklistserver + ++ // Tuinity start ++ public final boolean areChunksLoadedForMove(AxisAlignedBB axisalignedbb) { ++ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override ++ // ICollisionAccess methods for VoxelShapes) ++ // be more strict too, add a block (dumb plugins in move events?) ++ int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ ++ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ ChunkProviderServer chunkProvider = this.getChunkProvider(); ++ ++ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { ++ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { ++ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public final void loadChunksForMoveAsync(AxisAlignedBB axisalignedbb, double toX, double toZ, ++ java.util.function.Consumer> onLoad) { ++ if (Thread.currentThread() != this.serverThread) { ++ this.getChunkProvider().serverThreadQueue.execute(() -> { ++ this.loadChunksForMoveAsync(axisalignedbb, toX, toZ, onLoad); ++ }); ++ return; ++ } ++ List ret = new java.util.ArrayList<>(); ++ IntArrayList ticketLevels = new IntArrayList(); ++ ++ int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ ++ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ ChunkProviderServer chunkProvider = this.getChunkProvider(); ++ ++ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); ++ int[] loadedChunks = new int[1]; ++ ++ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); ++ ++ java.util.function.Consumer consumer = (IChunkAccess chunk) -> { ++ if (chunk != null) { ++ int ticketLevel = Math.max(33, chunkProvider.playerChunkMap.getUpdatingChunk(chunk.getPos().pair()).getTicketLevel()); ++ ret.add(chunk); ++ ticketLevels.add(ticketLevel); ++ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); ++ } ++ if (++loadedChunks[0] == requiredChunks) { ++ try { ++ onLoad.accept(java.util.Collections.unmodifiableList(ret)); ++ } finally { ++ for (int i = 0, len = ret.size(); i < len; ++i) { ++ ChunkCoordIntPair chunkPos = ret.get(i).getPos(); ++ int ticketLevel = ticketLevels.getInt(i); ++ ++ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); ++ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); ++ } ++ } ++ } ++ }; ++ ++ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { ++ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { ++ chunkProvider.getChunkAtAsynchronously(cx, cz, ChunkStatus.FULL, true, false, consumer); ++ } ++ } ++ } ++ // Tuinity end ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor diff --git a/patches/Tuinity/patches/server/0005-Tuinity-Server-Config.patch b/patches/Tuinity/patches/server/0005-Tuinity-Server-Config.patch new file mode 100644 index 00000000..32c3d780 --- /dev/null +++ b/patches/Tuinity/patches/server/0005-Tuinity-Server-Config.patch @@ -0,0 +1,361 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 3 Mar 2019 18:25:06 -0800 +Subject: [PATCH] Tuinity Server Config + + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index e33e889c291d37a821a4fbd40d9aac7bb079de0d..5dfa0658838c4801cdf260eae8b98163f729e5af 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -229,7 +229,8 @@ public class TimingsExport extends Thread { + parent.put("config", createObject( + pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), + pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), +- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) ++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report ++ pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report + )); + + new TimingsExport(listeners, parent, history).start(); +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f66484a80d3cc7caaf8a111bd50229e673bd8e0 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -0,0 +1,236 @@ ++package com.tuinity.tuinity.config; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import org.bukkit.Bukkit; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.file.YamlConfiguration; ++import java.io.File; ++import java.lang.reflect.Method; ++import java.lang.reflect.Modifier; ++import java.util.List; ++import java.util.logging.Level; ++ ++public final class TuinityConfig { ++ ++ public static final String CONFIG_HEADER = "Configuration file for Tuinity."; ++ public static final int CURRENT_CONFIG_VERSION = 2; ++ ++ private static final Object[] EMPTY = new Object[0]; ++ ++ private static File configFile; ++ public static YamlConfiguration config; ++ private static int configVersion; ++ public static boolean createWorldSections = true; ++ ++ public static void init(final File file) { ++ // TODO remove this in the future... ++ final File tuinityConfig = new File(file.getParent(), "tuinity.yml"); ++ if (!tuinityConfig.exists()) { ++ final File oldConfig = new File(file.getParent(), "concrete.yml"); ++ oldConfig.renameTo(tuinityConfig); ++ } ++ TuinityConfig.configFile = file; ++ final YamlConfiguration config = new YamlConfiguration(); ++ config.options().header(CONFIG_HEADER); ++ config.options().copyDefaults(true); ++ ++ if (!file.exists()) { ++ try { ++ file.createNewFile(); ++ } catch (final Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Failure to create tuinity config", ex); ++ } ++ } else { ++ try { ++ config.load(file); ++ } catch (final Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Failure to load tuinity config", ex); ++ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ ++ throw new RuntimeException(ex); // unreachable ++ } ++ } ++ ++ TuinityConfig.load(config); ++ } ++ ++ public static void load(final YamlConfiguration config) { ++ TuinityConfig.config = config; ++ TuinityConfig.configVersion = TuinityConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); ++ TuinityConfig.set("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); ++ ++ for (final Method method : TuinityConfig.class.getDeclaredMethods()) { ++ if (method.getReturnType() != void.class || method.getParameterCount() != 0 || ++ !Modifier.isPrivate(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) { ++ continue; ++ } ++ ++ try { ++ method.setAccessible(true); ++ method.invoke(null, EMPTY); ++ } catch (final Exception ex) { ++ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ ++ throw new RuntimeException(ex); // unreachable ++ } ++ } ++ ++ /* We re-save to add new options */ ++ try { ++ config.save(TuinityConfig.configFile); ++ } catch (final Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); ++ } ++ } ++ ++ static void set(final String path, final Object value) { ++ TuinityConfig.config.set(path, value); ++ } ++ ++ static boolean getBoolean(final String path, final boolean dfl) { ++ TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl)); ++ return TuinityConfig.config.getBoolean(path, dfl); ++ } ++ ++ static int getInt(final String path, final int dfl) { ++ TuinityConfig.config.addDefault(path, Integer.valueOf(dfl)); ++ return TuinityConfig.config.getInt(path, dfl); ++ } ++ ++ static long getLong(final String path, final long dfl) { ++ TuinityConfig.config.addDefault(path, Long.valueOf(dfl)); ++ return TuinityConfig.config.getLong(path, dfl); ++ } ++ ++ static double getDouble(final String path, final double dfl) { ++ TuinityConfig.config.addDefault(path, Double.valueOf(dfl)); ++ return TuinityConfig.config.getDouble(path, dfl); ++ } ++ ++ static String getString(final String path, final String dfl) { ++ TuinityConfig.config.addDefault(path, dfl); ++ return TuinityConfig.config.getString(path, dfl); ++ } ++ ++ public static final class WorldConfig { ++ ++ public final String worldName; ++ public String configPath; ++ ConfigurationSection worldDefaults; ++ ++ public WorldConfig(final String worldName) { ++ this.worldName = worldName; ++ this.init(); ++ } ++ ++ public void init() { ++ this.worldDefaults = TuinityConfig.config.getConfigurationSection("world-settings.default"); ++ if (this.worldDefaults == null) { ++ this.worldDefaults = TuinityConfig.config.createSection("world-settings.default"); ++ } ++ ++ String worldSectionPath = TuinityConfig.configVersion < 1 ? this.worldName : "world-settings.".concat(this.worldName); ++ ConfigurationSection section = TuinityConfig.config.getConfigurationSection(worldSectionPath); ++ this.configPath = worldSectionPath; ++ if (TuinityConfig.createWorldSections) { ++ if (section == null) { ++ section = TuinityConfig.config.createSection(worldSectionPath); ++ } ++ TuinityConfig.config.set(worldSectionPath, section); ++ } ++ ++ this.load(); ++ } ++ ++ public void load() { ++ for (final Method method : TuinityConfig.WorldConfig.class.getDeclaredMethods()) { ++ if (method.getReturnType() != void.class || method.getParameterCount() != 0 || ++ !Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) { ++ continue; ++ } ++ ++ try { ++ method.setAccessible(true); ++ method.invoke(this, EMPTY); ++ } catch (final Exception ex) { ++ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ ++ throw new RuntimeException(ex); // unreachable ++ } ++ } ++ ++ if (TuinityConfig.configVersion < 1) { ++ ConfigurationSection oldSection = TuinityConfig.config.getConfigurationSection(this.worldName); ++ TuinityConfig.config.set("world-settings.".concat(this.worldName), oldSection); ++ TuinityConfig.config.set(this.worldName, null); ++ } ++ ++ /* We re-save to add new options */ ++ try { ++ TuinityConfig.config.save(TuinityConfig.configFile); ++ } catch (final Exception ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); ++ } ++ } ++ ++ /** ++ * update world defaults for the specified path, but also sets this world's config value for the path ++ * if it exists ++ */ ++ void set(final String path, final Object val) { ++ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); ++ this.worldDefaults.set(path, val); ++ if (config != null && config.get(path) != null) { ++ config.set(path, val); ++ } ++ } ++ ++ boolean getBoolean(final String path, final boolean dfl) { ++ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); ++ this.worldDefaults.addDefault(path, Boolean.valueOf(dfl)); ++ if (TuinityConfig.configVersion < 1) { ++ if (config != null && config.getBoolean(path) == dfl) { ++ config.set(path, null); ++ } ++ } ++ return config == null ? this.worldDefaults.getBoolean(path) : config.getBoolean(path, this.worldDefaults.getBoolean(path)); ++ } ++ ++ int getInt(final String path, final int dfl) { ++ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); ++ this.worldDefaults.addDefault(path, Integer.valueOf(dfl)); ++ if (TuinityConfig.configVersion < 1) { ++ if (config != null && config.getInt(path) == dfl) { ++ config.set(path, null); ++ } ++ } ++ return config == null ? this.worldDefaults.getInt(path) : config.getInt(path, this.worldDefaults.getInt(path)); ++ } ++ ++ long getLong(final String path, final long dfl) { ++ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); ++ this.worldDefaults.addDefault(path, Long.valueOf(dfl)); ++ if (TuinityConfig.configVersion < 1) { ++ if (config != null && config.getLong(path) == dfl) { ++ config.set(path, null); ++ } ++ } ++ return config == null ? this.worldDefaults.getLong(path) : config.getLong(path, this.worldDefaults.getLong(path)); ++ } ++ ++ double getDouble(final String path, final double dfl) { ++ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); ++ this.worldDefaults.addDefault(path, Double.valueOf(dfl)); ++ if (TuinityConfig.configVersion < 1) { ++ if (config != null && config.getDouble(path) == dfl) { ++ config.set(path, null); ++ } ++ } ++ return config == null ? this.worldDefaults.getDouble(path) : config.getDouble(path, this.worldDefaults.getDouble(path)); ++ } ++ ++ String getString(final String path, final String dfl) { ++ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); ++ this.worldDefaults.addDefault(path, dfl); ++ return config == null ? this.worldDefaults.getString(path) : config.getString(path, this.worldDefaults.getString(path)); ++ } ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java +index 5504facd2e453238caa71d98743be5416d4dd4fe..fcba187bbdc1b468cfea2bc922187d9b8959a9d5 100644 +--- a/src/main/java/net/minecraft/server/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/DedicatedServer.java +@@ -169,6 +169,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + com.destroystokyo.paper.PaperConfig.registerCommands(); + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now + // Paper end ++ com.tuinity.tuinity.config.TuinityConfig.init((java.io.File) options.valueOf("tuinity-settings")); // Tuinity - Server Config + + this.setPVP(dedicatedserverproperties.pvp); + this.setAllowFlight(dedicatedserverproperties.allowFlight); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 49fae9a579d31e298c3d2c351bf0a330bf3aaf5d..9def4694d5b90b108f9f67721cf62e58f1ac6536 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -952,6 +952,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Sun, 3 Mar 2019 20:53:18 -0800 +Subject: [PATCH] Multi-Threaded Server Ticking Vanilla + +This patch is the vanilla server changes + +Currently a placeholder patch. + +diff --git a/src/main/java/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..033548a58d27f64d3954206d267783c0437d4019 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java +@@ -0,0 +1,15 @@ ++package com.tuinity.tuinity.util; ++ ++public final class TickThread extends Thread { ++ ++ public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ ++ ++ public TickThread(final Runnable run, final String name, final int id) { ++ super(run, name); ++ this.id = id; ++ } ++ ++ public static TickThread getCurrentTickThread() { ++ return (TickThread)Thread.currentThread(); ++ } ++} +\ No newline at end of file diff --git a/patches/Tuinity/patches/server/0007-Multi-Threaded-ticking-CraftBukkit.patch b/patches/Tuinity/patches/server/0007-Multi-Threaded-ticking-CraftBukkit.patch new file mode 100644 index 00000000..5366f4c6 --- /dev/null +++ b/patches/Tuinity/patches/server/0007-Multi-Threaded-ticking-CraftBukkit.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 3 Mar 2019 20:55:07 -0800 +Subject: [PATCH] Multi-Threaded ticking CraftBukkit + +These are the changes to CB + +Currently a placeholder patch. + +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index 0f66484a80d3cc7caaf8a111bd50229e673bd8e0..f10fa659680f8a574f77d260bbc52be349c244e8 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -110,6 +110,19 @@ public final class TuinityConfig { + return TuinityConfig.config.getString(path, dfl); + } + ++ public static boolean tickWorldsInParallel; ++ ++ /** ++ * if tickWorldsInParallel == true, then this value is used as a default only for worlds ++ */ ++ public static int tickThreads; ++ ++ /* ++ private static void worldticking() { ++ tickWorldsInParallel = TuinityConfig.getBoolean("tick-worlds-in-parallel", false); ++ tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future ++ }*/ ++ + public static final class WorldConfig { + + public final String worldName; +@@ -231,6 +244,15 @@ public final class TuinityConfig { + this.worldDefaults.addDefault(path, dfl); + return config == null ? this.worldDefaults.getString(path) : config.getString(path, this.worldDefaults.getString(path)); + } ++ ++ /** ignored if {@link TuinityConfig#tickWorldsInParallel} == false */ ++ public int threads; ++ ++ /* ++ private void worldthreading() { ++ final int threads = this.getInt("tick-threads", -1); ++ this.threads = threads == -1 ? TuinityConfig.tickThreads : threads; ++ }*/ + } + + } +\ No newline at end of file +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 72916822b74c06865c8fb0135029d199343f5fbd..bd7bccbea1a0a052ef7bd6ab299ae72336874911 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1860,7 +1860,10 @@ public final class CraftServer implements Server { + + @Override + public boolean isPrimaryThread() { +- return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario ++ // Tuinity start ++ final Thread currThread = Thread.currentThread(); ++ return currThread == console.serverThread || currThread instanceof com.tuinity.tuinity.util.TickThread || currThread.equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario ++ // Tuinity End + } + + @Override +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index 9f7d2ef932ab41cef5d3d0736d20a7c7e4a2c888..10606ed03e88e4e467de2d5424df12466c4e7873 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -10,7 +10,7 @@ public class AsyncCatcher + + public static void catchOp(String reason) + { +- if ( enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) ++ if ( enabled && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity + { + throw new IllegalStateException( "Asynchronous " + reason + "!" ); + } diff --git a/patches/Tuinity/patches/server/0008-Add-soft-async-catcher.patch b/patches/Tuinity/patches/server/0008-Add-soft-async-catcher.patch new file mode 100644 index 00000000..45698ae8 --- /dev/null +++ b/patches/Tuinity/patches/server/0008-Add-soft-async-catcher.patch @@ -0,0 +1,352 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 17 Aug 2019 18:06:04 -0700 +Subject: [PATCH] Add soft async catcher + +Must be enabled via -Dtuinity.strict-thread-checks=true + +diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +index e7624948ea4aa1a07d84ed3d295cfe2dd354fd14..a263cd7a0680e0cc3517f84308118eb32c487732 100644 +--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java ++++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +@@ -186,6 +186,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + } + + public void onChunkSetTicking(final int chunkX, final int chunkZ) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list chunk ticking update"); // Tuinity - soft async catcher + final ArrayList> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ)); + if (pending == null) { + return; +@@ -268,6 +269,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + protected void nextTick() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher + ++this.currentTick; + if (this.currentTick != this.world.getTime()) { + if (!this.warnedAboutDesync) { +@@ -280,6 +282,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + public void tick() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher + final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); + + this.world.getMethodProfiler().enter("cleaning"); +@@ -424,6 +427,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + } + + public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list schedule"); // Tuinity - soft async catcher + final NextTickListEntry entry = new NextTickListEntry<>(pos, data, targetTick, priority); + if (this.excludeFromScheduling.test(entry.getData())) { + return; +@@ -479,6 +483,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + public List> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get in bounding box"); // Tuinity - soft async catcher + if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) { + return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above + } +@@ -535,6 +540,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list copy"); // Tuinity - soft async catcher + // start copy from TickListServer // TODO check on update + List> list = this.getEntriesInBoundingBox(structureboundingbox, false, false); + Iterator> iterator = list.iterator(); +@@ -554,6 +560,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + public List> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get"); // Tuinity - soft async catcher + // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks + // not at ticking status, and ticking status requires neighbours loaded + // so with this method we will reduce scheduler churning +@@ -585,6 +592,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list serialize"); // Tuinity - soft async catcher + // start copy from TickListServer // TODO check on update + List> list = this.getEntriesInChunk(chunkcoordintpair, false, true); + +@@ -594,6 +602,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + + @Override + public int getTotalScheduledEntries() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get size"); // Tuinity - soft async catcher + // good thing this is only used in debug reports // TODO check on update + int ret = 0; + +diff --git a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java +index 20150ad0750a648d349701a09b31881be124e46c..cae06962d80cdd00962236891472ba815b0ab8cd 100644 +--- a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java ++++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java +@@ -162,6 +162,7 @@ public final class SingleThreadChunkRegionManager & SingleThre + } + + public void addChunk(final int chunkX, final int chunkZ) { ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("async region manager add chunk"); // Tuinity + this.addChunkTimings.startTiming(); + try { + this.getOrCreateAndMergeSection(chunkX >> REGION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_CHUNK_SIZE_SHIFT, null).addChunk(chunkX, chunkZ); +@@ -171,6 +172,7 @@ public final class SingleThreadChunkRegionManager & SingleThre + } + + public void removeChunk(final int chunkX, final int chunkZ) { ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("async region manager remove chunk"); // Tuinity + this.removeChunkTimings.startTiming(); + try { + final RegionSection section = this.regionsBySection.get( +@@ -187,6 +189,7 @@ public final class SingleThreadChunkRegionManager & SingleThre + } + + public void recalculateRegions() { ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("async region recalculation"); // Tuinity + for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) { + final Region region = this.needsRecalculation.removeFirst(); + +diff --git a/src/main/java/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java +index 033548a58d27f64d3954206d267783c0437d4019..08ed243259f052165c6f75aed1d1d65a14219715 100644 +--- a/src/main/java/com/tuinity/tuinity/util/TickThread.java ++++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java +@@ -1,7 +1,33 @@ + package com.tuinity.tuinity.util; + ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++ + public final class TickThread extends Thread { + ++ public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("tuinity.strict-thread-checks"); ++ ++ static { ++ if (STRICT_THREAD_CHECKS) { ++ MinecraftServer.LOGGER.warn("Strict thread checks enabled - performance may suffer"); ++ } ++ } ++ ++ public static void softEnsureTickThread(final String reason) { ++ if (!STRICT_THREAD_CHECKS) { ++ return; ++ } ++ ensureTickThread(reason); ++ } ++ ++ ++ public static void ensureTickThread(final String reason) { ++ if (!Bukkit.isPrimaryThread()) { ++ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); ++ throw new IllegalStateException(reason); ++ } ++ } ++ + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + public TickThread(final Runnable run, final String name, final int id) { +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index af9d54ef057d5f6977cf77c57cde25b6b0d1f39d..8816d3326af25431e2235c5e735e86c7335f60dc 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -548,6 +548,7 @@ public class Chunk implements IChunkAccess { + + @Override + public void a(Entity entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Chunk add entity"); // Tuinity + this.q = true; + int i = MathHelper.floor(entity.locX() / 16.0D); + int j = MathHelper.floor(entity.locZ() / 16.0D); +@@ -617,6 +618,7 @@ public class Chunk implements IChunkAccess { + } + + public void a(Entity entity, int i) { ++ org.spigotmc.AsyncCatcher.catchOp("Chunk remove entity"); // Tuinity // Tuinity + if (i < 0) { + i = 0; + } +diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index 3c7b225edbe23dc1959002293a6f8b816287b5a8..d3588e238506ea859407b72da0d0cf291945b2ec 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -65,6 +65,7 @@ public abstract class ChunkMapDistance { + } + + protected void purgeTickets() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async purge tickets"); // Tuinity + ++this.currentTick; + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + +@@ -98,6 +99,7 @@ public abstract class ChunkMapDistance { + protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k); + + public boolean a(PlayerChunkMap playerchunkmap) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot tick ChunkMapDistance off of the main-thread");// Tuinity + //this.f.a(); // Paper - no longer used + AsyncCatcher.catchOp("DistanceManagerTick"); // Paper + this.g.a(); +@@ -370,6 +372,7 @@ public abstract class ChunkMapDistance { + } + + private ArraySetSorted> e(long i) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async tickets compute"); // Tuinity + return (ArraySetSorted) this.tickets.computeIfAbsent(i, (j) -> { + return ArraySetSorted.a(4); + }); +@@ -387,6 +390,7 @@ public abstract class ChunkMapDistance { + } + + public void a(SectionPosition sectionposition, EntityPlayer entityplayer) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player add"); // Tuinity + long i = sectionposition.r().pair(); + + ((ObjectSet) this.c.computeIfAbsent(i, (j) -> { +@@ -397,6 +401,7 @@ public abstract class ChunkMapDistance { + } + + public void b(SectionPosition sectionposition, EntityPlayer entityplayer) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player remove"); // Tuinity + long i = sectionposition.r().pair(); + ObjectSet objectset = (ObjectSet) this.c.get(i); + +@@ -446,6 +451,7 @@ public abstract class ChunkMapDistance { + + // CraftBukkit start + public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket remove"); // Tuinity + Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); + + for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 5e2168fa0958325358a9c41478d70c78accb53d1..d68f91a881e001649f20e9257a4b2a4a8fc2cb04 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -1190,6 +1190,7 @@ public class ChunkProviderServer extends IChunkProvider { + + @Override + protected boolean executeNext() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot execute chunk tasks off-main thread");// Tuinity + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task + try { + boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper +diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java +index 4efc40c01ec12b80bd7cf9d35cf0ea0df973baf7..f322dccd834ff56b99f8796309709b5b6ac01456 100644 +--- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java ++++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java +@@ -74,6 +74,7 @@ public class EntityTrackerEntry { + + public final void tick() { this.a(); } // Paper - OBFHELPER + public void a() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity + List list = this.tracker.getPassengers(); + + if (!list.equals(this.p)) { +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index fb46fdeb22a7c31c8d132bdc80d657ae82c191be..2be8c1b7974f3f6271be60872d45123ba9743672 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -200,6 +200,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Paper end - no-tick view distance + + void addPlayerToDistanceMaps(EntityPlayer player) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity + int chunkX = MCUtil.getChunkCoordinate(player.locX()); + int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); + // Note: players need to be explicitly added to distance maps before they can be updated +@@ -230,6 +231,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity + // Paper start - use distance map to optimise tracker + for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { + this.playerEntityTrackerTrackMaps[i].remove(player); +@@ -247,6 +249,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + void updateMaps(EntityPlayer player) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity + int chunkX = MCUtil.getChunkCoordinate(player.locX()); + int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); + // Note: players need to be explicitly added to distance maps before they can be updated +@@ -774,6 +777,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + @Nullable + private PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Chunk holder update"); // Tuinity + if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) { + return playerchunk; + } else { +@@ -1081,6 +1085,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + protected boolean b() { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update visibleChunks off of the main thread"); // Tuinity + if (!this.updatingChunksModified) { + return false; + } else { +@@ -1520,6 +1525,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + public void setViewDistance(int i) { // Paper - public ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity + int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32 + + if (j != this.viewDistance) { +@@ -1533,6 +1539,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + // Paper start - no-tick view distance + public final void setNoTickViewDistance(int viewDistance) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity + viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32); + + this.noTickViewDistance = viewDistance; +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 38c49d04d7bfb9c116a3b6eb87daaad2c75040cf..ab8a5c00e383ef84cb3e5acf9ac9221f672effdd 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -363,6 +363,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + @Override + public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) { ++ org.spigotmc.AsyncCatcher.catchOp("set type call"); // Tuinity + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { + // Paper start +@@ -464,6 +465,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + // CraftBukkit start - Split off from above in order to directly send client and physic updates + public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i, int j) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity + IBlockData iblockdata = newBlock; + IBlockData iblockdata1 = oldBlock; + IBlockData iblockdata2 = actualBlock; +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 603935fef6211bd4addcd3bac58ebc0729e387c3..969902a5c498114d93c3e956a01066343afca14c 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -1660,6 +1660,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + @Override + public void notify(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1, int i) { ++ org.spigotmc.AsyncCatcher.catchOp("notify call"); // Tuinity + this.getChunkProvider().flagDirty(blockposition); + VoxelShape voxelshape = iblockdata.getCollisionShape(this, blockposition); + VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition); +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index 10606ed03e88e4e467de2d5424df12466c4e7873..51e9c54cddf4b28ba3d3d892322c487774bdab70 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -10,8 +10,9 @@ public class AsyncCatcher + + public static void catchOp(String reason) + { +- if ( enabled && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity ++ if ( ( enabled || com.tuinity.tuinity.util.TickThread.STRICT_THREAD_CHECKS ) && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity + { ++ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed thread check for reason: Asynchronous " + reason, new Throwable()); // Tuinity - not all exceptions are printed + throw new IllegalStateException( "Asynchronous " + reason + "!" ); + } + } diff --git a/patches/Tuinity/patches/server/0009-Delay-chunk-unloads.patch b/patches/Tuinity/patches/server/0009-Delay-chunk-unloads.patch new file mode 100644 index 00000000..2fb8d8a2 --- /dev/null +++ b/patches/Tuinity/patches/server/0009-Delay-chunk-unloads.patch @@ -0,0 +1,260 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 25 Oct 2019 02:11:30 -0700 +Subject: [PATCH] Delay chunk unloads + +Chunk unloads are now delayed by 1s. Specifically, ticket level +reduction is delayed by 1s. This is done to allow players to +teleport and have their pets follow them, as the chunks will no longer +unload or have entity ticking status removed. + +It's also targetted to reduce performance regressions when +plugins or edge cases in code do not spam sync loads since chunks +without tickets get unloaded immediately. + +Configurable under `delay-chunkunloads-by` in config. + +This patch replaces the paper patch as the paper patch only +affects player loaded chunks, when we want to target all +loads. + +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index f10fa659680f8a574f77d260bbc52be349c244e8..7b12e7610444ff20f2c3f458887bd7d4e6715036 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -1,6 +1,7 @@ + package com.tuinity.tuinity.config; + + import com.destroystokyo.paper.util.SneakyThrow; ++import net.minecraft.server.TicketType; + import org.bukkit.Bukkit; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.file.YamlConfiguration; +@@ -123,6 +124,15 @@ public final class TuinityConfig { + tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future + }*/ + ++ public static int delayChunkUnloadsBy; ++ ++ private static void delayChunkUnloadsBy() { ++ delayChunkUnloadsBy = TuinityConfig.getInt("delay-chunkunloads-by", 5) * 20; ++ if (delayChunkUnloadsBy >= 0) { ++ TicketType.DELAYED_UNLOAD.loadPeriod = delayChunkUnloadsBy; ++ } ++ } ++ + public static final class WorldConfig { + + public final String worldName; +diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index d3588e238506ea859407b72da0d0cf291945b2ec..f1c686810fb4e9c05df45d664c93af73d17f0624 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -31,7 +31,7 @@ public abstract class ChunkMapDistance { + private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; + private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); +- private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); ++ private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.ticketLevelTracker; } // Tuinity - OBFHELPER + public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); + // Paper start use a queue, but still keep unique requirement +@@ -53,6 +53,47 @@ public abstract class ChunkMapDistance { + + PlayerChunkMap chunkMap; // Paper + ++ // Tuinity start - delay chunk unloads ++ private long nextUnloadId; // delay chunk unloads ++ private final Long2ObjectOpenHashMap> delayedChunks = new Long2ObjectOpenHashMap<>(); ++ public final void removeTickets(long chunk, TicketType type) { ++ ArraySetSorted> tickets = this.tickets.get(chunk); ++ if (tickets == null) { ++ return; ++ } ++ if (type == TicketType.DELAYED_UNLOAD) { ++ this.delayedChunks.remove(chunk); ++ } ++ boolean changed = tickets.removeIf((Ticket ticket) -> { ++ return ticket.getTicketType() == type; ++ }); ++ if (changed) { ++ this.getTicketTracker().update(chunk, getLowestTicketLevel(tickets), false); ++ } ++ } ++ ++ private final java.util.function.LongFunction> computeFuntion = (long key) -> { ++ Ticket ret = new Ticket<>(TicketType.DELAYED_UNLOAD, -1, ++ChunkMapDistance.this.nextUnloadId); ++ ret.isCached = true; ++ return ret; ++ }; ++ ++ private void computeDelayedTicketFor(long chunk, int removedLevel, ArraySetSorted> tickets) { ++ int lowestLevel = getLowestTicketLevel(tickets); ++ if (removedLevel > lowestLevel) { ++ return; ++ } ++ final Ticket ticket = this.delayedChunks.computeIfAbsent(chunk, this.computeFuntion); ++ if (ticket.getTicketLevel() != -1) { ++ // since we modify data used in sorting, we need to remove before ++ tickets.remove(ticket); ++ } ++ ticket.setCreationTick(this.currentTick); ++ ticket.setTicketLevel(removedLevel); ++ tickets.add(ticket); // re-add with new expire time and ticket level ++ } ++ // Tuinity end - delay chunk unloads ++ + protected ChunkMapDistance(Executor executor, Executor executor1) { + executor1.getClass(); + Mailbox mailbox = Mailbox.a("player ticket throttler", executor1::execute); +@@ -69,18 +110,41 @@ public abstract class ChunkMapDistance { + ++this.currentTick; + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + ++ // Tuinity start - delay chunk unloads ++ int[] tempLevel = new int[] { PlayerChunkMap.GOLDEN_TICKET + 1 }; ++ Entry>>[] entryPass = new Entry[1]; ++ java.util.function.Predicate> isExpired = (ticket) -> { // CraftBukkit - decompile error ++ // Tuinity start - delay chunk unloads ++ boolean ret = ticket.isExpired(this.currentTick); ++ if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy <= 0) { ++ return ret; ++ } ++ if (ret && ticket.getTicketType().delayUnloadViable && ticket.getTicketLevel() < tempLevel[0]) { ++ tempLevel[0] = ticket.getTicketLevel(); ++ } ++ if (ret && ticket.getTicketType() == TicketType.DELAYED_UNLOAD && ticket.isCached) { ++ this.delayedChunks.remove(entryPass[0].getLongKey(), ticket); // clean up ticket... ++ } ++ return ret; ++ }; ++ // Tuinity end - delay chunk unloads + while (objectiterator.hasNext()) { +- Entry>> entry = (Entry) objectiterator.next(); ++ Entry>> entry = (Entry) objectiterator.next(); entryPass[0] = entry; // Tuinity - only allocate lambda once + +- if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error +- return ticket.b(this.currentTick); +- })) { ++ if ((entry.getValue()).removeIf(isExpired)) { // Tuinity - move above - only allocate once ++ // Tuinity start - delay chunk unloads ++ if (tempLevel[0] < (PlayerChunkMap.GOLDEN_TICKET + 1)) { ++ this.computeDelayedTicketFor(entry.getLongKey(), tempLevel[0], entry.getValue()); ++ } ++ // Tuinity end - delay chunk unloads + this.ticketLevelTracker.update(entry.getLongKey(), getLowestTicketLevel((ArraySetSorted) entry.getValue()), false); + } + + if (((ArraySetSorted) entry.getValue()).isEmpty()) { + objectiterator.remove(); + } ++ ++ tempLevel[0] = PlayerChunkMap.GOLDEN_TICKET + 1; // Tuinity - reset + } + + } +@@ -178,27 +242,11 @@ public abstract class ChunkMapDistance { + boolean removed = false; // CraftBukkit + if (arraysetsorted.remove(ticket)) { + removed = true; // CraftBukkit +- // Paper start - delay chunk unloads for player tickets +- long delayChunkUnloadsBy = chunkMap.world.paperConfig.delayChunkUnloadsBy; +- if (ticket.getTicketType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) { +- boolean hasPlayer = false; +- for (Ticket ticket1 : arraysetsorted) { +- if (ticket1.getTicketType() == TicketType.PLAYER) { +- hasPlayer = true; +- break; +- } +- } +- PlayerChunk playerChunk = chunkMap.getUpdatingChunk(i); +- if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) { +- Ticket delayUnload = new Ticket(TicketType.DELAY_UNLOAD, 33, i); +- delayUnload.delayUnloadBy = delayChunkUnloadsBy; +- delayUnload.setCurrentTick(this.currentTick); +- arraysetsorted.remove(delayUnload); +- // refresh ticket +- arraysetsorted.add(delayUnload); +- } ++ // Tuinity start - delay chunk unloads ++ if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy > 0 && ticket.getTicketType().delayUnloadViable) { ++ this.computeDelayedTicketFor(i, ticket.getTicketLevel(), arraysetsorted); + } +- // Paper end ++ // Tuinity end - delay chunk unloads + } + + if (arraysetsorted.isEmpty()) { +diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java +index e41cb8613efc86499dfe3be36c9130ab6dc9b89e..c19ffb925a02d123da8a5c77186e6105422dccf7 100644 +--- a/src/main/java/net/minecraft/server/Ticket.java ++++ b/src/main/java/net/minecraft/server/Ticket.java +@@ -5,17 +5,17 @@ import java.util.Objects; + public final class Ticket implements Comparable> { + + private final TicketType a; +- private final int b; ++ private int b; public final void setTicketLevel(final int value) { this.b = value; } // Tuinity - remove final, add set OBFHELPER + public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER +- private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER ++ private long d; public final long getCreationTick() { return this.d; } public final void setCreationTick(final long value) { this.d = value; } // Paper - OBFHELPER // Tuinity - OBFHELPER + public int priority = 0; // Paper +- public long delayUnloadBy; // Paper ++ boolean isCached; // Tuinity - delay chunk unloads, this defends against really stupid plugins + + protected Ticket(TicketType tickettype, int i, T t0) { + this.a = tickettype; + this.b = i; + this.identifier = t0; +- this.delayUnloadBy = tickettype.loadPeriod; // Paper ++ // Tuinity - delay chunk unloads + } + + public int compareTo(Ticket ticket) { +@@ -64,8 +64,9 @@ public final class Ticket implements Comparable> { + this.d = i; + } + ++ protected final boolean isExpired(long time) { return this.b(time); } // Tuinity - OBFHELPER + protected boolean b(long i) { +- long j = delayUnloadBy; // Paper ++ long j = this.a.b(); // Tuinity - delay chunk unloads + + return j != 0L && i - this.d > j; + } +diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java +index 5c789b25f1df2eae8ea8ceb4ba977ba336fe6d5e..ab0417b3897911ba29602d696f4842bfb77cee16 100644 +--- a/src/main/java/net/minecraft/server/TicketType.java ++++ b/src/main/java/net/minecraft/server/TicketType.java +@@ -26,8 +26,18 @@ public class TicketType { + public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper + public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper + public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper +- public static final TicketType DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper ++ public static final TicketType DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads + ++ // Tuinity start - delay chunk unloads ++ boolean delayUnloadViable = true; ++ static { ++ TicketType.LIGHT.delayUnloadViable = false; ++ TicketType.PLUGIN.delayUnloadViable = false; ++ TicketType.PRIORITY.delayUnloadViable = false; ++ TicketType.URGENT.delayUnloadViable = false; ++ TicketType.DELAYED_UNLOAD.delayUnloadViable = false; ++ } ++ // Tuinity end - delay chunk unloads + public static TicketType a(String s, Comparator comparator) { + return new TicketType<>(s, comparator, 0L); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index d86c25593db7cc0a73db1c37af94ae4e41bb4e93..02b5547e53f6a2c4bdfffa96c6f70d74416a7d40 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -504,6 +504,7 @@ public class CraftWorld implements World { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + if (isChunkLoaded(x, z)) { + world.getChunkProvider().removeTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 0, Unit.INSTANCE); // Paper ++ ((ChunkMapDistance)world.getChunkProvider().playerChunkMap.chunkDistanceManager).removeTickets(ChunkCoordIntPair.pair(x, z), TicketType.DELAYED_UNLOAD); // Tuinity - delay chunk unloads - let plugins override + } + + return true; diff --git a/patches/Tuinity/patches/server/0010-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/patches/Tuinity/patches/server/0010-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch new file mode 100644 index 00000000..7cc61843 --- /dev/null +++ b/patches/Tuinity/patches/server/0010-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch @@ -0,0 +1,875 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 2 Feb 2020 02:25:10 -0800 +Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt + +Instead of trying to relocate the chunk, which is seems to never +be the correct choice, so we end up duplicating or swapping chunks, +we instead drop the current regionfile header and recalculate - +hoping that at least then we don't swap chunks, and maybe recover +them all. + +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index f51bf71c8d6eef3c054ac64765709794fcfad5ee..17a5b5b5bab4412242a18224b366674c25cdf286 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -24,6 +24,14 @@ public class ChunkRegionLoader { + + private static final Logger LOGGER = LogManager.getLogger(); + ++ // Tuinity start ++ // TODO: Check on update ++ public static long getLastWorldSaveTime(NBTTagCompound chunkData) { ++ NBTTagCompound levelData = chunkData.getCompound("Level"); ++ return levelData.getLong("LastUpdate"); ++ } ++ // Tuinity end ++ + // Paper start - guard against serializing mismatching coordinates + // TODO Note: This needs to be re-checked each update + public static ChunkCoordIntPair getChunkCoordinate(NBTTagCompound chunkData) { +@@ -401,10 +409,10 @@ public class ChunkRegionLoader { + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); + + nbttagcompound.setInt("DataVersion", SharedConstants.getGameVersion().getWorldVersion()); +- nbttagcompound.set("Level", nbttagcompound1); ++ nbttagcompound.set("Level", nbttagcompound1); // Tuinity - diff on change + nbttagcompound1.setInt("xPos", chunkcoordintpair.x); + nbttagcompound1.setInt("zPos", chunkcoordintpair.z); +- nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading ++ nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading // Tuinity - diff on change + nbttagcompound1.setLong("InhabitedTime", ichunkaccess.getInhabitedTime()); + nbttagcompound1.setString("Status", ichunkaccess.getChunkStatus().d()); + ChunkConverter chunkconverter = ichunkaccess.p(); +diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java +index 582a5695bac7d078e3022b8ee70c512c0680d992..5601088cd5024a40e8296bab979f43de924c2b62 100644 +--- a/src/main/java/net/minecraft/server/IChunkLoader.java ++++ b/src/main/java/net/minecraft/server/IChunkLoader.java +@@ -21,7 +21,7 @@ public class IChunkLoader implements AutoCloseable { + protected final RegionFileCache regionFileCache; + + public IChunkLoader(File file, DataFixer datafixer, boolean flag) { +- this.regionFileCache = new RegionFileCache(file, flag); // Paper - nuke IOWorker ++ this.regionFileCache = new RegionFileCache(file, flag, true); // Paper - nuke IOWorker // Tuinity + this.b = datafixer; + // Paper - nuke IOWorker + } +diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java +index 1751fb6934d9242e475c1a44b2a4a1ade6987766..1ffa213a819f9d39488ca3599f77e771de8081a5 100644 +--- a/src/main/java/net/minecraft/server/RegionFile.java ++++ b/src/main/java/net/minecraft/server/RegionFile.java +@@ -5,6 +5,7 @@ import java.io.BufferedInputStream; + import java.io.BufferedOutputStream; + import java.io.ByteArrayInputStream; + import java.io.ByteArrayOutputStream; ++import java.io.DataInput; + import java.io.DataInputStream; + import java.io.DataOutputStream; + import java.io.File; +@@ -29,15 +30,350 @@ public class RegionFile implements AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(); + private static final ByteBuffer c = ByteBuffer.allocateDirect(1); + private final FileChannel dataFile; +- private final java.nio.file.Path e; +- private final RegionFileCompression f; ++ private final java.nio.file.Path e; private final java.nio.file.Path getContainingDataFolder() { return this.e; } // Tuinity - OBFHELPER ++ private final RegionFileCompression f; private final RegionFileCompression getRegionFileCompression() { return this.f; } // Tuinity - OBFHELPER + private final ByteBuffer g; +- private final IntBuffer h; +- private final IntBuffer i; ++ private final IntBuffer h; private final IntBuffer getOffsets() { return this.h; } // Tuinity - OBFHELPER ++ private final IntBuffer i; private final IntBuffer getTimestamps() { return this.i; } // Tuinity - OBFHELPER + @VisibleForTesting + protected final RegionFileBitSet freeSectors; + public final File file; // Paper + ++ // Tuinity start - try to recover from RegionFile header corruption ++ private static long roundToSectors(long bytes) { ++ long sectors = bytes >>> 12; // 4096 = 2^12 ++ long remainingBytes = bytes & 4095; ++ long sign = -remainingBytes; // sign is 1 if nonzero ++ return sectors + (sign >>> 63); ++ } ++ ++ private static final NBTTagCompound OVERSIZED_COMPOUND = new NBTTagCompound(); ++ ++ private NBTTagCompound attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { ++ try { ++ if (chunkDataLength < 0) { ++ return null; ++ } ++ ++ long offset = sector * 4096L + 4L; // offset for chunk data ++ ++ if ((offset + chunkDataLength) > fileLength) { ++ return null; ++ } ++ ++ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); ++ if (chunkDataLength != this.dataFile.read(chunkData, offset)) { ++ return null; ++ } ++ ++ ((java.nio.Buffer)chunkData).flip(); ++ ++ byte compressionType = chunkData.get(); ++ if (compressionType < 0) { // compressionType & 128 != 0 ++ // oversized chunk ++ return OVERSIZED_COMPOUND; ++ } ++ ++ RegionFileCompression compression = RegionFileCompression.getByType(compressionType); ++ if (compression == null) { ++ return null; ++ } ++ ++ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); ++ ++ return NBTCompressedStreamTools.readNBT((java.io.DataInput)new DataInputStream(new BufferedInputStream(input))); ++ } catch (Exception ex) { ++ return null; ++ } ++ } ++ ++ private int getLength(long sector) throws IOException { ++ ByteBuffer length = ByteBuffer.allocate(4); ++ if (4 != this.dataFile.read(length, sector * 4096L)) { ++ return -1; ++ } ++ ++ return length.getInt(0); ++ } ++ ++ private void backupRegionFile() { ++ File backup = new File(this.file.getParent(), this.file.getName() + "." + new java.util.Random().nextLong() + ".backup"); ++ this.backupRegionFile(backup); ++ } ++ ++ private void backupRegionFile(File to) { ++ try { ++ this.dataFile.force(true); ++ MinecraftServer.LOGGER.warn("Backing up regionfile \"" + this.file.getAbsolutePath() + "\" to " + to.getAbsolutePath()); ++ java.nio.file.Files.copy(this.file.toPath(), to.toPath()); ++ MinecraftServer.LOGGER.warn("Backed up the regionfile to " + to.getAbsolutePath()); ++ } catch (IOException ex) { ++ MinecraftServer.LOGGER.error("Failed to backup to " + to.getAbsolutePath(), ex); ++ } ++ } ++ ++ // note: only call for CHUNK regionfiles ++ void recalculateHeader() throws IOException { ++ if (!this.canRecalcHeader) { ++ return; ++ } ++ synchronized (this) { ++ MinecraftServer.LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.file.getAbsolutePath(), new Throwable()); ++ ++ // try to backup file so maybe it could be sent to us for further investigation ++ ++ this.backupRegionFile(); ++ NBTTagCompound[] compounds = new NBTTagCompound[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) ++ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes ++ int[] sectorOffsets = new int[32 * 32]; // in sectors ++ boolean[] hasAikarOversized = new boolean[32 * 32]; ++ ++ long fileLength = this.dataFile.size(); ++ long totalSectors = roundToSectors(fileLength); ++ ++ // search the regionfile from start to finish for the most up-to-date chunk data ++ ++ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip ++ int chunkDataLength = this.getLength(i); ++ NBTTagCompound compound = this.attemptRead(i, chunkDataLength, fileLength); ++ if (compound == null || compound == OVERSIZED_COMPOUND) { ++ continue; ++ } ++ ++ ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(compound); ++ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); ++ ++ NBTTagCompound otherCompound = compounds[location]; ++ ++ if (otherCompound != null && ChunkRegionLoader.getLastWorldSaveTime(otherCompound) > ChunkRegionLoader.getLastWorldSaveTime(compound)) { ++ continue; // don't overwrite newer data. ++ } ++ ++ // aikar oversized? ++ File aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); ++ boolean isAikarOversized = false; ++ if (aikarOversizedFile.exists()) { ++ try { ++ NBTTagCompound aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); ++ if (ChunkRegionLoader.getLastWorldSaveTime(compound) == ChunkRegionLoader.getLastWorldSaveTime(aikarOversizedCompound)) { ++ // best we got for an id. hope it's good enough ++ isAikarOversized = true; ++ } ++ } catch (Exception ex) { ++ MinecraftServer.LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.file.getAbsolutePath() + ", oversized data for this chunk will be lost", ex); ++ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data ++ } ++ } ++ ++ hasAikarOversized[location] = isAikarOversized; ++ compounds[location] = compound; ++ rawLengths[location] = chunkDataLength + 4; ++ sectorOffsets[location] = (int)i; ++ ++ int chunkSectorLength = (int)roundToSectors(rawLengths[location]); ++ i += chunkSectorLength; ++ --i; // gets incremented next iteration ++ } ++ ++ // forge style oversized data is already handled by the local search, and aikar data we just hope ++ // we get it right as aikar data has no identifiers we could use to try and find its corresponding ++ // local data compound ++ ++ java.nio.file.Path containingFolder = this.getContainingDataFolder(); ++ File[] regionFiles = containingFolder.toFile().listFiles(); ++ boolean[] oversized = new boolean[32 * 32]; ++ RegionFileCompression[] oversizedCompressionTypes = new RegionFileCompression[32 * 32]; ++ ++ if (regionFiles != null) { ++ ChunkCoordIntPair ourLowerLeftPosition = RegionFileCache.getRegionFileCoordinates(this.file); ++ ++ if (ourLowerLeftPosition == null) { ++ MinecraftServer.LOGGER.fatal("Unable to get chunk location of regionfile " + this.file.getAbsolutePath() + ", cannot recover oversized chunks"); ++ } else { ++ int lowerXBound = ourLowerLeftPosition.x; // inclusive ++ int lowerZBound = ourLowerLeftPosition.z; // inclusive ++ int upperXBound = lowerXBound + 32 - 1; // inclusive ++ int upperZBound = lowerZBound + 32 - 1; // inclusive ++ ++ // read mojang oversized data ++ for (File regionFile : regionFiles) { ++ ChunkCoordIntPair oversizedCoords = getOversizedChunkPair(regionFile); ++ if (oversizedCoords == null) { ++ continue; ++ } ++ ++ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { ++ continue; // not in our regionfile ++ } ++ ++ // ensure oversized data is valid & is newer than data in the regionfile ++ ++ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); ++ ++ byte[] chunkData; ++ try { ++ chunkData = Files.readAllBytes(regionFile.toPath()); ++ } catch (Exception ex) { ++ MinecraftServer.LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", data will be lost", ex); ++ continue; ++ } ++ ++ NBTTagCompound compound = null; ++ ++ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them ++ RegionFileCompression compression = null; ++ for (RegionFileCompression compressionType : RegionFileCompression.getCompressionTypes().values()) { ++ try { ++ DataInputStream in = new DataInputStream(new BufferedInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData)))); // typical java ++ compound = NBTCompressedStreamTools.readNBT((DataInput)in); ++ compression = compressionType; ++ break; // reaches here iff readNBT does not throw ++ } catch (Exception ex) { ++ continue; ++ } ++ } ++ ++ if (compound == null) { ++ MinecraftServer.LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", it's corrupt. Its data will be lost"); ++ continue; ++ } ++ ++ if (compounds[location] == null || ChunkRegionLoader.getLastWorldSaveTime(compound) > ChunkRegionLoader.getLastWorldSaveTime(compounds[location])) { ++ oversized[location] = true; ++ oversizedCompressionTypes[location] = compression; ++ } ++ } ++ } ++ } ++ ++ // now we need to calculate a new offset header ++ ++ int[] calculatedOffsets = new int[32 * 32]; ++ RegionFileBitSet newSectorAllocations = new RegionFileBitSet(); ++ newSectorAllocations.allocate(0, 2); // make space for header ++ ++ // allocate sectors for normal chunks ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ if (oversized[location]) { ++ continue; ++ } ++ ++ int rawLength = rawLengths[location]; // bytes ++ int sectorOffset = sectorOffsets[location]; // sectors ++ int sectorLength = (int)roundToSectors(rawLength); ++ ++ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { ++ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized ++ } else { ++ MinecraftServer.LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath() + ", chunk will be regenerated"); ++ } ++ } ++ } ++ ++ // allocate sectors for oversized chunks ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ if (!oversized[location]) { ++ continue; ++ } ++ ++ int sectorOffset = newSectorAllocations.allocateNewSpace(1); ++ int sectorLength = 1; ++ ++ try { ++ this.dataFile.write(this.getOversizedChunkHolderData(oversizedCompressionTypes[location]), sectorOffset * 4096); ++ // only allocate in the new offsets if the write succeeds ++ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized ++ } catch (IOException ex) { ++ newSectorAllocations.free(sectorOffset, sectorLength); ++ MinecraftServer.LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath() + " will be regenerated"); ++ } ++ } ++ } ++ ++ // rewrite aikar oversized data ++ ++ this.oversizedCount = 0; ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ int isAikarOversized = hasAikarOversized[location] ? 1 : 0; ++ ++ this.oversizedCount += isAikarOversized; ++ this.oversized[location] = (byte)isAikarOversized; ++ } ++ } ++ ++ if (this.oversizedCount > 0) { ++ try { ++ this.writeOversizedMeta(); ++ } catch (Exception ex) { ++ MinecraftServer.LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.file.getAbsolutePath(), ex); ++ this.getOversizedMetaFile().delete(); ++ } ++ } else { ++ this.getOversizedMetaFile().delete(); ++ } ++ ++ this.freeSectors.copyFrom(newSectorAllocations); ++ ++ // before we overwrite the old sectors, print a summary of the chunks that got changed. ++ ++ MinecraftServer.LOGGER.info("Starting summary of changes for regionfile " + this.file.getAbsolutePath()); ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ int oldOffset = this.getOffsets().get(location); ++ int newOffset = calculatedOffsets[location]; ++ ++ if (oldOffset == newOffset) { ++ continue; ++ } ++ ++ this.getOffsets().put(location, newOffset); // overwrite incorrect offset ++ ++ if (oldOffset == 0) { ++ // found lost data ++ MinecraftServer.LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath()); ++ } else if (newOffset == 0) { ++ MinecraftServer.LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.file.getAbsolutePath() + ", it will be regenerated"); ++ } else { ++ MinecraftServer.LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.file.getAbsolutePath()); ++ } ++ } ++ } ++ ++ MinecraftServer.LOGGER.info("End of change summary for regionfile " + this.file.getAbsolutePath()); ++ ++ // simply destroy the timestamp header, it's not used ++ ++ for (int i = 0; i < 32 * 32; ++i) { ++ this.getTimestamps().put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this ++ } ++ ++ // write new header ++ try { ++ this.flushHeader(); ++ this.dataFile.force(true); // try to ensure it goes through... ++ MinecraftServer.LOGGER.info("Successfully wrote new header to disk for regionfile " + this.file.getAbsolutePath()); ++ } catch (IOException ex) { ++ MinecraftServer.LOGGER.fatal("Failed to write new header to disk for regionfile " + this.file.getAbsolutePath(), ex); ++ } ++ } ++ } ++ ++ final boolean canRecalcHeader; // final forces compile fail on new constructor ++ // Tuinity end ++ + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper + + // Paper start - Cache chunk status +@@ -65,11 +401,22 @@ public class RegionFile implements AutoCloseable { + // Paper end + + public RegionFile(File file, File file1, boolean flag) throws IOException { ++ // Tuinity start - add can recalc flag + this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag); + } ++ public RegionFile(File file, File file1, boolean flag, boolean canRecalcHeader) throws IOException { ++ this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag, canRecalcHeader); ++ // Tuinity end - add can recalc flag ++ } + + public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean flag) throws IOException { ++ // Tuinity start - add can recalc flag ++ this(java_nio_file_path, java_nio_file_path1, regionfilecompression, flag, false); ++ } ++ public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean flag, boolean canRecalcHeader) throws IOException { + this.g = ByteBuffer.allocateDirect(8192); ++ this.canRecalcHeader = canRecalcHeader; ++ // Tuinity end - add can recalc flag + this.file = java_nio_file_path.toFile(); // Paper + initOversizedState(); // Paper + this.freeSectors = new RegionFileBitSet(); +@@ -97,14 +444,16 @@ public class RegionFile implements AutoCloseable { + RegionFile.LOGGER.warn("Region file {} has truncated header: {}", java_nio_file_path, i); + } + +- long j = Files.size(java_nio_file_path); ++ final long j = Files.size(java_nio_file_path); final long regionFileSize = j; + ++ boolean needsHeaderRecalc = false; // Tuinity - recalculate header on header corruption ++ boolean hasBackedUp = false; // Tuinity - recalculate header on header corruption + for (int k = 0; k < 1024; ++k) { +- int l = this.h.get(k); ++ int l = this.h.get(k); final int headerLocation = k; // Tuinity - we expect this to be the header location + + if (l != 0) { +- int i1 = b(l); +- int j1 = a(l); ++ final int i1 = b(l); final int offset = i1; // Tuinity - we expect this to be offset in file in sectors ++ int j1 = a(l); final int sectorLength; // Tuinity - diff on change, we expect this to be sector length of region - watch out for reassignments + // Spigot start + if (j1 == 255) { + // We're maxed out, so we need to read the proper length from the section +@@ -112,33 +461,105 @@ public class RegionFile implements AutoCloseable { + this.dataFile.read(realLen, i1 * 4096); + j1 = (realLen.getInt(0) + 4) / 4096 + 1; + } ++ sectorLength = j1; // Tuinity - diff on change, we expect this to be sector length of region + // Spigot end + + if (i1 < 2) { + RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", java_nio_file_path, k, i1); +- this.h.put(k, 0); +- } else if (j1 == 0) { ++ //this.h.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change ++ } else if (j1 <= 0) { // Tuinity - <= 0, not == + RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", java_nio_file_path, k); +- this.h.put(k, 0); ++ //this.h.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change + } else if ((long) i1 * 4096L > j) { + RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", java_nio_file_path, k, i1); +- this.h.put(k, 0); ++ //this.h.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change + } else { +- this.freeSectors.a(i1, j1); ++ //this.freeSectors.a(i1, j1); // Tuinity - move this down so we can check if it fails to allocate ++ } ++ // Tuinity start - recalculate header on header corruption ++ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) { ++ if (canRecalcHeader) { ++ MinecraftServer.LOGGER.error("Detected invalid header for regionfile " + this.file.getAbsolutePath() + "! Recalculating header..."); ++ needsHeaderRecalc = true; ++ break; ++ } else { ++ // location = chunkX | (chunkZ << 5); ++ MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() + ++ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); ++ if (!hasBackedUp) { ++ hasBackedUp = true; ++ this.backupRegionFile(); ++ } ++ this.getTimestamps().put(headerLocation, 0); // be consistent, delete the timestamp too ++ this.getOffsets().put(headerLocation, 0); // delete the entry from header ++ continue; ++ } ++ } ++ boolean failedToAllocate = !this.freeSectors.tryAllocate(offset, sectorLength); ++ if (failedToAllocate) { ++ MinecraftServer.LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.file.getAbsolutePath()); + } ++ if (failedToAllocate & !canRecalcHeader) { ++ // location = chunkX | (chunkZ << 5); ++ MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() + ++ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); ++ if (!hasBackedUp) { ++ hasBackedUp = true; ++ this.backupRegionFile(); ++ } ++ this.getTimestamps().put(headerLocation, 0); // be consistent, delete the timestamp too ++ this.getOffsets().put(headerLocation, 0); // delete the entry from header ++ continue; ++ } ++ needsHeaderRecalc |= failedToAllocate; ++ // Tuinity end - recalculate header on header corruption + } + } ++ ++ // Tuinity start - recalculate header on header corruption ++ // we move the recalc here so comparison to old header is correct when logging to console ++ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues ++ MinecraftServer.LOGGER.error("Recalculating regionfile " + this.file.getAbsolutePath() + ", header gave erroneous offsets & locations"); ++ this.recalculateHeader(); ++ } ++ // Tuinity end + } + + } + } + ++ private final java.nio.file.Path getOversizedChunkPath(ChunkCoordIntPair chunkcoordintpair) { return this.e(chunkcoordintpair); } // Tuinity - OBFHELPER + private java.nio.file.Path e(ChunkCoordIntPair chunkcoordintpair) { +- String s = "c." + chunkcoordintpair.x + "." + chunkcoordintpair.z + ".mcc"; ++ String s = "c." + chunkcoordintpair.x + "." + chunkcoordintpair.z + ".mcc"; // Tuinity - diff on change + + return this.e.resolve(s); + } + ++ // Tuinity start ++ private static ChunkCoordIntPair getOversizedChunkPair(File file) { ++ String fileName = file.getName(); ++ ++ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkCoordIntPair(x, z); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ // Tuinity end ++ + @Nullable public synchronized DataInputStream getReadStream(ChunkCoordIntPair chunkCoordIntPair) throws IOException { return a(chunkCoordIntPair);} // Paper - OBFHELPER + @Nullable + public synchronized DataInputStream a(ChunkCoordIntPair chunkcoordintpair) throws IOException { +@@ -163,6 +584,12 @@ public class RegionFile implements AutoCloseable { + ((java.nio.Buffer) bytebuffer).flip(); + if (bytebuffer.remaining() < 5) { + RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", chunkcoordintpair, l, bytebuffer.remaining()); ++ // Tuinity start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ // Tuinity end + return null; + } else { + int i1 = bytebuffer.getInt(); +@@ -170,6 +597,12 @@ public class RegionFile implements AutoCloseable { + + if (i1 == 0) { + RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", chunkcoordintpair); ++ // Tuinity start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ // Tuinity end - recalculate header on regionfile corruption + return null; + } else { + int j1 = i1 - 1; +@@ -177,17 +610,49 @@ public class RegionFile implements AutoCloseable { + if (a(b0)) { + if (j1 != 0) { + RegionFile.LOGGER.warn("Chunk has both internal and external streams"); ++ // Tuinity start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ // Tuinity end - recalculate header on regionfile corruption + } + +- return this.a(chunkcoordintpair, b(b0)); ++ // Tuinity start - recalculate header on regionfile corruption ++ DataInputStream ret = this.a(chunkcoordintpair, b(b0)); ++ if (ret == null && this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ return ret; ++ // Tuinity end - recalculate header on regionfile corruption + } else if (j1 > bytebuffer.remaining()) { + RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", chunkcoordintpair, j1, bytebuffer.remaining()); ++ // Tuinity start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ // Tuinity end + return null; + } else if (j1 < 0) { + RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, chunkcoordintpair); ++ // Tuinity start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ // Tuinity end - recalculate header on regionfile corruption + return null; + } else { +- return this.a(chunkcoordintpair, b0, a(bytebuffer, j1)); ++ // Tuinity start - recalculate header on regionfile corruption ++ DataInputStream ret = this.a(chunkcoordintpair, b0, a(bytebuffer, j1)); ++ if (ret == null && this.canRecalcHeader) { ++ this.recalculateHeader(); ++ return this.getReadStream(chunkcoordintpair); ++ } ++ return ret; ++ // Tuinity end - recalculate header on regionfile corruption + } + } + } +@@ -347,10 +812,15 @@ public class RegionFile implements AutoCloseable { + } + + private ByteBuffer b() { ++ // Tuinity start - add compressionType param ++ return this.getOversizedChunkHolderData(this.getRegionFileCompression()); ++ } ++ private ByteBuffer getOversizedChunkHolderData(RegionFileCompression compressionType) { ++ // Tuinity end + ByteBuffer bytebuffer = ByteBuffer.allocate(5); + + bytebuffer.putInt(1); +- bytebuffer.put((byte) (this.f.a() | 128)); ++ bytebuffer.put((byte) (compressionType.compressionTypeId() | 128)); // Tuinity - replace with compressionType + ((java.nio.Buffer) bytebuffer).flip(); + return bytebuffer; + } +@@ -387,6 +857,7 @@ public class RegionFile implements AutoCloseable { + }; + } + ++ private final void flushHeader() throws IOException { this.b(); } // Tuinity - OBFHELPER + private void c() throws IOException { + ((java.nio.Buffer) this.g).position(0); + this.dataFile.write(this.g, 0L); +diff --git a/src/main/java/net/minecraft/server/RegionFileBitSet.java b/src/main/java/net/minecraft/server/RegionFileBitSet.java +index 1ebdf73cc927405bc536dc74a5118d2a086db0e5..cfa3ecb031b59ec677f016ecdea92d16436fb511 100644 +--- a/src/main/java/net/minecraft/server/RegionFileBitSet.java ++++ b/src/main/java/net/minecraft/server/RegionFileBitSet.java +@@ -4,18 +4,42 @@ import java.util.BitSet; + + public class RegionFileBitSet { + +- private final BitSet a = new BitSet(); ++ private final BitSet a = new BitSet(); private final BitSet getBitset() { return this.a; } // Tuinity - OBFHELPER + + public RegionFileBitSet() {} + ++ public final void allocate(int from, int length) { this.a(from, length); } // Tuinity - OBFHELPER + public void a(int i, int j) { + this.a.set(i, i + j); + } + ++ public final void free(int from, int length) { this.b(from, length); } // Tuinity - OBFHELPER + public void b(int i, int j) { + this.a.clear(i, i + j); + } + ++ // Tuinity start ++ public final void copyFrom(RegionFileBitSet other) { ++ BitSet thisBitset = this.getBitset(); ++ BitSet otherBitset = other.getBitset(); ++ ++ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { ++ thisBitset.set(i, otherBitset.get(i)); ++ } ++ } ++ ++ public final boolean tryAllocate(int from, int length) { ++ BitSet bitset = this.getBitset(); ++ int firstSet = bitset.nextSetBit(from); ++ if (firstSet > 0 && firstSet < (from + length)) { ++ return false; ++ } ++ bitset.set(from, from + length); ++ return true; ++ } ++ // Tuinity end ++ ++ public final int allocateNewSpace(final int requiredLength) { return this.a(requiredLength); } // Tuinity - OBFHELPER + public int a(int i) { + int j = 0; + +diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java +index d64f7ad925e5f40740a58ceee0845ac2db5419f2..8b341c14e7082fc96a464f2386a3dedea31ec59c 100644 +--- a/src/main/java/net/minecraft/server/RegionFileCache.java ++++ b/src/main/java/net/minecraft/server/RegionFileCache.java +@@ -15,12 +15,43 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final + public final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap(); + private final File b; + private final boolean c; ++ private final boolean isChunkData; // Tuinity + + RegionFileCache(File file, boolean flag) { ++ // Tuinity start - add isChunkData param ++ this(file, flag, false); ++ } ++ RegionFileCache(File file, boolean flag, boolean isChunkData) { ++ this.isChunkData = isChunkData; ++ // Tuinity end - add isChunkData param + this.b = file; + this.c = flag; + } + ++ // Tuinity start ++ public static ChunkCoordIntPair getRegionFileCoordinates(File file) { ++ String fileName = file.getName(); ++ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkCoordIntPair(x << 5, z << 5); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ // Tuinity end ++ + + // Paper start + public synchronized RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) { // Paper - synchronize for async io +@@ -54,9 +85,9 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final + this.b.mkdirs(); + } + +- File file = new File(this.b, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca"); ++ File file = new File(this.b, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Tuinity - diff on change + if (existingOnly && !file.exists()) return null; // CraftBukkit +- RegionFile regionfile1 = new RegionFile(file, this.b, this.c); ++ RegionFile regionfile1 = new RegionFile(file, this.b, this.c, this.isChunkData); // Tuinity - allow for chunk regionfiles to regen header + + this.cache.putAndMoveToFirst(i, regionfile1); + // Paper start +@@ -145,6 +176,13 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final + return null; + } + // CraftBukkit end ++ // Tuinity start - Add regionfile parameter ++ return this.readFromRegionFile(regionfile, chunkcoordintpair); ++ } ++ private NBTTagCompound readFromRegionFile(RegionFile regionfile, ChunkCoordIntPair chunkcoordintpair) throws IOException { ++ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile ++ // if we decide to re-read ++ // Tuinity end + try { // Paper + DataInputStream datainputstream = regionfile.a(chunkcoordintpair); + // Paper start +@@ -160,6 +198,17 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final + try { + if (datainputstream != null) { + nbttagcompound = NBTCompressedStreamTools.a((DataInput) datainputstream); ++ // Tuinity start - recover from corrupt regionfile header ++ if (this.isChunkData) { ++ ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(nbttagcompound); ++ if (!chunkPos.equals(chunkcoordintpair)) { ++ MinecraftServer.LOGGER.error("Attempting to read chunk data at " + chunkcoordintpair.toString() + " but got chunk data for " + chunkPos.toString() + " instead! Attempting regionfile recalculation for regionfile " + regionfile.file.getAbsolutePath()); ++ regionfile.recalculateHeader(); ++ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. ++ return this.readFromRegionFile(regionfile, chunkcoordintpair); ++ } ++ } ++ // Tuinity end - recover from corrupt regionfile header + return nbttagcompound; + } + +diff --git a/src/main/java/net/minecraft/server/RegionFileCompression.java b/src/main/java/net/minecraft/server/RegionFileCompression.java +index 3382d678e68e559b8d3cb9dced4fce24206cd38f..3b7894256dc8daa81be35f845cb5f8de02d7cb00 100644 +--- a/src/main/java/net/minecraft/server/RegionFileCompression.java ++++ b/src/main/java/net/minecraft/server/RegionFileCompression.java +@@ -13,7 +13,7 @@ import javax.annotation.Nullable; + + public class RegionFileCompression { + +- private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); ++ private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); static final Int2ObjectMap getCompressionTypes() { return RegionFileCompression.d; } // Tuinity - OBFHELPER + public static final RegionFileCompression a = a(new RegionFileCompression(1, GZIPInputStream::new, GZIPOutputStream::new)); + public static final RegionFileCompression b = a(new RegionFileCompression(2, InflaterInputStream::new, DeflaterOutputStream::new)); + public static final RegionFileCompression c = a(new RegionFileCompression(3, (inputstream) -> { +@@ -36,8 +36,8 @@ public class RegionFileCompression { + return regionfilecompression; + } + +- @Nullable +- public static RegionFileCompression a(int i) { ++ @Nullable public static RegionFileCompression getByType(int type) { return RegionFileCompression.a(type); } // Tuinity - OBFHELPER ++ @Nullable public static RegionFileCompression a(int i) { // Tuinity - OBFHELPER + return (RegionFileCompression) RegionFileCompression.d.get(i); + } + +@@ -45,6 +45,7 @@ public class RegionFileCompression { + return RegionFileCompression.d.containsKey(i); + } + ++ public final int compressionTypeId() { return this.a(); } // Tuinity - OBFHELPER + public int a() { + return this.e; + } +@@ -53,6 +54,7 @@ public class RegionFileCompression { + return (OutputStream) this.g.wrap(outputstream); + } + ++ public final InputStream wrap(InputStream inputstream) throws IOException { return this.a(inputstream); } // Tuinity - OBFHELPER + public InputStream a(InputStream inputstream) throws IOException { + return (InputStream) this.f.wrap(inputstream); + } +diff --git a/src/main/java/net/minecraft/server/WorldUpgrader.java b/src/main/java/net/minecraft/server/WorldUpgrader.java +index 5ccdc0b87b922724c3dd3085860c55d4959ca0b4..888dae2d5ee8a71e83dd24e5f3c6bc8513016f9d 100644 +--- a/src/main/java/net/minecraft/server/WorldUpgrader.java ++++ b/src/main/java/net/minecraft/server/WorldUpgrader.java +@@ -218,7 +218,7 @@ public class WorldUpgrader { + int l = Integer.parseInt(matcher.group(2)) << 5; + + try { +- RegionFile regionfile = new RegionFile(file2, file1, true); ++ RegionFile regionfile = new RegionFile(file2, file1, true, true); // Tuinity - allow for chunk regionfiles to regen header + Throwable throwable = null; + + try { diff --git a/patches/Tuinity/patches/server/0011-Lag-compensate-block-breaking.patch b/patches/Tuinity/patches/server/0011-Lag-compensate-block-breaking.patch new file mode 100644 index 00000000..9af804fc --- /dev/null +++ b/patches/Tuinity/patches/server/0011-Lag-compensate-block-breaking.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 14 Feb 2020 22:16:34 -0800 +Subject: [PATCH] Lag compensate block breaking + +Use time instead of ticks if ticks fall behind + +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index 7b12e7610444ff20f2c3f458887bd7d4e6715036..994c735958dc0ee0dfd8c28820fcd4f50057aad0 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -133,6 +133,12 @@ public final class TuinityConfig { + } + } + ++ public static boolean lagCompensateBlockBreaking; ++ ++ private static void lagCompensateBlockBreaking() { ++ lagCompensateBlockBreaking = TuinityConfig.getBoolean("lag-compensate-block-breaking", true); ++ } ++ + public static final class WorldConfig { + + public final String worldName; +diff --git a/src/main/java/net/minecraft/server/PlayerInteractManager.java b/src/main/java/net/minecraft/server/PlayerInteractManager.java +index 114e986e5132e5e4bb42d0f08a067429bce53ba6..05656ea8448aa569e8dd480461e2d5f70d01568b 100644 +--- a/src/main/java/net/minecraft/server/PlayerInteractManager.java ++++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java +@@ -21,14 +21,29 @@ public class PlayerInteractManager { + private EnumGamemode gamemode; + private EnumGamemode e; + private boolean f; +- private int lastDigTick; ++ private int lastDigTick; private long lastDigTime; // Tuinity - lag compensate block breaking + private BlockPosition h; + private int currentTick; +- private boolean j; ++ private boolean j; private final boolean hasDestroyedTooFast() { return this.j; } // Tuinity - OBFHELPER + private BlockPosition k; +- private int l; ++ private int l; private final int getHasDestroyedTooFastStartTick() { return this.l; } // Tuinity - OBFHELPER ++ private long hasDestroyedTooFastStartTime; // Tuinity - lag compensate block breaking + private int m; + ++ // Tuinity start - lag compensate block breaking ++ private int getTimeDiggingLagCompensate() { ++ int lagCompensated = (int)((System.nanoTime() - this.lastDigTime) / (50L * 1000L * 1000L)); ++ int tickDiff = this.currentTick - this.lastDigTick; ++ return (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to ++ } ++ ++ private int getTimeDiggingTooFastLagCompensate() { ++ int lagCompensated = (int)((System.nanoTime() - this.hasDestroyedTooFastStartTime) / (50L * 1000L * 1000L)); ++ int tickDiff = this.currentTick - this.getHasDestroyedTooFastStartTick(); ++ return (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to ++ } ++ // Tuinity end ++ + public PlayerInteractManager(WorldServer worldserver) { + this.gamemode = EnumGamemode.NOT_SET; + this.e = EnumGamemode.NOT_SET; +@@ -84,7 +99,7 @@ public class PlayerInteractManager { + if (iblockdata == null || iblockdata.isAir()) { // Paper + this.j = false; + } else { +- float f = this.a(iblockdata, this.k, this.l); ++ float f = this.updateBlockBreakAnimation(iblockdata, this.k, this.getTimeDiggingTooFastLagCompensate()); // Tuinity - lag compensate destroying blocks + + if (f >= 1.0F) { + this.j = false; +@@ -104,7 +119,7 @@ public class PlayerInteractManager { + this.m = -1; + this.f = false; + } else { +- this.a(iblockdata, this.h, this.lastDigTick); ++ this.updateBlockBreakAnimation(iblockdata, this.h, this.getTimeDiggingLagCompensate()); // Tuinity - lag compensate destroying blocks + } + } + +@@ -112,6 +127,12 @@ public class PlayerInteractManager { + + private float a(IBlockData iblockdata, BlockPosition blockposition, int i) { + int j = this.currentTick - i; ++ // Tuinity start - change i (startTime) to totalTime ++ return this.updateBlockBreakAnimation(iblockdata, blockposition, j); ++ } ++ private float updateBlockBreakAnimation(IBlockData iblockdata, BlockPosition blockposition, int totalTime) { ++ int j = totalTime; ++ // Tuinity end + float f = iblockdata.getDamage(this.player, this.player.world, blockposition) * (float) (j + 1); + int k = (int) (f * 10.0F); + +@@ -179,7 +200,7 @@ public class PlayerInteractManager { + return; + } + +- this.lastDigTick = this.currentTick; ++ this.lastDigTick = this.currentTick; this.lastDigTime = System.nanoTime(); // Tuinity - lag compensate block breaking + float f = 1.0F; + + iblockdata = this.world.getType(blockposition); +@@ -232,12 +253,12 @@ public class PlayerInteractManager { + int j = (int) (f * 10.0F); + + this.world.a(this.player.getId(), blockposition, j); +- this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "actual start of destroying")); ++ if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "actual start of destroying")); // Tuinity - on lagging servers this can cause the client to think it's only just started to destroy a block when it already has/will + this.m = j; + } + } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.STOP_DESTROY_BLOCK) { + if (blockposition.equals(this.h)) { +- int k = this.currentTick - this.lastDigTick; ++ int k = this.getTimeDiggingLagCompensate(); // Tuinity - lag compensate block breaking + + iblockdata = this.world.getType(blockposition); + if (!iblockdata.isAir()) { +@@ -254,12 +275,18 @@ public class PlayerInteractManager { + this.f = false; + this.j = true; + this.k = blockposition; +- this.l = this.lastDigTick; ++ this.l = this.lastDigTick; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Tuinity - lag compensate block breaking + } + } + } + ++ // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block ++ if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) { ++ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); ++ } else { + this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "stopped destroying")); ++ } ++ // Tuinity end - this can cause clients on a lagging server to think they're not currently destroying a block + } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.ABORT_DESTROY_BLOCK) { + this.f = false; + if (!Objects.equals(this.h, blockposition) && !BlockPosition.ZERO.equals(this.h)) { +@@ -271,7 +298,7 @@ public class PlayerInteractManager { + } + + this.world.a(this.player.getId(), blockposition, -1); +- this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "aborted destroying")); ++ if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "aborted destroying")); // Tuinity - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying + } + + } +@@ -281,7 +308,13 @@ public class PlayerInteractManager { + + public void a(BlockPosition blockposition, PacketPlayInBlockDig.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype, String s) { + if (this.breakBlock(blockposition)) { ++ // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block ++ if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) { ++ this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); ++ } else { + this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, s)); ++ } ++ // Tuinity end - this can cause clients on a lagging server to think they're not currently destroying a block + } else { + this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); // CraftBukkit - SPIGOT-5196 + } diff --git a/patches/Tuinity/patches/server/0012-Update-version-fetcher-repo.patch b/patches/Tuinity/patches/server/0012-Update-version-fetcher-repo.patch new file mode 100644 index 00000000..59960a10 --- /dev/null +++ b/patches/Tuinity/patches/server/0012-Update-version-fetcher-repo.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 19 Mar 2020 20:32:56 -0400 +Subject: [PATCH] Update version fetcher repo + +Sets the target github repo to Tuinity in the version checker. Also disables the jenkins build lookups. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +index 49a38c6608b652ff48ef4eaca0dd3ccb1ba570e3..255bbd6e48b95c70fad02ba692c64c7579496827 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -24,8 +24,8 @@ public class PaperVersionFetcher implements VersionFetcher { + @Nonnull + @Override + public String getVersionMessage(@Nonnull String serverVersion) { +- String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); +- String updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ String[] parts = serverVersion.substring("git-Tuinity-".length()).split("[-\\s]"); // Tuinity ++ String updateMessage = getUpdateStatusMessage("Spottedleaf/Tuinity", GITHUB_BRANCH_NAME, parts[0]); // Tuinity + String history = getHistory(); + + return history != null ? history + "\n" + updateMessage : updateMessage; +@@ -49,13 +49,10 @@ public class PaperVersionFetcher implements VersionFetcher { + + private static String getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { + int distance; +- try { +- int jenkinsBuild = Integer.parseInt(versionInfo); +- distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); +- } catch (NumberFormatException ignored) { ++ // Tuinity - we don't have jenkins setup + versionInfo = versionInfo.replace("\"", ""); + distance = fetchDistanceFromGitHub(repo, branch, versionInfo); +- } ++ // Tuinity - we don't have jenkins setup + + switch (distance) { + case -1: diff --git a/patches/Tuinity/patches/server/0013-Per-World-Spawn-Limits.patch b/patches/Tuinity/patches/server/0013-Per-World-Spawn-Limits.patch new file mode 100644 index 00000000..f06c0397 --- /dev/null +++ b/patches/Tuinity/patches/server/0013-Per-World-Spawn-Limits.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Chase Whipple +Date: Thu, 26 Mar 2020 21:45:54 -0600 +Subject: [PATCH] Per World Spawn Limits + + +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index 994c735958dc0ee0dfd8c28820fcd4f50057aad0..a302cda14aa2dd6550cca03b07be21cdcb993061 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -269,6 +269,23 @@ public final class TuinityConfig { + final int threads = this.getInt("tick-threads", -1); + this.threads = threads == -1 ? TuinityConfig.tickThreads : threads; + }*/ ++ ++ public int spawnLimitMonsters; ++ public int spawnLimitAnimals; ++ public int spawnLimitWaterAmbient; ++ public int spawnLimitWaterAnimals; ++ public int spawnLimitAmbient; ++ ++ private void perWorldSpawnLimit() { ++ final String path = "spawn-limits"; ++ ++ this.spawnLimitMonsters = this.getInt(path + ".monsters", -1); ++ this.spawnLimitAnimals = this.getInt(path + ".animals", -1); ++ this.spawnLimitWaterAmbient = this.getInt(path + ".water-ambient", -1); ++ this.spawnLimitWaterAnimals = this.getInt(path + ".water-animals", -1); ++ this.spawnLimitAmbient = this.getInt(path + ".ambient", -1); ++ } ++ + } + + } +\ No newline at end of file +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 02b5547e53f6a2c4bdfffa96c6f70d74416a7d40..61c0ea8ec08c08793ddffd4a7fa84c29a75185fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -341,6 +341,14 @@ public class CraftWorld implements World { + this.generator = gen; + + environment = env; ++ ++ //Tuinity start - per world spawn limits ++ monsterSpawn = world.tuinityConfig.spawnLimitMonsters; ++ animalSpawn = world.tuinityConfig.spawnLimitAnimals; ++ waterAmbientSpawn = world.tuinityConfig.spawnLimitWaterAmbient; ++ waterAnimalSpawn = world.tuinityConfig.spawnLimitWaterAnimals; ++ ambientSpawn = world.tuinityConfig.spawnLimitAmbient; ++ //Tuinity end + } + + @Override diff --git a/patches/Tuinity/patches/server/0014-Detail-more-information-in-watchdog-dumps.patch b/patches/Tuinity/patches/server/0014-Detail-more-information-in-watchdog-dumps.patch new file mode 100644 index 00000000..2af1effd --- /dev/null +++ b/patches/Tuinity/patches/server/0014-Detail-more-information-in-watchdog-dumps.patch @@ -0,0 +1,281 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 26 Mar 2020 21:59:32 -0700 +Subject: [PATCH] Detail more information in watchdog dumps + +- Dump position, world, velocity, and uuid for currently ticking entities +- Dump player name, player uuid, position, and world for packet handling + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 5c8dd000af238ea703c9f84a4621472f17955060..c9b6fc731f478976466972a4bdc5002e0d704b35 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -599,7 +599,39 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return this.onGround; + } + ++ // Tuinity start - detailed watchdog information ++ private Vec3D moveVector; ++ private double moveStartX; ++ private double moveStartY; ++ private double moveStartZ; ++ ++ public final Vec3D getMoveVector() { ++ return this.moveVector; ++ } ++ ++ public final double getMoveStartX() { ++ return this.moveStartX; ++ } ++ ++ public final double getMoveStartY() { ++ return this.moveStartY; ++ } ++ ++ public final double getMoveStartZ() { ++ return this.moveStartZ; ++ } ++ // Tuinity end - detailed watchdog information + public void move(EnumMoveType enummovetype, Vec3D vec3d) { ++ // Tuinity start - detailed watchdog information ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot move an entity off-main"); ++ synchronized (this.posLock) { ++ this.moveStartX = this.locX(); ++ this.moveStartY = this.locY(); ++ this.moveStartZ = this.locZ(); ++ this.moveVector = vec3d; ++ } ++ try { ++ // Tuinity end - detailed watchdog information + if (this.noclip) { + this.a(this.getBoundingBox().c(vec3d)); + this.recalcPosition(); +@@ -743,6 +775,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + + this.world.getMethodProfiler().exit(); + } ++ // Tuinity start - detailed watchdog information ++ } finally { ++ synchronized (this.posLock) { // Tuinity ++ this.moveVector = null; ++ } // Tuinity ++ } ++ // Tuinity end - detailed watchdog information + } + + protected BlockPosition ap() { +@@ -3316,12 +3355,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return this.locBlock; + } + ++ public final Object posLock = new Object(); // Tuinity - log detailed entity tick information ++ + public Vec3D getMot() { + return this.mot; + } + + public void setMot(Vec3D vec3d) { ++ synchronized (this.posLock) { // Tuinity + this.mot = vec3d; ++ } // Tuinity + } + + public void setMot(double d0, double d1, double d2) { +@@ -3376,7 +3419,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + // Paper end + if (this.loc.x != d0 || this.loc.y != d1 || this.loc.z != d2) { ++ synchronized (this.posLock) { // Tuinity + this.loc = new Vec3D(d0, d1, d2); ++ } // Tuinity + int i = MathHelper.floor(d0); + int j = MathHelper.floor(d1); + int k = MathHelper.floor(d2); +diff --git a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java +index 7ea293f38dedd6066601d94adbe175a31c502e1f..e698dd22607b2b2c4068c5bfb03ac53eb5bac080 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java ++++ b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java +@@ -13,10 +13,30 @@ public class PlayerConnectionUtils { + ensureMainThread(packet, t0, (IAsyncTaskHandler) worldserver.getMinecraftServer()); + } + ++ // Tuinity start - detailed watchdog information ++ private static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); ++ private static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); ++ ++ public static long getTotalProcessedPackets() { ++ return totalMainThreadPacketsProcessed.get(); ++ } ++ ++ public static java.util.List getCurrentPacketProcessors() { ++ java.util.List ret = new java.util.ArrayList<>(4); ++ for (PacketListener listener : packetProcessing) { ++ ret.add(listener); ++ } ++ ++ return ret; ++ } ++ // Tuinity end - detailed watchdog information ++ + public static void ensureMainThread(Packet packet, T t0, IAsyncTaskHandler iasynctaskhandler) throws CancelledPacketHandleException { + if (!iasynctaskhandler.isMainThread()) { + Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings + iasynctaskhandler.execute(() -> { ++ packetProcessing.push(t0); // Tuinity - detailed watchdog information ++ try { // Tuinity - detailed watchdog information + if (MinecraftServer.getServer().hasStopped() || (t0 instanceof PlayerConnection && ((PlayerConnection) t0).processedDisconnect)) return; // CraftBukkit, MC-142590 + if (t0.a().isConnected()) { + try (Timing ignored = timing.startTiming()) { // Paper - timings +@@ -40,6 +60,12 @@ public class PlayerConnectionUtils { + } else { + PlayerConnectionUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet); + } ++ // Tuinity start - detailed watchdog information ++ } finally { ++ totalMainThreadPacketsProcessed.getAndIncrement(); ++ packetProcessing.pop(); ++ } ++ // Tuinity end - detailed watchdog information + + }); + throw CancelledPacketHandleException.INSTANCE; +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 969902a5c498114d93c3e956a01066343afca14c..d43f97449d8de64c041912b36d1c2369661c1f64 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -900,7 +900,26 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + } + ++ // Tuinity start - log detailed entity tick information ++ static final java.util.concurrent.ConcurrentLinkedDeque currentlyTickingEntities = new java.util.concurrent.ConcurrentLinkedDeque<>(); ++ ++ public static List getCurrentlyTickingEntities() { ++ List ret = Lists.newArrayListWithCapacity(4); ++ ++ for (Entity entity : currentlyTickingEntities) { ++ ret.add(entity); ++ } ++ ++ return ret; ++ } ++ // Tuinity end - log detailed entity tick information ++ + public void entityJoinedWorld(Entity entity) { ++ // Tuinity start - log detailed entity tick information ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); ++ try { ++ currentlyTickingEntities.push(entity); ++ // Tuinity end - log detailed entity tick information + if (!(entity instanceof EntityHuman) && !this.getChunkProvider().a(entity)) { + this.chunkCheck(entity); + } else { +@@ -953,6 +972,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { + //} finally { timer.stopTiming(); } // Paper - timings - move up + + } ++ // Tuinity start - log detailed entity tick information ++ } finally { ++ currentlyTickingEntities.pop(); ++ } ++ // Tuinity end - log detailed entity tick information + } + + public void a(Entity entity, Entity entity1) { +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index ae8903ee1decd22e2ad6138f29fbc757b807e0a7..58d01c6f8abcd9e1792495abd08b186f9d03f834 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -65,6 +65,84 @@ public class WatchdogThread extends Thread + } + } + ++ // Tuinity start - log detailed tick information ++ private void dumpTickingInfo() { ++ Logger log = Bukkit.getServer().getLogger(); ++ ++ // ticking entities ++ for (net.minecraft.server.Entity entity : net.minecraft.server.WorldServer.getCurrentlyTickingEntities()) { ++ double posX, posY, posZ; ++ net.minecraft.server.Vec3D mot; ++ double moveStartX, moveStartY, moveStartZ; ++ net.minecraft.server.Vec3D moveVec; ++ synchronized (entity.posLock) { ++ posX = entity.locX(); ++ posY = entity.locY(); ++ posZ = entity.locZ(); ++ mot = entity.getMot(); ++ moveStartX = entity.getMoveStartX(); ++ moveStartY = entity.getMoveStartY(); ++ moveStartZ = entity.getMoveStartZ(); ++ moveVec = entity.getMoveVector(); ++ } ++ ++ String entityType = entity.getMinecraftKey().toString(); ++ java.util.UUID entityUUID = entity.getUniqueID(); ++ net.minecraft.server.World world = entity.getWorld(); ++ ++ log.log(Level.SEVERE, "Ticking entity: " + entityType); ++ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); ++ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); ++ if (moveVec != null) { ++ log.log(Level.SEVERE, "Move call information: "); ++ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); ++ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); ++ } ++ log.log(Level.SEVERE, "UUID: " + entityUUID); ++ } ++ ++ // packet processors ++ for (net.minecraft.server.PacketListener packetListener : net.minecraft.server.PlayerConnectionUtils.getCurrentPacketProcessors()) { ++ if (packetListener instanceof net.minecraft.server.PlayerConnection) { ++ net.minecraft.server.EntityPlayer player = ((net.minecraft.server.PlayerConnection)packetListener).player; ++ long totalPackets = net.minecraft.server.PlayerConnectionUtils.getTotalProcessedPackets(); ++ if (player == null) { ++ log.log(Level.SEVERE, "Handling packet for player connection (null player): " + packetListener); ++ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); ++ } else { ++ // exclude velocity, this is set client side... Paper will also warn on high velocity set too ++ double posX, posY, posZ; ++ double moveStartX, moveStartY, moveStartZ; ++ net.minecraft.server.Vec3D moveVec; ++ synchronized (player.posLock) { ++ posX = player.locX(); ++ posY = player.locY(); ++ posZ = player.locZ(); ++ moveStartX = player.getMoveStartX(); ++ moveStartY = player.getMoveStartY(); ++ moveStartZ = player.getMoveStartZ(); ++ moveVec = player.getMoveVector(); ++ } ++ ++ java.util.UUID entityUUID = player.getUniqueID(); ++ net.minecraft.server.World world = player.getWorld(); ++ ++ log.log(Level.SEVERE, "Handling packet for player '" + player.getName() + "', UUID: " + entityUUID); ++ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); ++ if (moveVec != null) { ++ log.log(Level.SEVERE, "Move call information: "); ++ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); ++ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); ++ } ++ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); ++ } ++ } else { ++ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); ++ } ++ } ++ } ++ // Tuinity end - log detailed tick information ++ + @Override + public void run() + { +@@ -121,6 +199,7 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper ++ this.dumpTickingInfo(); // Tuinity - log detailed tick information + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/Tuinity/patches/server/0015-Execute-chunk-tasks-mid-tick.patch b/patches/Tuinity/patches/server/0015-Execute-chunk-tasks-mid-tick.patch new file mode 100644 index 00000000..87c698d6 --- /dev/null +++ b/patches/Tuinity/patches/server/0015-Execute-chunk-tasks-mid-tick.patch @@ -0,0 +1,331 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 04:20:44 -0700 +Subject: [PATCH] Execute chunk tasks mid-tick + +This will help the server load chunks if tick times are high. + +diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +index a263cd7a0680e0cc3517f84308118eb32c487732..77df6888803093ad9527d276033f2ed767b39764 100644 +--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java ++++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +@@ -310,6 +310,7 @@ public final class PaperTickList extends TickListServer { // extend to avo + if (toTick.tickState == STATE_TICKING) { + toTick.tickState = STATE_TICKED; + } // else it's STATE_CANCELLED_TICK ++ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick + } else { + // re-schedule eventually + toTick.tickState = STATE_SCHEDULED; +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index d68f91a881e001649f20e9257a4b2a4a8fc2cb04..2a28502f9e2bc812370d0258d1c1d492b061b464 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -885,7 +885,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.getMethodProfiler().enter("purge"); + this.world.timings.doChunkMap.startTiming(); // Spigot + this.chunkMapDistance.purgeTickets(); +- this.world.getMinecraftServer().midTickLoadChunks(); // Paper ++ // Tuinity - replace logic + this.tickDistanceManager(); + this.world.timings.doChunkMap.stopTiming(); // Spigot + this.world.getMethodProfiler().exitEnter("chunks"); +@@ -895,7 +895,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.timings.doChunkUnload.startTiming(); // Spigot + this.world.getMethodProfiler().exitEnter("unload"); + this.playerChunkMap.unloadChunks(booleansupplier); +- this.world.getMinecraftServer().midTickLoadChunks(); // Paper ++ // Tuinity - replace logic + this.world.timings.doChunkUnload.stopTiming(); // Spigot + this.world.getMethodProfiler().exit(); + this.clearCache(); +@@ -996,7 +996,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.timings.chunkTicks.startTiming(); // Spigot // Paper + this.world.a(chunk, k); + this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper +- if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper ++ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick + } + } + } +@@ -1152,41 +1152,7 @@ public class ChunkProviderServer extends IChunkProvider { + ChunkProviderServer.this.world.getMethodProfiler().c("runTask"); + super.executeTask(runnable); + } +- +- // Paper start +- private long lastMidTickChunkTask = 0; +- public boolean pollChunkLoadTasks() { +- if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask()) { +- try { +- ChunkProviderServer.this.tickDistanceManager(); +- } finally { +- // from below: process pending Chunk loadCallback() and unloadCallback() after each run task +- playerChunkMap.callbackExecutor.run(); +- } +- return true; +- } +- return false; +- } +- public void midTickLoadChunks() { +- MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer(); +- // always try to load chunks, restrain generation/other updates only. don't count these towards tick count +- //noinspection StatementWithEmptyBody +- while (pollChunkLoadTasks()) {} +- +- if (System.nanoTime() - lastMidTickChunkTask < 200000) { +- return; +- } +- +- for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.canSleepForTick();) { +- if (this.executeNext()) { +- server.midTickChunksTasksRan++; +- lastMidTickChunkTask = System.nanoTime(); +- } else { +- break; +- } +- } +- } +- // Paper end ++ // Tuinity - replace logic + + @Override + protected boolean executeNext() { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 9def4694d5b90b108f9f67721cf62e58f1ac6536..b6f7d1c38517c8c96684913ad58b6f1e929e2d2b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -985,7 +985,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant= MAX_CHUNK_EXEC_TIME) { ++ if (!moreTasks) { ++ lastMidTickExecuteFailure = currTime; ++ } ++ ++ // note: negative values reduce the time ++ long overuse = diff - MAX_CHUNK_EXEC_TIME; ++ if (overuse >= (10L * 1000L * 1000L)) { // 10ms ++ // make sure something like a GC or dumb plugin doesn't screw us over... ++ overuse = 10L * 1000L * 1000L; // 10ms ++ } ++ ++ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; ++ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); ++ ++ lastMidTickExecute = currTime + extraSleep; ++ return; ++ } ++ } ++ } finally { ++ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); ++ } ++ } ++ // Tuinity end - execute chunk tasks mid tick ++ + private void executeModerately() { + this.executeAll(); + java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L); +@@ -1091,22 +1161,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { +- midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick ++ // Tuinity - replace logic + return !this.canOversleep(); + }); + isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); +@@ -1291,16 +1347,16 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor +@@ -558,7 +562,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + timings.scheduledBlocks.stopTiming(); // Paper + +- this.getMinecraftServer().midTickLoadChunks(); // Paper ++ // Tuinity - replace logic + gameprofilerfiller.exitEnter("raid"); + this.timings.raids.startTiming(); // Paper - timings + this.persistentRaid.a(); +@@ -567,7 +571,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + timings.doSounds.startTiming(); // Spigot + this.ak(); + timings.doSounds.stopTiming(); // Spigot +- this.getMinecraftServer().midTickLoadChunks(); // Paper ++ // Tuinity - replace logic + this.ticking = false; + gameprofilerfiller.exitEnter("entities"); + boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players +@@ -644,7 +648,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + this.afterEntityTickingTasks.clear(); + // Paper end +- this.getMinecraftServer().midTickLoadChunks(); // Paper ++ // Tuinity - replace logic + + Entity entity2; + +@@ -654,7 +658,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + timings.tickEntities.stopTiming(); // Spigot +- this.getMinecraftServer().midTickLoadChunks(); // Paper ++ // Tuinity - replace logic + this.tickBlockEntities(); + } + diff --git a/patches/Tuinity/patches/server/0016-Change-writes-to-use-NORMAL-priority-rather-than-LOW.patch b/patches/Tuinity/patches/server/0016-Change-writes-to-use-NORMAL-priority-rather-than-LOW.patch new file mode 100644 index 00000000..e3016273 --- /dev/null +++ b/patches/Tuinity/patches/server/0016-Change-writes-to-use-NORMAL-priority-rather-than-LOW.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 8 Apr 2020 03:16:48 -0700 +Subject: [PATCH] Change writes to use NORMAL priority rather than LOW + +Should limit build up of I/O tasks, or at least properly +indicate to server owners that I/O is falling behind + +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 2be8c1b7974f3f6271be60872d45123ba9743672..75fba9065ef178207e964e3e5516fa81743fc88c 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -993,7 +993,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, chunkPos.x, chunkPos.z, +- poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); ++ poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority + + if (!chunk.isNeedsSaving()) { + return; +@@ -1027,7 +1027,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + asyncSaveData = ChunkRegionLoader.getAsyncSaveData(this.world, chunk); + } + +- this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY, ++ this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, // Tuinity - use normal priority + asyncSaveData, chunk); + + chunk.setLastSaved(this.world.getTime()); +@@ -1655,7 +1655,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( + this.world, chunkcoordintpair.x, chunkcoordintpair.z, null, nbttagcompound, +- com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - writes are async, no need for priority + return; + } + super.write(chunkcoordintpair, nbttagcompound); +diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java +index 6a0f07b13eef5560dfc7c7b39618c0b825533aec..46c4e66566b7206d311653341987b9312dea3e68 100644 +--- a/src/main/java/net/minecraft/server/VillagePlace.java ++++ b/src/main/java/net/minecraft/server/VillagePlace.java +@@ -167,7 +167,7 @@ public class VillagePlace extends RegionFileSection { + data = this.getData(chunkcoordintpair); + } + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, +- chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); ++ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority + } + } + // Paper end +@@ -292,7 +292,7 @@ public class VillagePlace extends RegionFileSection { + if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave( + this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null, +- com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread()); ++ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - writes are async, no need for priority + return; + } + super.write(chunkcoordintpair, nbttagcompound); diff --git a/patches/Tuinity/patches/server/0017-Allow-controlled-flushing-for-network-manager.patch b/patches/Tuinity/patches/server/0017-Allow-controlled-flushing-for-network-manager.patch new file mode 100644 index 00000000..afa59038 --- /dev/null +++ b/patches/Tuinity/patches/server/0017-Allow-controlled-flushing-for-network-manager.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 4 Apr 2020 15:27:44 -0700 +Subject: [PATCH] Allow controlled flushing for network manager + +Only make one flush call when emptying the packet queue too + +This patch will be used to optimise out flush calls in later +patches. + +diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java +index fb1e3c705b8abee13695762cdfd0e9f1bfdb5ad8..39f19c82bbd3c17c64c8b2ed06727a95d28a4674 100644 +--- a/src/main/java/net/minecraft/server/NetworkManager.java ++++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -71,6 +71,39 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + EnumProtocol protocol; + // Paper end + ++ // Tuinity start - allow controlled flushing ++ volatile boolean canFlush = true; ++ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); ++ private int flushPacketsStart; ++ private final Object flushLock = new Object(); ++ ++ void disableAutomaticFlush() { ++ synchronized (this.flushLock) { ++ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false ++ this.canFlush = false; ++ } ++ } ++ ++ void enableAutomaticFlush() { ++ synchronized (this.flushLock) { ++ this.canFlush = true; ++ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true ++ this.flush(); // only make the flush call if we need to ++ } ++ } ++ } ++ ++ private final void flush() { ++ if (this.channel.eventLoop().inEventLoop()) { ++ this.channel.flush(); ++ } else { ++ this.channel.eventLoop().execute(() -> { ++ this.channel.flush(); ++ }); ++ } ++ } ++ // Tuinity end - allow controlled flushing ++ + public NetworkManager(EnumProtocolDirection enumprotocoldirection) { + this.h = enumprotocoldirection; + } +@@ -222,7 +255,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && + (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) + ))) { +- this.dispatchPacket(packet, genericfuturelistener); ++ this.writePacket(packet, genericfuturelistener, null); // Tuinity + return; + } + // write the packets to the queue, then flush - antixray hooks there already +@@ -248,6 +281,14 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + + private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER + private void b(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { ++ // Tuinity start - add flush parameter ++ this.writePacket(packet, genericfuturelistener, Boolean.TRUE); ++ } ++ private void writePacket(Packet packet, @Nullable GenericFutureListener> genericfuturelistener, Boolean flushConditional) { ++ this.packetWrites.getAndIncrement(); // must be befeore using canFlush ++ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); ++ final boolean flush = effectiveFlush || packet instanceof PacketPlayOutKeepAlive || packet instanceof PacketPlayOutKickDisconnect; // no delay for certain packets ++ // Tuinity end - add flush parameter + EnumProtocol enumprotocol = EnumProtocol.a(packet); + EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get(); + +@@ -270,7 +311,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + try { + // Paper end + +- ChannelFuture channelfuture = this.channel.writeAndFlush(packet); ++ ChannelFuture channelfuture = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter + + if (genericfuturelistener != null) { + channelfuture.addListener(genericfuturelistener); +@@ -302,7 +343,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + try { + // Paper end +- ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet); ++ ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter + + + if (genericfuturelistener != null) { +@@ -345,6 +386,8 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + private boolean processQueue() { + if (this.packetQueue.isEmpty()) return true; ++ final boolean needsFlush = this.canFlush; // Tuinity - make only one flush call per sendPacketQueue() call ++ boolean hasWrotePacket = false; + // If we are on main, we are safe here in that nothing else should be processing queue off main anymore + // But if we are not on main due to login/status, the parent is synchronized on packetQueue + java.util.Iterator iterator = this.packetQueue.iterator(); +@@ -352,16 +395,22 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + NetworkManager.QueuedPacket queued = iterator.next(); // poll -> peek + + // Fix NPE (Spigot bug caused by handleDisconnection()) +- if (queued == null) { ++ if (false && queued == null) { // Tuinity - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here + return true; + } + + Packet packet = queued.getPacket(); + if (!packet.isReady()) { ++ // Tuinity start - make only one flush call per sendPacketQueue() call ++ if (hasWrotePacket && (needsFlush || this.canFlush)) { ++ this.flush(); ++ } ++ // Tuinity end - make only one flush call per sendPacketQueue() call + return false; + } else { + iterator.remove(); +- this.dispatchPacket(packet, queued.getGenericFutureListener()); ++ this.writePacket(packet, queued.getGenericFutureListener(), (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Tuinity - make only one flush call per sendPacketQueue() call ++ hasWrotePacket = true; // Tuinity - make only one flush call per sendPacketQueue() call + } + } + return true; diff --git a/patches/Tuinity/patches/server/0018-Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/Tuinity/patches/server/0018-Consolidate-flush-calls-for-entity-tracker-packets.patch new file mode 100644 index 00000000..5a91b1b1 --- /dev/null +++ b/patches/Tuinity/patches/server/0018-Consolidate-flush-calls-for-entity-tracker-packets.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 4 Apr 2020 17:00:20 -0700 +Subject: [PATCH] Consolidate flush calls for entity tracker packets + +Most server packets seem to be sent from here, so try to avoid +expensive flush calls from them. + +This change was motivated due to local testing: + +- My server spawn has 130 cows in it (for testing a prev. patch) +- Try to let 200 players join spawn + +Without this change, I could only get 20 players on before they +all started timing out due to the load put on the Netty I/O threads. + +With this change I could get all 200 on at 0ms ping. + +(one of the primary issues is that my CPU is kinda trash, and having +4 extra threads at 100% is just too much for it). + +So in general this patch should reduce Netty I/O thread load. + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 2a28502f9e2bc812370d0258d1c1d492b061b464..89abe51205c7c03fdb3937d10c95d15228401b0c 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -1012,7 +1012,25 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.getMethodProfiler().exit(); + } + ++ // Tuinity start - controlled flush for entity tracker packets ++ List disabledFlushes = new java.util.ArrayList<>(this.world.getPlayers().size()); ++ for (EntityPlayer player : this.world.getPlayers()) { ++ PlayerConnection connection = player.playerConnection; ++ if (connection != null) { ++ connection.networkManager.disableAutomaticFlush(); ++ disabledFlushes.add(connection.networkManager); ++ } ++ } ++ try { ++ // Tuinity end - controlled flush for entity tracker packets + this.playerChunkMap.g(); ++ // Tuinity start - controlled flush for entity tracker packets ++ } finally { ++ for (NetworkManager networkManager : disabledFlushes) { ++ networkManager.enableAutomaticFlush(); ++ } ++ } ++ // Tuinity end - controlled flush for entity tracker packets + } + + private void a(long i, Consumer consumer) { diff --git a/patches/Tuinity/patches/server/0019-Time-scoreboard-search.patch b/patches/Tuinity/patches/server/0019-Time-scoreboard-search.patch new file mode 100644 index 00000000..4c19fcc2 --- /dev/null +++ b/patches/Tuinity/patches/server/0019-Time-scoreboard-search.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 21 Apr 2020 01:53:22 -0700 +Subject: [PATCH] Time scoreboard search + +Plugins leaking scoreboards will make this very expensive, +let server owners debug it easily + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 884b59d478aa7de49906520e77866a7949bed19d..7991c66a8fe7ee9725ab75fb80d1363cd7348532 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -43,6 +43,8 @@ public final class MinecraftTimings { + public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); + public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + ++ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Tuinity - add timings for scoreboard search ++ + private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); + + private MinecraftTimings() {} +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index 6fa2e271f7f01cd0bf247e2071fa33bd8c5c6cbe..3a9491e9495bec93d5556bd8c09196ea117161d5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +@@ -113,9 +113,18 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + // CraftBukkit method + public void getScoreboardScores(IScoreboardCriteria criteria, String name, Consumer consumer) { ++ // Tuinity start - add timings for scoreboard search ++ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily ++ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); ++ try { ++ // Tuinity end - add timings for scoreboard search + for (CraftScoreboard scoreboard : scoreboards) { + Scoreboard board = scoreboard.board; + board.getObjectivesForCriteria(criteria, name, (score) -> consumer.accept(score)); + } ++ } finally { // Tuinity start - add timings for scoreboard search ++ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); ++ } ++ // Tuinity end - add timings for scoreboard search + } + } diff --git a/patches/Tuinity/patches/server/0020-Make-CallbackExecutor-strict-again.patch b/patches/Tuinity/patches/server/0020-Make-CallbackExecutor-strict-again.patch new file mode 100644 index 00000000..eed3fa6d --- /dev/null +++ b/patches/Tuinity/patches/server/0020-Make-CallbackExecutor-strict-again.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 24 Apr 2020 09:06:15 -0700 +Subject: [PATCH] Make CallbackExecutor strict again + +The correct fix for double scheduling is to avoid it. The reason +this class is used is because double scheduling causes issues +elsewhere, and it acts as an explicit detector of what double +schedules. Effectively, use the callback executor as a tool of +finding issues rather than hiding these issues. + +This patch also reverts incorrect use(s) of the class by paper. + +- getChunkFutureAsynchronously + There is no risk at all of recursion. The future is executed on + the chunk provider's thread queue, the same place general plugin + load callbacks are executed on. Forcing the task execution into + the callback executor also prevents the future from catching + any exception thrown from it. + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 89abe51205c7c03fdb3937d10c95d15228401b0c..26db7d4f9c5e9bb5ecd7f79e263d365d98f70fd7 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -174,9 +174,9 @@ public class ChunkProviderServer extends IChunkProvider { + + try { + if (onLoad != null) { +- playerChunkMap.callbackExecutor.execute(() -> { ++ // Tuinity - revert incorrect use of callback executor + onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. +- }); ++ // Tuinity - revert incorrect use of callback executor + } + } catch (Throwable thr) { + if (thr instanceof ThreadDeath) { +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 75fba9065ef178207e964e3e5516fa81743fc88c..5c7fb3cb21478ff6a88e47d1e63c9baba4a8d5e7 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -121,31 +121,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); + public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { +- +- // Paper start - replace impl with recursive safe multi entry queue +- // it's possible to schedule multiple tasks currently, so it's vital we change this impl +- // If we recurse into the executor again, we will append to another queue, ensuring task order consistency +- private java.util.ArrayDeque queued = new java.util.ArrayDeque<>(); ++ // Tuinity start - revert paper's change ++ private Runnable queued; + + @Override + public void execute(Runnable runnable) { + AsyncCatcher.catchOp("Callback Executor execute"); +- if (queued == null) { +- queued = new java.util.ArrayDeque<>(); ++ if (queued != null) { ++ MinecraftServer.LOGGER.fatal("Failed to schedule runnable", new IllegalStateException("Already queued")); // Paper - make sure this is printed ++ throw new IllegalStateException("Already queued"); + } +- queued.add(runnable); ++ queued = runnable; + } ++ // Tuinity end - revert paper's change + + @Override + public void run() { + AsyncCatcher.catchOp("Callback Executor run"); +- if (queued == null) { +- return; +- } +- java.util.ArrayDeque queue = queued; ++ // Tuinity start - revert paper's change ++ Runnable task = queued; + queued = null; +- Runnable task; +- while ((task = queue.pollFirst()) != null) { ++ if (task != null) { ++ // Tuinity end - revert paper's change + task.run(); + } + } diff --git a/patches/Tuinity/patches/server/0021-Optimise-entity-hard-collision-checking.patch b/patches/Tuinity/patches/server/0021-Optimise-entity-hard-collision-checking.patch new file mode 100644 index 00000000..e0dfa24e --- /dev/null +++ b/patches/Tuinity/patches/server/0021-Optimise-entity-hard-collision-checking.patch @@ -0,0 +1,232 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Apr 2020 18:08:53 -0700 +Subject: [PATCH] Optimise entity hard collision checking + +Very few entities actually hard collide, so store them in their own +entity slices and provide a special getEntites type call just for them. +This reduces entity collision checking impact (in my testing) by 25% +for crammed entities (shove 130 cows into an 8x6 area in one chunk). +Less crammed entities are likely to show significantly less benefit. +Effectively, this patch optimises crammed entity situations. + +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index 8816d3326af25431e2235c5e735e86c7335f60dc..2e25102b6d7e71b0a536e77c2116981aed8623e2 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -91,6 +91,56 @@ public class Chunk implements IChunkAccess { + private final int[] inventoryEntityCounts = new int[16]; + // Paper end + ++ // Tuinity start - optimise hard collision handling ++ final com.destroystokyo.paper.util.maplist.EntityList[] hardCollidingEntities = new com.destroystokyo.paper.util.maplist.EntityList[16]; ++ ++ { ++ for (int i = 0, len = this.hardCollidingEntities.length; i < len; ++i) { ++ this.hardCollidingEntities[i] = new com.destroystokyo.paper.util.maplist.EntityList(); ++ } ++ } ++ ++ public final void getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List into, Predicate predicate) { ++ // copied from getEntities ++ int min = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); ++ int max = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); ++ ++ min = MathHelper.clamp(min, 0, this.hardCollidingEntities.length - 1); ++ max = MathHelper.clamp(max, 0, this.hardCollidingEntities.length - 1); ++ ++ for (int k = min; k <= max; ++k) { ++ com.destroystokyo.paper.util.maplist.EntityList entityList = this.hardCollidingEntities[k]; ++ Entity[] entities = entityList.getRawData(); ++ ++ for (int i = 0, len = entityList.size(); i < len; ++i) { ++ Entity entity1 = entities[i]; ++ if (entity1.shouldBeRemoved) continue; // Paper ++ ++ if (entity1 != entity && entity1.getBoundingBox().intersects(axisalignedbb)) { ++ if (predicate == null || predicate.test(entity1)) { ++ into.add(entity1); ++ } ++ ++ if (!(entity1 instanceof EntityEnderDragon)) { ++ continue; ++ } ++ ++ EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon)entity1).children; ++ int l = aentitycomplexpart.length; ++ ++ for (int i1 = 0; i1 < l; ++i1) { ++ EntityComplexPart entitycomplexpart = aentitycomplexpart[i1]; ++ ++ if (entitycomplexpart != entity && entitycomplexpart.getBoundingBox().intersects(axisalignedbb) && (predicate == null || predicate.test(entitycomplexpart))) { ++ into.add(entitycomplexpart); ++ } ++ } ++ } ++ } ++ } ++ } ++ // Tuinity end - optimise hard collision handling ++ + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { + this.sections = new ChunkSection[16]; + this.e = Maps.newHashMap(); +@@ -595,7 +645,7 @@ public class Chunk implements IChunkAccess { + entity.chunkY = k; + entity.chunkZ = this.loc.z; + this.entities.add(entity); // Paper - per chunk entity list +- this.entitySlices[k].add(entity); ++ this.entitySlices[k].add(entity); if (entity.hardCollides()) this.hardCollidingEntities[k].add(entity); // Tuinity - optimise hard colliding entities + // Paper start + if (entity instanceof EntityItem) { + itemCounts[k]++; +@@ -633,7 +683,7 @@ public class Chunk implements IChunkAccess { + entity.entitySlice = null; + entity.inChunk = false; + } +- if (!this.entitySlices[i].remove(entity)) { ++ if (entity.hardCollides()) this.hardCollidingEntities[i].remove(entity); if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities + return; + } + if (entity instanceof EntityItem) { +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index c9b6fc731f478976466972a4bdc5002e0d704b35..6548ffe332460e66409b345bae2f5222d32cec71 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -230,6 +230,41 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + // Paper end - optimise entity tracking + ++ // Tuinity start ++ /** ++ * Overriding this field will cause memory leaks. ++ */ ++ private final boolean hardCollides; ++ ++ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); ++ { ++ Boolean hardCollides = cachedOverrides.get(this.getClass()); ++ if (hardCollides == null) { ++ try { ++ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("j", Entity.class); ++ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("aZ"); ++ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) ++ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { ++ hardCollides = Boolean.TRUE; ++ } else { ++ hardCollides = Boolean.FALSE; ++ } ++ cachedOverrides.put(this.getClass(), hardCollides); ++ } ++ catch (ThreadDeath thr) { throw thr; } ++ catch (Throwable thr) { ++ // shouldn't happen, just explode ++ throw new RuntimeException(thr); ++ } ++ } ++ this.hardCollides = hardCollides.booleanValue(); ++ } ++ ++ public final boolean hardCollides() { ++ return this.hardCollides; ++ } ++ // Tuinity end ++ + public Entity(EntityTypes entitytypes, World world) { + this.id = Entity.entityCount.incrementAndGet(); + this.passengers = Lists.newArrayList(); +@@ -2002,11 +2037,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return EnumInteractionResult.PASS; + } + +- public boolean j(Entity entity) { ++ public boolean j(Entity entity) { // Tuinity - diff on change, hard colliding entities override this + return entity.aZ() && !this.isSameVehicle(entity); + } + +- public boolean aZ() { ++ public boolean aZ() {// Tuinity - diff on change, hard colliding entities override this + return false; + } + +diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java +index 2639c17b7f6100533f33124f9e49990cd303d161..b053bb74f6df174a27dbfd7b1b3e3ccbb0b26659 100644 +--- a/src/main/java/net/minecraft/server/IEntityAccess.java ++++ b/src/main/java/net/minecraft/server/IEntityAccess.java +@@ -55,16 +55,25 @@ public interface IEntityAccess { + return this.b(oclass, axisalignedbb, IEntitySelector.g); + } + ++ // Tuinity start - optimise hard collision ++ /** ++ * Not guaranteed to only return hard colliding entities ++ */ ++ default List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return this.getEntities(entity, axisalignedbb, predicate); ++ } ++ // Tuinity end - optimise hard collision ++ + default Stream c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { + if (axisalignedbb.a() < 1.0E-7D) { + return Stream.empty(); + } else { + AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D); + +- return this.getEntities(entity, axisalignedbb1, predicate.and((entity1) -> { ++ predicate = predicate.and((entity1) -> { // Tuinity - optimise entity hard collisions + boolean flag; + +- if (entity1.getBoundingBox().c(axisalignedbb1)) { ++ if (true || entity1.getBoundingBox().c(axisalignedbb1)) { // Tuinity - always true, wtf did they think this.getEntities(entity, axisalignedbb1) does? + label25: + { + if (entity == null) { +@@ -82,7 +91,7 @@ public interface IEntityAccess { + + flag = false; + return flag; +- })).stream().map(Entity::getBoundingBox).map(VoxelShapes::a); ++ }); return ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb1, predicate) : this.getHardCollidingEntities(entity, axisalignedbb1, predicate)).stream().map(Entity::getBoundingBox).map(VoxelShapes::a); // Tuinity - optimise entity hard collisions + } + } + +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index e3ed8fb7881bc00b1b2345c1682f30057878d41a..8ec6b3a1b8f5281b875cbb3cf85833ab3c208bc3 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1085,6 +1085,35 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return this.getChunkAt(i, j, ChunkStatus.FULL, false); + } + ++ // Tuinity start - optimise hard collision handling ++ @Override ++ public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return this.getHardCollidingEntities(entity, axisalignedbb, predicate, Lists.newArrayList()); ++ } ++ ++ public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate, List list) { ++ // copied from below ++ int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D); ++ int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D); ++ int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D); ++ int l = MathHelper.floor((axisalignedbb.maxZ + 2.0D) / 16.0D); ++ ++ ChunkProviderServer chunkProvider = ((WorldServer)this).getChunkProvider(); ++ ++ for (int i1 = i; i1 <= j; ++i1) { ++ for (int j1 = k; j1 <= l; ++j1) { ++ Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1); ++ ++ if (chunk != null) { ++ chunk.getHardCollidingEntities(entity, axisalignedbb, list, predicate); ++ } ++ } ++ } ++ ++ return list; ++ } ++ // Tuinity end - optimise hard collision handling ++ + @Override + public List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { + this.getMethodProfiler().c("getEntities"); diff --git a/patches/Tuinity/patches/server/0022-Improved-oversized-chunk-data-packet-handling.patch b/patches/Tuinity/patches/server/0022-Improved-oversized-chunk-data-packet-handling.patch new file mode 100644 index 00000000..e72b2638 --- /dev/null +++ b/patches/Tuinity/patches/server/0022-Improved-oversized-chunk-data-packet-handling.patch @@ -0,0 +1,158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 3 Feb 2020 20:37:44 -0800 +Subject: [PATCH] Improved oversized chunk data packet handling + +Now target all TE data, except for TE's that do not have +update packets. + +This patch relies upon the improve extra packet handling +patch, as we now use PacketPlayOutMapChunk as an extra packet. +See its patch notes for further details. + +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java +index b9276928a58d56ca9aac95d262d8555522946bd7..d5a8036b764699a70a69b7dc3d45ea6d10835c44 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java +@@ -19,7 +19,7 @@ public class PacketPlayOutMapChunk implements Packet { + @Nullable + private int[] e; + private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER +- private List g; ++ private List g; private List getTileEntityData() { return this.g; } // Tuinity - OBFHELPER + private boolean h; + + // Paper start - Async-Anti-Xray - Set the ready flag to true +@@ -31,7 +31,9 @@ public class PacketPlayOutMapChunk implements Packet { + + // Paper start + private final java.util.List extraPackets = new java.util.ArrayList<>(); +- private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); ++ private static final int TE_LIMIT = Integer.getInteger("tuinity.excessive-te-limit", 750); // Tuinity - handle oversized chunk data packets more robustly ++ private static final int TE_SPLIT_LIMIT = Math.max(4096 + 1, Integer.getInteger("tuinity.te-split-limit", 15_000)); // Tuinity - handle oversized chunk data packets more robustly ++ private boolean mustSplit; // Tuinity - handle oversized chunk data packets more robustly + + @Override + public java.util.List getExtraPackets() { +@@ -40,7 +42,7 @@ public class PacketPlayOutMapChunk implements Packet { + // Paper end + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public PacketPlayOutMapChunk(Chunk chunk, int i) { this(chunk, i, true); } // Notice for updates: Please make sure this constructor isn't used anywhere +- public PacketPlayOutMapChunk(Chunk chunk, int i, boolean modifyBlocks) { ++ public PacketPlayOutMapChunk(Chunk chunk, int i, boolean modifyBlocks) { final int chunkSectionBitSet = i; // Tuinity - handle oversized chunk data packets more robustly + ChunkPacketInfo chunkPacketInfo = modifyBlocks ? chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i) : null; + // Paper end + ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); +@@ -49,27 +51,12 @@ public class PacketPlayOutMapChunk implements Packet { + this.b = chunkcoordintpair.z; + this.h = i == 65535; + this.d = new NBTTagCompound(); +- Iterator iterator = chunk.f().iterator(); +- +- Entry entry; +- +- while (iterator.hasNext()) { +- entry = (Entry) iterator.next(); +- if (((HeightMap.Type) entry.getKey()).c()) { +- this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a())); +- } +- } +- +- if (this.h) { +- this.e = chunk.getBiomeIndex().a(); +- } +- +- this.f = new byte[this.a(chunk, i)]; +- // Paper start - Anti-Xray - Add chunk packet info +- if (chunkPacketInfo != null) { +- chunkPacketInfo.setData(this.getData()); +- } +- this.c = this.writeChunk(new PacketDataSerializer(this.j()), chunk, i, chunkPacketInfo); ++ // Tuinity - move this after the tile entity logic, we need to determine whether we're going to split ++ // Tuinity - before writing chunk block data ++ // Tuinity - note: for future maintenance, git will prefer the smallest diff, so if moving the TE code is ++ // Tuinity - a smaller diff, do that, else move the chunk writing - this makes sure the start/end is correct ++ Iterator iterator; // Tuinity - move declaration up ++ Entry entry; // Tuinity - move delcaration up + // Paper end + this.g = Lists.newArrayList(); + iterator = chunk.getTileEntities().entrySet().iterator(); +@@ -82,8 +69,16 @@ public class PacketPlayOutMapChunk implements Packet { + int j = blockposition.getY() >> 4; + + if (this.f() || (i & 1 << j) != 0) { ++ // Tuinity start - improve oversized chunk data packet handling ++ ++totalTileEntities; ++ if (totalTileEntities > TE_SPLIT_LIMIT) { ++ this.mustSplit = true; ++ this.getTileEntityData().clear(); ++ this.extraPackets.clear(); ++ break; ++ } + // Paper start - improve oversized chunk data packet handling +- if (++totalTileEntities > TE_LIMIT) { ++ if (totalTileEntities > TE_LIMIT) { // Tuinity end - improve oversized chunk data packet handling + PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket(); + if (updatePacket != null) { + this.extraPackets.add(updatePacket); +@@ -97,7 +92,42 @@ public class PacketPlayOutMapChunk implements Packet { + this.g.add(nbttagcompound); + } + } ++ // Tuinity start - moved after tile entity gathering ++ iterator = chunk.f().iterator(); // Declared earlier ++ ++ while (iterator.hasNext()) { ++ entry = (Entry) iterator.next(); ++ if (((HeightMap.Type) entry.getKey()).c()) { ++ this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a())); ++ } ++ } ++ ++ if (this.h) { ++ this.e = chunk.getBiomeIndex().a(); ++ } ++ ++ this.f = new byte[this.a(chunk, i)]; ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setData(this.getData()); ++ } ++ this.c = this.writeChunk(new PacketDataSerializer(this.j()), chunk, i, chunkPacketInfo); ++ // Tuinity end - moved after tile entity gathering + chunk.world.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks ++ // Tuinity start - improve oversized chunk data packet handling ++ if (this.mustSplit) { ++ int chunkSectionBitSetCopy = chunkSectionBitSet; ++ for (int a = 0, len = Integer.bitCount(chunkSectionBitSet); a < len; ++a) { ++ int trailingBit = com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(chunkSectionBitSetCopy); ++ int sectionIndex = Integer.numberOfTrailingZeros(trailingBit); ++ chunkSectionBitSetCopy ^= trailingBit; // move on to the next ++ ++ if (chunk.getSections()[sectionIndex] != null) { ++ this.extraPackets.add(new PacketPlayOutMapChunk(chunk, trailingBit)); ++ } ++ } ++ } ++ // Tuinity end - improve oversized chunk data packet handling + } + + // Paper start - Async-Anti-Xray - Getter and Setter for the ready flag +@@ -188,7 +218,7 @@ public class PacketPlayOutMapChunk implements Packet { + for (int l = achunksection.length; k < l; ++k) { + ChunkSection chunksection = achunksection[k]; + +- if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { ++ if ((!this.mustSplit && chunksection != Chunk.a) && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { // Tuinity - improve oversized chunk data packet handling + j |= 1 << k; + chunksection.writeChunkSection(packetdataserializer, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info + } +@@ -205,7 +235,7 @@ public class PacketPlayOutMapChunk implements Packet { + for (int l = achunksection.length; k < l; ++k) { + ChunkSection chunksection = achunksection[k]; + +- if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { ++ if ((!this.mustSplit && chunksection != Chunk.a) && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { + j += chunksection.j(); + } + } diff --git a/patches/Tuinity/patches/server/0023-Reduce-iterator-allocation-from-chunk-gen.patch b/patches/Tuinity/patches/server/0023-Reduce-iterator-allocation-from-chunk-gen.patch new file mode 100644 index 00000000..da23296f --- /dev/null +++ b/patches/Tuinity/patches/server/0023-Reduce-iterator-allocation-from-chunk-gen.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 26 Apr 2020 21:52:13 -0700 +Subject: [PATCH] Reduce iterator allocation from chunk gen + +Replace via iterating over an array + +diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java +index f6c9bdbf52d773d7aa601125b887b347163f9328..51ea295d66312c95685b9fe4ee502a029d2fff20 100644 +--- a/src/main/java/net/minecraft/server/ChunkStatus.java ++++ b/src/main/java/net/minecraft/server/ChunkStatus.java +@@ -109,7 +109,7 @@ public class ChunkStatus { + private final ChunkStatus.c w; + private final int x; + private final ChunkStatus.Type y; +- private final EnumSet z; ++ private final EnumSet z; public final HeightMap.Type[] heightMaps; // Tuinity + + private static CompletableFuture> a(ChunkStatus chunkstatus, LightEngineThreaded lightenginethreaded, IChunkAccess ichunkaccess) { + boolean flag = a(chunkstatus, ichunkaccess); +@@ -171,7 +171,7 @@ public class ChunkStatus { + this.w = chunkstatus_c; + this.x = i; + this.y = chunkstatus_type; +- this.z = enumset; ++ this.z = enumset; this.heightMaps = new java.util.ArrayList<>(this.z).toArray(new HeightMap.Type[0]); // Tuinity + this.t = chunkstatus == null ? 0 : chunkstatus.c() + 1; + } + +diff --git a/src/main/java/net/minecraft/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java +index 5b0cd414ca1949ab53b289f7159f18da07d21f14..a3ac883500eaebb353ad3108a17b5c740e384b03 100644 +--- a/src/main/java/net/minecraft/server/ProtoChunk.java ++++ b/src/main/java/net/minecraft/server/ProtoChunk.java +@@ -179,14 +179,11 @@ public class ProtoChunk implements IChunkAccess { + lightengine.a(blockposition); + } + +- EnumSet enumset = this.getChunkStatus().h(); ++ HeightMap.Type[] enumset = this.getChunkStatus().heightMaps; // Tuinity - reduce iterator creation + EnumSet enumset1 = null; +- Iterator iterator = enumset.iterator(); ++ // Tuinity - reduce iterator creation + +- HeightMap.Type heightmap_type; +- +- while (iterator.hasNext()) { +- heightmap_type = (HeightMap.Type) iterator.next(); ++ for (HeightMap.Type heightmap_type : enumset) { // Tuinity - reduce iterator creation + HeightMap heightmap = (HeightMap) this.f.get(heightmap_type); + + if (heightmap == null) { +@@ -202,10 +199,9 @@ public class ProtoChunk implements IChunkAccess { + HeightMap.a(this, enumset1); + } + +- iterator = enumset.iterator(); +- +- while (iterator.hasNext()) { +- heightmap_type = (HeightMap.Type) iterator.next(); ++ // Tuinity start - reduce iterator creation ++ for (HeightMap.Type heightmap_type : enumset) { ++ // Tuinity end - reduce iterator creation + ((HeightMap) this.f.get(heightmap_type)).a(i & 15, j, k & 15, iblockdata); + } + diff --git a/patches/Tuinity/patches/server/0024-Prevent-long-map-entry-creation-in-light-engine.patch b/patches/Tuinity/patches/server/0024-Prevent-long-map-entry-creation-in-light-engine.patch new file mode 100644 index 00000000..5b3d7d1f --- /dev/null +++ b/patches/Tuinity/patches/server/0024-Prevent-long-map-entry-creation-in-light-engine.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 27 Apr 2020 03:01:42 -0700 +Subject: [PATCH] Prevent long map entry creation in light engine + +Use fastiterator to prevent it + +diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java +index b98e60772bad7e06845b50fdc11e98c0ea775d3d..e0bbfe1422cbad811ecb43d7436380d86b0f8abc 100644 +--- a/src/main/java/net/minecraft/server/LightEngineStorage.java ++++ b/src/main/java/net/minecraft/server/LightEngineStorage.java +@@ -23,7 +23,8 @@ public abstract class LightEngineStorage> e + protected final M f; protected final M updating; // Paper - diff on change, should be "updating" + protected final LongSet g = new LongOpenHashSet(); + protected final LongSet h = new LongOpenHashSet(); LongSet dirty = h; // Paper - OBFHELPER +- protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); ++ protected final Long2ObjectOpenHashMap i_synchronized_map_real = new Long2ObjectOpenHashMap<>(); // Tuinity - store wrapped map, we need fastIterator ++ protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(this.i_synchronized_map_real); // Tuinity - store wrapped map, we need fastIterator + private final LongSet n = new LongOpenHashSet(); + private final LongSet o = new LongOpenHashSet(); + private final LongSet p = new LongOpenHashSet(); +@@ -247,7 +248,7 @@ public abstract class LightEngineStorage> e + + this.p.clear(); + this.j = false; +- ObjectIterator objectiterator = this.i.long2ObjectEntrySet().iterator(); ++ ObjectIterator objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation + + Entry entry; + long j; +@@ -284,7 +285,7 @@ public abstract class LightEngineStorage> e + } + + this.n.clear(); +- objectiterator = this.i.long2ObjectEntrySet().iterator(); ++ objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation; + + while (objectiterator.hasNext()) { + entry = (Entry) objectiterator.next(); diff --git a/patches/Tuinity/patches/server/0025-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch b/patches/Tuinity/patches/server/0025-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch new file mode 100644 index 00000000..741c9a42 --- /dev/null +++ b/patches/Tuinity/patches/server/0025-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch @@ -0,0 +1,1740 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 4 May 2020 10:06:24 -0700 +Subject: [PATCH] Highly optimise single and multi-AABB VoxelShapes and + collisions + + +diff --git a/src/main/java/com/tuinity/tuinity/util/CachedLists.java b/src/main/java/com/tuinity/tuinity/util/CachedLists.java +new file mode 100644 +index 0000000000000000000000000000000000000000..387eeb5d770ba9fe564c61df8cc92ac8b1569f61 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java +@@ -0,0 +1,53 @@ ++package com.tuinity.tuinity.util; ++ ++import net.minecraft.server.AxisAlignedBB; ++import net.minecraft.server.Entity; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.UnsafeList; ++import java.util.List; ++ ++public class CachedLists { ++ ++ static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); ++ static boolean tempCollisionListInUse; ++ ++ public static UnsafeList getTempCollisionList() { ++ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { ++ return new UnsafeList<>(16); ++ } ++ tempCollisionListInUse = true; ++ return TEMP_COLLISION_LIST; ++ } ++ ++ public static void returnTempCollisionList(List list) { ++ if (list != TEMP_COLLISION_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempCollisionListInUse = false; ++ } ++ ++ static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); ++ static boolean tempGetEntitiesListInUse; ++ ++ public static UnsafeList getTempGetEntitiesList() { ++ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { ++ return new UnsafeList<>(16); ++ } ++ tempGetEntitiesListInUse = true; ++ return TEMP_GET_ENTITIES_LIST; ++ } ++ ++ public static void returnTempGetEntitiesList(List list) { ++ if (list != TEMP_GET_ENTITIES_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempGetEntitiesListInUse = false; ++ } ++ ++ public static void reset() { ++ TEMP_COLLISION_LIST.completeReset(); ++ TEMP_GET_ENTITIES_LIST.completeReset(); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java +new file mode 100644 +index 0000000000000000000000000000000000000000..002abb3cbf0f742e685f2f043d2600de03e37a19 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java +@@ -0,0 +1,165 @@ ++package com.tuinity.tuinity.voxel; ++ ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import net.minecraft.server.AxisAlignedBB; ++import net.minecraft.server.EnumDirection; ++import net.minecraft.server.VoxelShape; ++import net.minecraft.server.VoxelShapes; ++import java.util.ArrayList; ++import java.util.List; ++ ++public final class AABBVoxelShape extends VoxelShape { ++ ++ public final AxisAlignedBB aabb; ++ ++ public AABBVoxelShape(AxisAlignedBB aabb) { ++ super(VoxelShapes.getFullUnoptimisedCube().getShape()); ++ this.aabb = aabb; ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.aabb.isEmpty(); ++ } ++ ++ @Override ++ public double b(EnumDirection.EnumAxis enumdirection_enumaxis) { // getMin ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return this.aabb.minX; ++ case 1: ++ return this.aabb.minY; ++ case 2: ++ return this.aabb.minZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public double c(EnumDirection.EnumAxis enumdirection_enumaxis) { //getMax ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return this.aabb.maxX; ++ case 1: ++ return this.aabb.maxY; ++ case 2: ++ return this.aabb.maxZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public AxisAlignedBB getBoundingBox() { // rets bounding box enclosing this entire shape ++ return this.aabb; ++ } ++ ++ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis. ++ @Override ++ protected double a(EnumDirection.EnumAxis enumdirection_enumaxis, int i) { // getPointFromIndex ++ switch (enumdirection_enumaxis.ordinal() | (i << 2)) { ++ case (0 | (0 << 2)): ++ return this.aabb.minX; ++ case (1 | (0 << 2)): ++ return this.aabb.minY; ++ case (2 | (0 << 2)): ++ return this.aabb.minZ; ++ case (0 | (1 << 2)): ++ return this.aabb.maxX; ++ case (1 | (1 << 2)): ++ return this.aabb.maxY; ++ case (2 | (1 << 2)): ++ return this.aabb.maxZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ private DoubleList cachedListX; ++ private DoubleList cachedListY; ++ private DoubleList cachedListZ; ++ ++ @Override ++ protected DoubleList a(EnumDirection.EnumAxis enumdirection_enumaxis) { // getPoints ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX; ++ case 1: ++ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY; ++ case 2: ++ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public VoxelShape a(double d0, double d1, double d2) { // createOffset ++ return new AABBVoxelShape(this.aabb.offset(d0, d1, d2)); ++ } ++ ++ @Override ++ public VoxelShape c() { // simplify ++ return this; ++ } ++ ++ @Override ++ public void b(VoxelShapes.a voxelshapes_a) { // forEachAABB ++ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ); ++ } ++ ++ @Override ++ public List d() { // getAABBs ++ List ret = new ArrayList<>(1); ++ ret.add(this.aabb); ++ return ret; ++ } ++ ++ @Override ++ protected int a(EnumDirection.EnumAxis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1; ++ case 1: ++ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1; ++ case 2: ++ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ protected boolean b(double d0, double d1, double d2) { // containsPoint ++ return this.aabb.contains(d0, d1, d2); ++ } ++ ++ @Override ++ public VoxelShape a(EnumDirection enumdirection) { // unknown ++ return super.a(enumdirection); ++ } ++ ++ @Override ++ public double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, double d0) { // collide ++ if (this.aabb.isEmpty() || axisalignedbb.isEmpty()) { ++ return d0; ++ } ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return AxisAlignedBB.collideX(this.aabb, axisalignedbb, d0); ++ case 1: ++ return AxisAlignedBB.collideY(this.aabb, axisalignedbb, d0); ++ case 2: ++ return AxisAlignedBB.collideZ(this.aabb, axisalignedbb, d0); ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public boolean intersects(AxisAlignedBB axisalingedbb) { ++ return this.aabb.voxelShapeIntersect(axisalingedbb); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java +index ed9b2f9adfecdc6d1b9925579ec510657adde11f..5c3d5b22b833d9f835e17803295b87893fd05e62 100644 +--- a/src/main/java/net/minecraft/server/AxisAlignedBB.java ++++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java +@@ -13,6 +13,157 @@ public class AxisAlignedBB { + public final double maxY; + public final double maxZ; + ++ // Tuinity start ++ public final boolean isEmpty() { ++ return (this.maxX - this.minX) < MCUtil.COLLISION_EPSILON && (this.maxY - this.minY) < MCUtil.COLLISION_EPSILON && (this.maxZ - this.minZ) < MCUtil.COLLISION_EPSILON; ++ } ++ ++ public static AxisAlignedBB getBoxForChunk(int chunkX, int chunkZ) { ++ double x = (double)(chunkX << 4); ++ double z = (double)(chunkZ << 4); ++ // use a bounding box bigger than the chunk to prevent entities from entering it on move ++ return new AxisAlignedBB(x - 3*MCUtil.COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*MCUtil.COLLISION_EPSILON, x + (16.0 + 3*MCUtil.COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*MCUtil.COLLISION_EPSILON), false); ++ } ++ ++ /* ++ A couple of rules for VoxelShape collisions: ++ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement ++ checks. ++ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite ++ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code ++ will automatically round it to 0. ++ */ ++ ++ public final boolean voxelShapeIntersect(AxisAlignedBB other) { ++ return (this.minX - other.maxX) < -MCUtil.COLLISION_EPSILON && (this.maxX - other.minX) > MCUtil.COLLISION_EPSILON && ++ (this.minY - other.maxY) < -MCUtil.COLLISION_EPSILON && (this.maxY - other.minY) > MCUtil.COLLISION_EPSILON && ++ (this.minZ - other.maxZ) < -MCUtil.COLLISION_EPSILON && (this.maxZ - other.minZ) > MCUtil.COLLISION_EPSILON; ++ } ++ ++ public final boolean voxelShapeIntersect(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { ++ return (this.minX - maxX) < -MCUtil.COLLISION_EPSILON && (this.maxX - minX) > MCUtil.COLLISION_EPSILON && ++ (this.minY - maxY) < -MCUtil.COLLISION_EPSILON && (this.maxY - minY) > MCUtil.COLLISION_EPSILON && ++ (this.minZ - maxZ) < -MCUtil.COLLISION_EPSILON && (this.maxZ - minZ) > MCUtil.COLLISION_EPSILON; ++ } ++ ++ public static boolean voxelShapeIntersect(double minX1, double minY1, double minZ1, double maxX1, double maxY1, double maxZ1, ++ double minX2, double minY2, double minZ2, double maxX2, double maxY2, double maxZ2) { ++ return (minX1 - maxX2) < -MCUtil.COLLISION_EPSILON && (maxX1 - minX2) > MCUtil.COLLISION_EPSILON && ++ (minY1 - maxY2) < -MCUtil.COLLISION_EPSILON && (maxY1 - minY2) > MCUtil.COLLISION_EPSILON && ++ (minZ1 - maxZ2) < -MCUtil.COLLISION_EPSILON && (maxZ1 - minZ2) > MCUtil.COLLISION_EPSILON; ++ } ++ ++ public static double collideX(AxisAlignedBB target, AxisAlignedBB source, double source_move) { ++ if (source_move == 0.0) { ++ return 0.0; ++ } ++ ++ if ((source.minY - target.maxY) < -MCUtil.COLLISION_EPSILON && (source.maxY - target.minY) > MCUtil.COLLISION_EPSILON && ++ (source.minZ - target.maxZ) < -MCUtil.COLLISION_EPSILON && (source.maxZ - target.minZ) > MCUtil.COLLISION_EPSILON) { ++ ++ if (source_move >= 0.0) { ++ double max_move = target.minX - source.maxX; // < 0.0 if no strict collision ++ if (max_move < -MCUtil.COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ double max_move = target.maxX - source.minX; // > 0.0 if no strict collision ++ if (max_move > MCUtil.COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ public static double collideY(AxisAlignedBB target, AxisAlignedBB source, double source_move) { ++ if (source_move == 0.0) { ++ return 0.0; ++ } ++ ++ if ((source.minX - target.maxX) < -MCUtil.COLLISION_EPSILON && (source.maxX - target.minX) > MCUtil.COLLISION_EPSILON && ++ (source.minZ - target.maxZ) < -MCUtil.COLLISION_EPSILON && (source.maxZ - target.minZ) > MCUtil.COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ double max_move = target.minY - source.maxY; // < 0.0 if no strict collision ++ if (max_move < -MCUtil.COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ double max_move = target.maxY - source.minY; // > 0.0 if no strict collision ++ if (max_move > MCUtil.COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ public static double collideZ(AxisAlignedBB target, AxisAlignedBB source, double source_move) { ++ if (source_move == 0.0) { ++ return 0.0; ++ } ++ ++ if ((source.minX - target.maxX) < -MCUtil.COLLISION_EPSILON && (source.maxX - target.minX) > MCUtil.COLLISION_EPSILON && ++ (source.minY - target.maxY) < -MCUtil.COLLISION_EPSILON && (source.maxY - target.minY) > MCUtil.COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision ++ if (max_move < -MCUtil.COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision ++ if (max_move > MCUtil.COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ public final AxisAlignedBB offsetX(double dx) { ++ return new AxisAlignedBB(this.minX + dx, this.minY, this.minZ, this.maxX + dx, this.maxY, this.maxZ, false); ++ } ++ ++ public final AxisAlignedBB offsetY(double dy) { ++ return new AxisAlignedBB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); ++ } ++ ++ public final AxisAlignedBB offsetZ(double dz) { ++ return new AxisAlignedBB(this.minX, this.minY, this.minZ + dz, this.maxX, this.maxY, this.maxZ + dz, false); ++ } ++ ++ public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5, boolean dummy) { ++ this.minX = d0; ++ this.minY = d1; ++ this.minZ = d2; ++ this.maxX = d3; ++ this.maxY = d4; ++ this.maxZ = d5; ++ } ++ ++ public final AxisAlignedBB expandUpwards(double dy) { ++ return new AxisAlignedBB(this.minX, this.minY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); ++ } ++ ++ public final AxisAlignedBB cutUpwards(final double dy) { // dy > 0.0 ++ return new AxisAlignedBB(this.minX, this.maxY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); ++ } ++ ++ public final AxisAlignedBB cutDownwards(final double dy) { // dy < 0.0 ++ return new AxisAlignedBB(this.minX, this.minY + dy, this.minZ, this.maxX, this.minY, this.maxZ, false); ++ } ++ ++ public final AxisAlignedBB expandUpwardsAndCutBelow(double dy) { ++ return new AxisAlignedBB(this.minX, this.maxY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); ++ } ++ // Tuinity end ++ + public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5) { + this.minX = Math.min(d0, d3); + this.minY = Math.min(d1, d4); +@@ -185,6 +336,7 @@ public class AxisAlignedBB { + return new AxisAlignedBB(d0, d1, d2, d3, d4, d5); + } + ++ public final AxisAlignedBB offset(double d0, double d1, double d2) { return this.d(d0, d1, d2); } // Tuinity - OBFHELPER + public AxisAlignedBB d(double d0, double d1, double d2) { + return new AxisAlignedBB(this.minX + d0, this.minY + d1, this.minZ + d2, this.maxX + d0, this.maxY + d1, this.maxZ + d2); + } +@@ -193,6 +345,7 @@ public class AxisAlignedBB { + return new AxisAlignedBB(this.minX + (double) blockposition.getX(), this.minY + (double) blockposition.getY(), this.minZ + (double) blockposition.getZ(), this.maxX + (double) blockposition.getX(), this.maxY + (double) blockposition.getY(), this.maxZ + (double) blockposition.getZ()); + } + ++ public final AxisAlignedBB offset(Vec3D vec3d) { return this.b(vec3d); } // Tuinity - OBFHELPER + public AxisAlignedBB c(Vec3D vec3d) { + return this.d(vec3d.x, vec3d.y, vec3d.z); + } +@@ -212,6 +365,7 @@ public class AxisAlignedBB { + return this.e(vec3d.x, vec3d.y, vec3d.z); + } + ++ public final boolean contains(double d0, double d1, double d2) { return this.e(d0, d1, d2); } // Tuinity - OBFHELPER + public boolean e(double d0, double d1, double d2) { + return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ; + } +diff --git a/src/main/java/net/minecraft/server/ChunkCache.java b/src/main/java/net/minecraft/server/ChunkCache.java +index 8eecdcde510661ec3a13a25a04ba394f6b6dc012..ab1085091fefea3a3fa15f7028bec050d00a6f5e 100644 +--- a/src/main/java/net/minecraft/server/ChunkCache.java ++++ b/src/main/java/net/minecraft/server/ChunkCache.java +@@ -1,5 +1,6 @@ + package net.minecraft.server; + ++import java.util.List; + import java.util.function.Predicate; + import java.util.stream.Stream; + import javax.annotation.Nullable; +@@ -12,6 +13,156 @@ public class ChunkCache implements IBlockAccess, ICollisionAccess { + protected boolean d; + protected final World e; protected final World getWorld() { return e; } // Paper - OBFHELPER + ++ // Tuinity start - optimise pathfinder collision detection ++ @Override ++ public boolean getCubes(Entity entity) { ++ return !this.getCollisionsForBlocksOrWorldBorder(entity, entity.getBoundingBox(), null, true, null); ++ } ++ ++ @Override ++ public boolean getCubes(Entity entity, AxisAlignedBB axisalignedbb) { ++ return !this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, true, null); ++ } ++ ++ @Override ++ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ return !this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, true, null); ++ } ++ ++ public boolean getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, ++ boolean collidesWithUnloaded, ++ java.util.function.BiPredicate predicate) { ++ boolean ret = false; ++ final boolean checkOnly = true; ++ ++ if (entity != null) { ++ if (this.getWorldBorder().isAlmostCollidingOnBorder(axisalignedbb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list); ++ ret = true; ++ } ++ } ++ } ++ ++ int minBlockX = MathHelper.floor(axisalignedbb.minX - MCUtil.COLLISION_EPSILON) - 1; ++ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1; ++ ++ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1; ++ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1; ++ ++ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1; ++ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 1; ++ ++ ++ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); ++ VoxelShapeCollision collisionShape = null; ++ ++ // special cases: ++ if (minBlockY > 255 || maxBlockY < 0) { ++ // no point in checking ++ return ret; ++ } ++ ++ int minYIterate = Math.max(0, minBlockY); ++ int maxYIterate = Math.min(255, maxBlockY); ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ // TODO special case single chunk? ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk ++ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk ++ ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk ++ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk ++ ++ int chunkXGlobalPos = currChunkX << 4; ++ int chunkZGlobalPos = currChunkZ << 4; ++ Chunk chunk = (Chunk)this.getChunkIfLoaded(currChunkX, currChunkZ); ++ ++ if (chunk == null) { ++ if (collidesWithUnloaded) { ++ if (checkOnly) { ++ return true; ++ } else { ++ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ ++ ChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ ChunkSection section = sections[currY >>> 4]; ++ if (section == null || section.isFullOfAir()) { ++ // empty ++ // skip to next section ++ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one ++ continue; ++ } ++ ++ DataPaletteBlock blocks = section.blockIds; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8); ++ int blockX = currX | chunkXGlobalPos; ++ int blockY = currY; ++ int blockZ = currZ | chunkZGlobalPos; ++ ++ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ IBlockData blockData = blocks.rawGet(localBlockIndex); ++ ++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { ++ mutablePos.setValues(blockX, blockY, blockZ); ++ if (collisionShape == null) { ++ collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); ++ } ++ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); ++ if (voxelshape2 != VoxelShapes.getEmptyShape()) { ++ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)blockY, (double)blockZ); ++ ++ if (predicate != null && !predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ ++ if (checkOnly) { ++ if (voxelshape3.intersects(axisalignedbb)) { ++ return true; ++ } ++ } else { ++ ret |= VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ // Tuinity end - optimise pathfinder collision detection ++ + public ChunkCache(World world, BlockPosition blockposition, BlockPosition blockposition1) { + this.e = world; + this.a = blockposition.getX() >> 4; +diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java +index e52df8096e399c84ff8a2637fdd65ea57d9001d0..cebd808e273dbdb88feb16920dd7a2f60390b34f 100644 +--- a/src/main/java/net/minecraft/server/ChunkSection.java ++++ b/src/main/java/net/minecraft/server/ChunkSection.java +@@ -96,6 +96,7 @@ public class ChunkSection { + return iblockdata1; + } + ++ public final boolean isFullOfAir() { return this.c(); } // Tuinity - OBFHELPER + public boolean c() { + return this.nonEmptyBlockCount == 0; + } +diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java +index 95ef96286855624590b72d69514b0fc0e08fddba..73163b417af7e522a4509bf9c1ab56d6499be622 100644 +--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java ++++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java +@@ -163,6 +163,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { + return this.a(j << 8 | k << 4 | i); // Paper - inline + } + ++ public final T rawGet(int index) { return this.a(index); } // Tuinity - OBFHELPER + protected T a(int i) { + T t0 = this.h.a(this.a.a(i)); + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 6548ffe332460e66409b345bae2f5222d32cec71..9287ab8e861a97fc4b132e46163b050a9ae52ced 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -136,7 +136,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + public double D; + public double E; + public double F; +- public float G; ++ public float G; public final float getStepHeight() { return this.G; } // Tuinity - OBFHELPER + public boolean noclip; + public float I; + protected final Random random; +@@ -694,7 +694,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + // Paper end + + vec3d = this.a(vec3d, enummovetype); +- Vec3D vec3d1 = this.g(vec3d); ++ Vec3D vec3d1 = this.performCollision(vec3d); // Tuinity - optimise collisions + + if (vec3d1.g() > 1.0E-7D) { + this.a(this.getBoundingBox().c(vec3d1)); +@@ -785,7 +785,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + + try { +- this.checkBlockCollisions(); ++ this.checkBlockCollisions(this.fireTicks <= 0); // Tuinity - move fire checking into method here + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Checking entity block collision"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being checked for collision"); +@@ -797,11 +797,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + float f2 = this.getBlockSpeedFactor(); + + this.setMot(this.getMot().d((double) f2, 1.0D, (double) f2)); +- if (this.world.c(this.getBoundingBox().shrink(0.001D)).noneMatch((iblockdata1) -> { +- return iblockdata1.a((Tag) TagsBlock.FIRE) || iblockdata1.a(Blocks.LAVA); +- }) && this.fireTicks <= 0) { +- this.setFireTicks(-this.getMaxFireTicks()); +- } ++ // Tuinity - move into checkBlockCollisions + + if (this.aG() && this.isBurning()) { + this.playSound(SoundEffects.ENTITY_GENERIC_EXTINGUISH_FIRE, 0.7F, 1.6F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F); +@@ -897,6 +893,137 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return d0; + } + ++ // Tuinity start - optimise entity movement ++ private static double performCollisionsX(AxisAlignedBB currentBoundingBox, double value, List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ AxisAlignedBB target = potentialCollisions.get(i); ++ value = AxisAlignedBB.collideX(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ private static double performCollisionsY(AxisAlignedBB currentBoundingBox, double value, List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ AxisAlignedBB target = potentialCollisions.get(i); ++ value = AxisAlignedBB.collideY(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ private static double performCollisionsZ(AxisAlignedBB currentBoundingBox, double value, List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ AxisAlignedBB target = potentialCollisions.get(i); ++ value = AxisAlignedBB.collideZ(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ private static Vec3D performCollisions(Vec3D moveVector, AxisAlignedBB axisalignedbb, List potentialCollisions) { ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = Entity.performCollisionsY(axisalignedbb, y, potentialCollisions); ++ if (y != 0.0) { ++ axisalignedbb = axisalignedbb.offsetY(y); ++ } ++ } ++ ++ boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (xSmaller && z != 0.0) { ++ z = Entity.performCollisionsZ(axisalignedbb, z, potentialCollisions); ++ if (z != 0.0) { ++ axisalignedbb = axisalignedbb.offsetZ(z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = Entity.performCollisionsX(axisalignedbb, x, potentialCollisions); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = axisalignedbb.offsetX(x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = Entity.performCollisionsZ(axisalignedbb, z, potentialCollisions); ++ } ++ ++ return new Vec3D(x, y, z); ++ } ++ ++ Vec3D performCollision(Vec3D moveVector) { ++ if (moveVector.getX() == 0.0 && moveVector.getY() == 0.0 && moveVector.getZ() == 0.0) { ++ return moveVector; ++ } ++ ++ WorldServer world = ((WorldServer)this.world); ++ AxisAlignedBB currBoundingBox = this.getBoundingBox(); ++ ++ List potentialCollisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList(); ++ try { ++ AxisAlignedBB collisionBox; ++ double stepHeight = (double)this.getStepHeight(); ++ ++ if (moveVector.x == 0.0 && moveVector.z == 0.0 && moveVector.y != 0.0) { ++ // a lot of entities just stand still. optimise the search AABB ++ if (moveVector.y > 0.0) { ++ collisionBox = currBoundingBox.cutUpwards(moveVector.y); ++ } else { ++ collisionBox = currBoundingBox.cutDownwards(moveVector.y); ++ } ++ } else { ++ if (stepHeight > 0.0 && (this.onGround || (moveVector.y < 0.0)) && (moveVector.x != 0.0 || moveVector.z != 0.0)) { ++ // don't bother getting the collisions if we don't need them. ++ if (moveVector.y <= 0.0) { ++ collisionBox = currBoundingBox.expand(moveVector.x, moveVector.y, moveVector.z).expandUpwards(stepHeight); ++ } else { ++ collisionBox = currBoundingBox.expand(moveVector.x, Math.max(stepHeight, moveVector.y), moveVector.z); ++ } ++ } else { ++ collisionBox = currBoundingBox.expand(moveVector.x, moveVector.y, moveVector.z); ++ } ++ } ++ ++ world.getCollisions(this, collisionBox, potentialCollisions, this instanceof EntityPlayer && !this.world.paperConfig.preventMovingIntoUnloadedChunks); ++ if (world.getWorldBorder().isCollidingWithBorderEdge(collisionBox)) { ++ VoxelShapes.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions); ++ } ++ ++ Vec3D limitedMoveVector = Entity.performCollisions(moveVector, currBoundingBox, potentialCollisions); ++ ++ if (stepHeight > 0.0 ++ && (this.onGround || (limitedMoveVector.y != moveVector.y && moveVector.y < 0.0)) ++ && (limitedMoveVector.x != moveVector.x || limitedMoveVector.z != moveVector.z)) { ++ Vec3D vec3d2 = Entity.performCollisions(new Vec3D(moveVector.x, stepHeight, moveVector.z), currBoundingBox, potentialCollisions); ++ Vec3D vec3d3 = Entity.performCollisions(new Vec3D(0.0, stepHeight, 0.0), currBoundingBox.expand(moveVector.x, 0.0, moveVector.z), potentialCollisions); ++ ++ if (vec3d3.y < stepHeight) { ++ Vec3D vec3d4 = Entity.performCollisions(new Vec3D(moveVector.x, 0.0D, moveVector.z), currBoundingBox.offset(vec3d3), potentialCollisions).add(vec3d3); ++ ++ if (Entity.getXZSquared(vec3d4) > Entity.getXZSquared(vec3d2)) { ++ vec3d2 = vec3d4; ++ } ++ } ++ ++ if (Entity.getXZSquared(vec3d2) > Entity.getXZSquared(limitedMoveVector)) { ++ return vec3d2.add(Entity.performCollisions(new Vec3D(0.0D, -vec3d2.y + moveVector.y, 0.0D), currBoundingBox.offset(vec3d2), potentialCollisions)); ++ } ++ ++ return limitedMoveVector; ++ } else { ++ return limitedMoveVector; ++ } ++ } finally { ++ com.tuinity.tuinity.util.CachedLists.returnTempCollisionList(potentialCollisions); ++ } ++ } ++ // Tuinity end - optimise entity movement ++ + private Vec3D g(Vec3D vec3d) { + AxisAlignedBB axisalignedbb = this.getBoundingBox(); + VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this); +@@ -932,6 +1059,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return vec3d1; + } + ++ public static double getXZSquared(Vec3D vec3d) { return Entity.c(vec3d); } // Tuinity - OBFHELPER + public static double c(Vec3D vec3d) { + return vec3d.x * vec3d.x + vec3d.z * vec3d.z; + } +@@ -1044,18 +1172,34 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + + protected void checkBlockCollisions() { ++ // Tuinity start ++ this.checkBlockCollisions(false); ++ } ++ protected void checkBlockCollisions(boolean checkFire) { ++ boolean inFire = false; ++ // Tuinity end + AxisAlignedBB axisalignedbb = this.getBoundingBox(); + BlockPosition blockposition = new BlockPosition(axisalignedbb.minX + 0.001D, axisalignedbb.minY + 0.001D, axisalignedbb.minZ + 0.001D); + BlockPosition blockposition1 = new BlockPosition(axisalignedbb.maxX - 0.001D, axisalignedbb.maxY - 0.001D, axisalignedbb.maxZ - 0.001D); + BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); + + if (this.world.areChunksLoadedBetween(blockposition, blockposition1)) { +- for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) { +- for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) { +- for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) { ++ // Tuinity start - reorder iteration to more cache aware ++ for (int j = blockposition.getY(); j <= blockposition1.getY(); ++j) { ++ for (int k = blockposition.getZ(); k <= blockposition1.getZ(); ++k) { ++ for (int i = blockposition.getX(); i <= blockposition1.getX(); ++i) { ++ // Tuinity end - reorder iteration to more cache aware + blockposition_mutableblockposition.d(i, j, k); + IBlockData iblockdata = this.world.getType(blockposition_mutableblockposition); + ++ // Tuinity start - move fire checking in here - reuse getType from this method ++ if (checkFire) { ++ if (!inFire && (iblockdata.a(TagsBlock.FIRE) || iblockdata.a(Blocks.LAVA))) { ++ inFire = true; ++ } ++ } ++ // Tuinity end - move fire checking in here - reuse getType from this method ++ + try { + iblockdata.a(this.world, blockposition_mutableblockposition, this); + this.a(iblockdata); +@@ -1069,6 +1213,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + } + } + } ++ // Tuinity start - move fire checking in here - reuse getType from this method ++ if (checkFire & !inFire) { ++ this.setFireTicks(-this.getMaxFireTicks()); ++ } ++ // Tuinity end - move fire checking in here - reuse getType from this method + } + + } +@@ -2027,9 +2176,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + float f1 = this.size.width * 0.8F; + AxisAlignedBB axisalignedbb = AxisAlignedBB.g((double) f1, 0.10000000149011612D, (double) f1).d(this.locX(), this.getHeadY(), this.locZ()); + +- return this.world.b(this, axisalignedbb, (iblockdata, blockposition) -> { ++ return ((WorldServer)this.world).collidesWithAnyBlockOrWorldBorder(this, axisalignedbb, false, false, (iblockdata, blockposition) -> { // Tuinity - use optimised method + return iblockdata.o(this.world, blockposition); +- }).findAny().isPresent(); ++ }); // Tuinity - use optimised method + } + } + +@@ -2037,10 +2186,12 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return EnumInteractionResult.PASS; + } + ++ public final boolean hardCollidesWith(Entity other) { return this.j(other); } // Tuinity - OBFHELPER + public boolean j(Entity entity) { // Tuinity - diff on change, hard colliding entities override this + return entity.aZ() && !this.isSameVehicle(entity); + } + ++ public final boolean collisionBoxIsHard() { return this.aZ(); } // Tuinity - OBFHELPER + public boolean aZ() {// Tuinity - diff on change, hard colliding entities override this + return false; + } +diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java +index 25e54a1fadc5d31fb250a3f47524b4f345fc8cc6..cce0ac8a36bef3b9e5a2b95e0c3dd137e8525226 100644 +--- a/src/main/java/net/minecraft/server/ICollisionAccess.java ++++ b/src/main/java/net/minecraft/server/ICollisionAccess.java +@@ -28,6 +28,11 @@ public interface ICollisionAccess extends IBlockAccess { + } + + default boolean b(AxisAlignedBB axisalignedbb) { ++ // Tuinity start - allow overriding in WorldServer ++ return this.getCubes(axisalignedbb); ++ } ++ default boolean getCubes(AxisAlignedBB axisalignedbb) { ++ // Tuinity end - allow overriding in WorldServer + return this.b((Entity) null, axisalignedbb, (entity) -> { + return true; + }); +@@ -46,6 +51,11 @@ public interface ICollisionAccess extends IBlockAccess { + } + + default boolean b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ // Tuinity start - allow overriding in WorldServer ++ return this.getCubes(entity, axisalignedbb, predicate); ++ } ++ default boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ // Tuinity end - allow overriding in WorldServer + try { if (entity != null) entity.collisionLoadChunks = true; // Paper + return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty); + } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper +diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java +index b053bb74f6df174a27dbfd7b1b3e3ccbb0b26659..64b59b17d28803f510b8b088ebafe446c450d486 100644 +--- a/src/main/java/net/minecraft/server/IEntityAccess.java ++++ b/src/main/java/net/minecraft/server/IEntityAccess.java +@@ -70,7 +70,8 @@ public interface IEntityAccess { + } else { + AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D); + +- predicate = predicate.and((entity1) -> { // Tuinity - optimise entity hard collisions ++ if (predicate == null) predicate = (e) -> true; // Tuinity - allow nullable ++ predicate = predicate.and((entity1) -> { // Tuinity - optimise entity hard collisions // Tuinity - allow nullable + boolean flag; + + if (true || entity1.getBoundingBox().c(axisalignedbb1)) { // Tuinity - always true, wtf did they think this.getEntities(entity, axisalignedbb1) does? +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 2371557d083446b17ffebdae576b1cc39e939eb1..e79e773f2219f9a9ae076fcbc8108b792201b11a 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -38,6 +38,7 @@ import java.util.function.Consumer; + import java.util.function.Supplier; + + public final class MCUtil { ++ public static final double COLLISION_EPSILON = 1.0E-7; // Tuinity - Just in case mojang changes this... + public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( + 0, 2, 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue(), +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index b6f7d1c38517c8c96684913ad58b6f1e929e2d2b..19b26543a1503b8710ef5c013d0cf26e55749bfd 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1321,6 +1321,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant getBoundingBoxesRepresentation() { return this.d(); } // Tuinity - OBFHELPER + public List d() { + List list = Lists.newArrayList(); + +diff --git a/src/main/java/net/minecraft/server/VoxelShapeArray.java b/src/main/java/net/minecraft/server/VoxelShapeArray.java +index 3c29cb1452cde1308f630bfcb82876ef19057e8f..c14b7bd63e3917bc5f495655c40d8825a8d2062f 100644 +--- a/src/main/java/net/minecraft/server/VoxelShapeArray.java ++++ b/src/main/java/net/minecraft/server/VoxelShapeArray.java +@@ -3,6 +3,7 @@ package net.minecraft.server; + import it.unimi.dsi.fastutil.doubles.DoubleArrayList; + import it.unimi.dsi.fastutil.doubles.DoubleList; + import java.util.Arrays; ++import java.util.List; + + public final class VoxelShapeArray extends VoxelShape { + +@@ -10,11 +11,25 @@ public final class VoxelShapeArray extends VoxelShape { + private final DoubleList c; + private final DoubleList d; + ++ // Tuinity start - optimise multi-aabb shapes ++ static final AxisAlignedBB[] EMPTY = new AxisAlignedBB[0]; ++ final AxisAlignedBB[] boundingBoxesRepresentation; ++ ++ final double offsetX; ++ final double offsetY; ++ final double offsetZ; ++ // Tuinity end - optimise multi-aabb shapes ++ + protected VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, double[] adouble, double[] adouble1, double[] adouble2) { + this(voxelshapediscrete, (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble, voxelshapediscrete.b() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble1, voxelshapediscrete.c() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble2, voxelshapediscrete.d() + 1))); + } + + VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2) { ++ // Tuinity start - optimise multi-aabb shapes ++ this(voxelshapediscrete, doublelist, doublelist1, doublelist2, null, null, 0.0, 0.0, 0.0); ++ } ++ VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2, VoxelShapeArray original, AxisAlignedBB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) { ++ // Tuinity end - optimise multi-aabb shapes + super(voxelshapediscrete); + int i = voxelshapediscrete.b() + 1; + int j = voxelshapediscrete.c() + 1; +@@ -27,6 +42,18 @@ public final class VoxelShapeArray extends VoxelShape { + } else { + throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape."))); + } ++ // Tuinity start - optimise multi-aabb shapes ++ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.getBoundingBoxesRepresentation().toArray(EMPTY) : boundingBoxesRepresentation; // Tuinity - optimise multi-aabb shapes ++ if (original == null) { ++ this.offsetX = offsetX; ++ this.offsetY = offsetY; ++ this.offsetZ = offsetZ; ++ } else { ++ this.offsetX = offsetX + original.offsetX; ++ this.offsetY = offsetY + original.offsetY; ++ this.offsetZ = offsetZ + original.offsetZ; ++ } ++ // Tuinity end - optimise multi-aabb shapes + } + + @Override +@@ -42,4 +69,63 @@ public final class VoxelShapeArray extends VoxelShape { + throw new IllegalArgumentException(); + } + } ++ ++ // Tuinity start - optimise multi-aabb shapes ++ @Override ++ public VoxelShape a(double d0, double d1, double d2) { ++ if (this == VoxelShapes.getEmptyShape() || this.boundingBoxesRepresentation.length == 0) { ++ return this; ++ } ++ return new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2), this, this.boundingBoxesRepresentation, d0, d1, d2); ++ } ++ ++ @Override ++ public List d() { // getBoundingBoxesRepresentation ++ if (this.boundingBoxesRepresentation == null) { ++ return super.d(); ++ } ++ List ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length); ++ ++ double offX = this.offsetX; ++ double offY = this.offsetY; ++ double offZ = this.offsetZ; ++ for (AxisAlignedBB boundingBox : this.boundingBoxesRepresentation) { ++ ret.add(boundingBox.offset(offX, offY, offZ)); ++ } ++ ++ return ret; ++ } ++ ++ public final AxisAlignedBB[] getBoundingBoxesRepresentationRaw() { ++ return this.boundingBoxesRepresentation; ++ } ++ ++ public final double getOffsetX() { ++ return this.offsetX; ++ } ++ ++ public final double getOffsetY() { ++ return this.offsetY; ++ } ++ ++ public final double getOffsetZ() { ++ return this.offsetZ; ++ } ++ ++ public final boolean intersects(AxisAlignedBB axisalingedbb) { ++ // this can be optimised by checking an "overall shape" first, but not needed ++ double offX = this.offsetX; ++ double offY = this.offsetY; ++ double offZ = this.offsetZ; ++ ++ for (AxisAlignedBB boundingBox : this.boundingBoxesRepresentation) { ++ if (axisalingedbb.voxelShapeIntersect(boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ, ++ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // Tuinity end - optimise multi-aabb shapes + } +diff --git a/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java b/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java +index e841611bb7c36dffec44bb9e74a0a9657a113263..259605daabb18aedb15d56c78e6553ae2d22e13f 100644 +--- a/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java ++++ b/src/main/java/net/minecraft/server/VoxelShapeSpliterator.java +@@ -91,7 +91,7 @@ public class VoxelShapeSpliterator extends AbstractSpliterator { + VoxelShape voxelshape = iblockdata.b((IBlockAccess) this.g, this.e, this.c); + + if (voxelshape == VoxelShapes.b()) { +- if (!this.b.a((double) i, (double) j, (double) k, (double) i + 1.0D, (double) j + 1.0D, (double) k + 1.0D)) { ++ if (!this.b.voxelShapeIntersect((double) i, (double) j, (double) k, (double) i + 1.0D, (double) j + 1.0D, (double) k + 1.0D)) { // Tuinity - keep vanilla behavior for voxelshape intersection - See comment in AxisAlignedBB + continue; + } + +diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java +index 9f4f9df09968dc45878ad59f5ee45672a3f08fbd..636bbbc42466cb54c300352f400464fe64cc2e79 100644 +--- a/src/main/java/net/minecraft/server/VoxelShapes.java ++++ b/src/main/java/net/minecraft/server/VoxelShapes.java +@@ -17,18 +17,101 @@ public final class VoxelShapes { + + voxelshapebitset.a(0, 0, 0, true, true); + return new VoxelShapeCube(voxelshapebitset); +- }); ++ }); public static final VoxelShape getFullUnoptimisedCube() { return VoxelShapes.b; } // Tuinity - OBFHELPER + public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); +- private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); ++ private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); public static final VoxelShape getEmptyShape() { return VoxelShapes.c; } // Tuinity - OBFHELPER ++ ++ // Tuinity start - optimise voxelshapes ++ public static boolean isEmpty(VoxelShape voxelshape) { ++ // helper function for determining empty shapes fast ++ return voxelshape == getEmptyShape() || voxelshape.isEmpty(); ++ } ++ // Tuinity end - optimise voxelshapes + + public static final VoxelShape empty() {return a();} // Paper - OBFHELPER + public static VoxelShape a() { + return VoxelShapes.c; + } + ++ static final com.tuinity.tuinity.voxel.AABBVoxelShape optimisedFullCube = new com.tuinity.tuinity.voxel.AABBVoxelShape(new AxisAlignedBB(0, 0, 0, 1.0, 1.0, 1.0)); // Tuinity - optimise voxelshape ++ ++ // Tuinity start - optimise voxelshapes ++ public static boolean addBoxesToIfIntersects(VoxelShape shape, AxisAlignedBB aabb, java.util.List list) { ++ if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { ++ com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape; ++ if (!shapeCasted.aabb.isEmpty() && shapeCasted.aabb.voxelShapeIntersect(aabb)) { ++ list.add(shapeCasted.aabb); ++ return true; ++ } ++ return false; ++ } else if (shape instanceof VoxelShapeArray) { ++ VoxelShapeArray shapeCasted = (VoxelShapeArray)shape; ++ // this can be optimised by checking an "overall shape" first, but not needed ++ ++ double offX = shapeCasted.offsetX; ++ double offY = shapeCasted.offsetY; ++ double offZ = shapeCasted.offsetZ; ++ ++ boolean ret = false; ++ ++ for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) { ++ double minX, minY, minZ, maxX, maxY, maxZ; ++ if (aabb.voxelShapeIntersect(minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ, ++ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ)) { ++ AxisAlignedBB box = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ, false); ++ if (!box.isEmpty()) { ++ list.add(box); ++ ret = true; ++ } ++ } ++ } ++ ++ return ret; ++ } else { ++ boolean ret = false; ++ ++ java.util.List boxes = shape.getBoundingBoxesRepresentation(); ++ for (int i = 0, len = boxes.size(); i < len; ++i) { ++ AxisAlignedBB box = boxes.get(i); ++ if (!box.isEmpty() && box.voxelShapeIntersect(aabb)) { ++ list.add(box); ++ ret = true; ++ } ++ } ++ ++ return ret; ++ } ++ } ++ ++ public static void addBoxesTo(VoxelShape shape, java.util.List list) { ++ if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { ++ com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape; ++ if (!shapeCasted.isEmpty()) { ++ list.add(shapeCasted.aabb); ++ } ++ } else if (shape instanceof VoxelShapeArray) { ++ VoxelShapeArray shapeCasted = (VoxelShapeArray)shape; ++ ++ for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) { ++ if (!boundingBox.isEmpty()) { ++ list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ)); ++ } ++ } ++ } else { ++ java.util.List boxes = shape.getBoundingBoxesRepresentation(); ++ for (int i = 0, len = boxes.size(); i < len; ++i) { ++ AxisAlignedBB box = boxes.get(i); ++ if (!box.isEmpty()) { ++ list.add(box); ++ } ++ } ++ } ++ } ++ // Tuinity end - optimise voxelshapes ++ + public static final VoxelShape fullCube() {return b();} // Paper - OBFHELPER + public static VoxelShape b() { +- return VoxelShapes.b; ++ return VoxelShapes.optimisedFullCube; // Tuinity - optimise voxelshape + } + + public static VoxelShape create(double d0, double d1, double d2, double d3, double d4, double d5) { +@@ -67,7 +150,7 @@ public final class VoxelShapes { + return new VoxelShapeCube(voxelshapebitset); + } + } else { +- return new VoxelShapeArray(VoxelShapes.b.a, new double[]{axisalignedbb.minX, axisalignedbb.maxX}, new double[]{axisalignedbb.minY, axisalignedbb.maxY}, new double[]{axisalignedbb.minZ, axisalignedbb.maxZ}); ++ return new com.tuinity.tuinity.voxel.AABBVoxelShape(axisalignedbb); // Tuinity - optimise VoxelShapes for single AABB shapes + } + } + +@@ -132,6 +215,20 @@ public final class VoxelShapes { + + public static final boolean applyOperation(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { return VoxelShapes.c(voxelshape, voxelshape1, operatorboolean); } // Paper - OBFHELPER + public static boolean c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { ++ // Tuinity start - optimise voxelshape ++ if (operatorboolean == OperatorBoolean.AND) { ++ if (voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { ++ return ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb.voxelShapeIntersect(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb); ++ } else if (voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape1 instanceof VoxelShapeArray) { ++ return ((VoxelShapeArray)voxelshape1).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb); ++ } else if (voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape instanceof VoxelShapeArray) { ++ return ((VoxelShapeArray)voxelshape).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb); ++ } ++ } ++ return abstract_c(voxelshape, voxelshape1, operatorboolean); ++ } ++ public static boolean abstract_c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { ++ // Tuinity end - optimise voxelshape + if (operatorboolean.apply(false, false)) { + throw (IllegalArgumentException) SystemUtils.c((Throwable) (new IllegalArgumentException())); + } else if (voxelshape == voxelshape1) { +@@ -316,7 +413,50 @@ public final class VoxelShapes { + + public static boolean combinationOccludes(VoxelShape voxelshape, VoxelShape voxelshape1) { return b(voxelshape, voxelshape1); } // Tuinity - OBFHELPER + public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) { +- return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; ++ if (voxelshape == getFullUnoptimisedCube() || voxelshape == optimisedFullCube ++ || voxelshape1 == getFullUnoptimisedCube() || voxelshape1 == optimisedFullCube) { ++ return true; ++ } ++ boolean v1Empty = voxelshape == getEmptyShape(); ++ boolean v2Empty = voxelshape1 == getEmptyShape(); ++ if (v1Empty && v2Empty) { ++ return false; ++ } ++ if ((voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v1Empty) && (voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v2Empty)) { ++ if (!v1Empty && !v2Empty && (voxelshape != voxelshape1)) { ++ AxisAlignedBB boundingBox1 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb; ++ AxisAlignedBB boundingBox2 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb; ++ // can call it here in some cases ++ ++ // check overall bounding box ++ double minY = Math.min(boundingBox1.minY, boundingBox2.minY); ++ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY); ++ if (minY > MCUtil.COLLISION_EPSILON || maxY < (1 - MCUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ double minX = Math.min(boundingBox1.minX, boundingBox2.minX); ++ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX); ++ if (minX > MCUtil.COLLISION_EPSILON || maxX < (1 - MCUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ); ++ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ); ++ if (minZ > MCUtil.COLLISION_EPSILON || maxZ < (1 - MCUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ // fall through to full merge check ++ } else { ++ AxisAlignedBB boundingBox = v1Empty ? ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb : ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb; ++ // check if the bounding box encloses the full cube ++ return (boundingBox.minY <= MCUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - MCUtil.COLLISION_EPSILON)) && ++ (boundingBox.minX <= MCUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - MCUtil.COLLISION_EPSILON)) && ++ (boundingBox.minZ <= MCUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - MCUtil.COLLISION_EPSILON)); ++ } ++ } ++ return b_rare(voxelshape, voxelshape1); ++ } ++ public static boolean b_rare(VoxelShape voxelshape, VoxelShape voxelshape1) { ++ return (voxelshape != b() || voxelshape != getFullUnoptimisedCube()) && (voxelshape1 != b() || voxelshape1 != getFullUnoptimisedCube()) ? ((voxelshape == VoxelShapes.getEmptyShape() || voxelshape.isEmpty()) && (voxelshape1 == VoxelShapes.getEmptyShape() || voxelshape1.isEmpty()) ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; // Tuinity - optimise call by checking against more constant shapes + } + + @VisibleForTesting +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 8ec6b3a1b8f5281b875cbb3cf85833ab3c208bc3..9d9ff42a4572da8f62cc9fc5f11053b578858767 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1116,8 +1116,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + @Override + public List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { +- this.getMethodProfiler().c("getEntities"); ++ // Tuinity start - add list parameter + List list = Lists.newArrayList(); ++ return this.getEntities(entity, axisalignedbb, predicate, list); ++ } ++ public List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate, List list) { ++ // Tuinity end - add list parameter ++ this.getMethodProfiler().c("getEntities"); + int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D); + int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D); + int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D); +diff --git a/src/main/java/net/minecraft/server/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java +index f011869880fedae4b69e505491e8bdbc5f51dfba..0d10d317cd0b60fc0866ae505c7fd71fa886c48b 100644 +--- a/src/main/java/net/minecraft/server/WorldBorder.java ++++ b/src/main/java/net/minecraft/server/WorldBorder.java +@@ -47,11 +47,59 @@ public class WorldBorder { + return axisalignedbb.maxX > this.e() && axisalignedbb.minX < this.g() && axisalignedbb.maxZ > this.f() && axisalignedbb.minZ < this.h(); + } + ++ // Tuinity start - optimise collisions ++ // determines whether we are almost colliding with the world border ++ // for clear collisions, this rets false ++ public final boolean isAlmostCollidingOnBorder(AxisAlignedBB boundingBox) { ++ return this.isAlmostCollidingOnBorder(boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); ++ } ++ ++ public final boolean isAlmostCollidingOnBorder(double boxMinX, double boxMaxX, double boxMinZ, double boxMaxZ) { ++ double borderMinX = this.getMinX(); ++ double borderMaxX = this.getMaxX(); ++ ++ double borderMinZ = this.getMinZ(); ++ double borderMaxZ = this.getMaxZ(); ++ ++ return ++ // Not intersecting if we're smaller ++ !AxisAlignedBB.voxelShapeIntersect( ++ boxMinX + MCUtil.COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + MCUtil.COLLISION_EPSILON, ++ boxMaxX - MCUtil.COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - MCUtil.COLLISION_EPSILON, ++ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ ++ ) ++ && ++ ++ // Are intersecting if we're larger ++ AxisAlignedBB.voxelShapeIntersect( ++ boxMinX - MCUtil.COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - MCUtil.COLLISION_EPSILON, ++ boxMaxX + MCUtil.COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + MCUtil.COLLISION_EPSILON, ++ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ ++ ) ++ ; ++ } ++ ++ public final boolean isCollidingWithBorderEdge(AxisAlignedBB boundingBox) { ++ return this.isCollidingWithBorderEdge(boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); ++ } ++ ++ public final boolean isCollidingWithBorderEdge(double boxMinX, double boxMaxX, double boxMinZ, double boxMaxZ) { ++ double borderMinX = this.getMinX() + MCUtil.COLLISION_EPSILON; ++ double borderMaxX = this.getMaxX() - MCUtil.COLLISION_EPSILON; ++ ++ double borderMinZ = this.getMinZ() + MCUtil.COLLISION_EPSILON; ++ double borderMaxZ = this.getMaxZ() - MCUtil.COLLISION_EPSILON; ++ ++ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ; ++ } ++ // Tuinity end - optimise collisions ++ + public double a(Entity entity) { + return this.b(entity.locX(), entity.locZ()); + } + + public final VoxelShape asVoxelShape(){ return c();} // Paper - OBFHELPER ++ public final VoxelShape getCollisionShape() { return this.c(); } // Tuinity - OBFHELPER + public VoxelShape c() { + return this.j.m(); + } +@@ -67,18 +115,22 @@ public class WorldBorder { + return Math.min(d6, d3); + } + ++ public final double getMinX() { return this.e(); } // Tuinity - OBFHELPER + public double e() { + return this.j.a(); + } + ++ public final double getMinZ() { return this.f(); } // Tuinity - OBFHELPER + public double f() { + return this.j.c(); + } + ++ public final double getMaxX() { return this.g(); } // Tuinity - OBFHELPER + public double g() { + return this.j.b(); + } + ++ public final double getMaxZ() { return this.h(); } // Tuinity - OBFHELPER + public double h() { + return this.j.d(); + } +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 1354e85916ab9748cea000c08c32bee3fa599655..48446407b70a8384c18eb0567f16dfb17c524312 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -360,6 +360,243 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper + } + ++ // Tuinity start - optimise collision ++ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks, ++ boolean collidesWithUnloaded) { ++ return this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, loadChunks, collidesWithUnloaded, true, null); ++ } ++ ++ public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, ++ boolean loadChunks, boolean collidesWithUnloaded, ++ java.util.function.BiPredicate predicate) { ++ return this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, null, loadChunks, collidesWithUnloaded, true, predicate); ++ } ++ ++ public final boolean hardCollidesWithAnyEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { ++ if (axisalignedbb.isEmpty()) { ++ return false; ++ } ++ axisalignedbb = axisalignedbb.grow(MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON); ++ List entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); ++ try { ++ if (entity != null && entity.hardCollides()) { ++ this.getEntities(entity, axisalignedbb, predicate, entities); ++ } else { ++ this.getHardCollidingEntities(entity, axisalignedbb, predicate, entities); ++ } ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity otherEntity = entities.get(i); ++ ++ if ((entity == null || otherEntity.collisionBoxIsHard()) || entity.hardCollidesWith(otherEntity)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } finally { ++ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities); ++ } ++ } ++ ++ public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { ++ return this.hasAnyCollisions(entity, axisalignedbb, true); ++ } ++ ++ public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) { ++ return this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, loadChunks, true) ++ || this.hardCollidesWithAnyEntities(entity, axisalignedbb, null); ++ } ++ ++ // returns whether any collisions were detected ++ public boolean getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, ++ boolean loadChunks, boolean collidesWithUnloaded, boolean checkOnly, ++ java.util.function.BiPredicate predicate) { ++ boolean ret = false; ++ ++ if (entity != null) { ++ if (this.getWorldBorder().isAlmostCollidingOnBorder(axisalignedbb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list); ++ ret = true; ++ } ++ } ++ } ++ ++ int minBlockX = MathHelper.floor(axisalignedbb.minX - MCUtil.COLLISION_EPSILON) - 1; ++ int maxBlockX = MathHelper.floor(axisalignedbb.maxX + MCUtil.COLLISION_EPSILON) + 1; ++ ++ int minBlockY = MathHelper.floor(axisalignedbb.minY - MCUtil.COLLISION_EPSILON) - 1; ++ int maxBlockY = MathHelper.floor(axisalignedbb.maxY + MCUtil.COLLISION_EPSILON) + 1; ++ ++ int minBlockZ = MathHelper.floor(axisalignedbb.minZ - MCUtil.COLLISION_EPSILON) - 1; ++ int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + MCUtil.COLLISION_EPSILON) + 1; ++ ++ ++ BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); ++ VoxelShapeCollision collisionShape = null; ++ ++ // special cases: ++ if (minBlockY > 255 || maxBlockY < 0) { ++ // no point in checking ++ return ret; ++ } ++ ++ int minYIterate = Math.max(0, minBlockY); ++ int maxYIterate = Math.min(255, maxBlockY); ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider; ++ // TODO special case single chunk? ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk ++ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk ++ ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk ++ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk ++ ++ int chunkXGlobalPos = currChunkX << 4; ++ int chunkZGlobalPos = currChunkZ << 4; ++ Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); ++ ++ if (chunk == null) { ++ if (collidesWithUnloaded) { ++ if (checkOnly) { ++ return true; ++ } else { ++ list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ ++ ChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ ChunkSection section = sections[currY >>> 4]; ++ if (section == null || section.isFullOfAir()) { ++ // empty ++ // skip to next section ++ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one ++ continue; ++ } ++ ++ DataPaletteBlock blocks = section.blockIds; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8); ++ int blockX = currX | chunkXGlobalPos; ++ int blockY = currY; ++ int blockZ = currZ | chunkZGlobalPos; ++ ++ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ IBlockData blockData = blocks.rawGet(localBlockIndex); ++ ++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { ++ mutablePos.setValues(blockX, blockY, blockZ); ++ if (collisionShape == null) { ++ collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); ++ } ++ VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); ++ if (voxelshape2 != VoxelShapes.getEmptyShape()) { ++ VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)blockY, (double)blockZ); ++ ++ if (predicate != null && !predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ ++ if (checkOnly) { ++ if (voxelshape3.intersects(axisalignedbb)) { ++ return true; ++ } ++ } else { ++ ret |= VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void getEntityHardCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate, List list) { ++ if (axisalignedbb.isEmpty()) { ++ return; ++ } ++ axisalignedbb = axisalignedbb.grow(MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON, MCUtil.COLLISION_EPSILON); ++ List entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); ++ try { ++ if (entity != null && entity.hardCollides()) { ++ this.getEntities(entity, axisalignedbb, predicate, entities); ++ } else { ++ this.getHardCollidingEntities(entity, axisalignedbb, predicate, entities); ++ } ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity otherEntity = entities.get(i); ++ ++ if ((entity == null || otherEntity.collisionBoxIsHard()) || entity.hardCollidesWith(otherEntity)) { ++ if (!otherEntity.getBoundingBox().isEmpty()) { ++ list.add(otherEntity.getBoundingBox()); ++ } ++ } ++ } ++ } finally { ++ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities); ++ } ++ } ++ ++ public final void getCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, boolean loadChunks) { ++ this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, list, loadChunks, true, false, null); ++ this.getEntityHardCollisions(entity, axisalignedbb, null, list); ++ } ++ ++ @Override ++ public boolean getCubes(AxisAlignedBB axisalignedbb) { ++ return !this.hasAnyCollisions(null, axisalignedbb); ++ } ++ ++ @Override ++ public boolean getCubes(Entity entity) { ++ return !this.hasAnyCollisions(entity, entity.getBoundingBox()); ++ } ++ ++ @Override ++ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { ++ if (entity instanceof EntityArmorStand && !entity.world.paperConfig.armorStandEntityLookups) return false; ++ return !this.hasAnyCollisions(entity, axisalignedbb); ++ } ++ ++ @Override ++ public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { ++ if (entity instanceof EntityArmorStand && !entity.world.paperConfig.armorStandEntityLookups) return false; ++ return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, true, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, predicate); ++ } ++ // Tuinity end - optimise collision ++ + // CraftBukkit start + @Override + protected TileEntity getTileEntity(BlockPosition pos, boolean validate) { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +index f72c13bedaa6fa45e26f5dcad564835bdd4af61f..50f855b931dba60754fff9c7cdf5e0e744f00fdd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +@@ -119,6 +119,32 @@ public class UnsafeList extends AbstractList implements List, RandomAcc + return indexOf(o) >= 0; + } + ++ // Tuinity start ++ protected transient int maxSize; ++ public void setSize(int size) { ++ if (this.maxSize < this.size) { ++ this.maxSize = this.size; ++ } ++ this.size = size; ++ } ++ ++ public void completeReset() { ++ if (this.data != null) { ++ Arrays.fill(this.data, 0, Math.max(this.size, this.maxSize), null); ++ } ++ this.size = 0; ++ this.maxSize = 0; ++ if (this.iterPool != null) { ++ for (Iterator temp : this.iterPool) { ++ if (temp == null) { ++ continue; ++ } ++ ((Itr)temp).valid = false; ++ } ++ } ++ } ++ // Tuinity end ++ + @Override + public void clear() { + // Create new array to reset memory usage to initial capacity diff --git a/patches/Tuinity/patches/server/0026-Reduce-allocation-rate-from-crammed-entities.patch b/patches/Tuinity/patches/server/0026-Reduce-allocation-rate-from-crammed-entities.patch new file mode 100644 index 00000000..f1b819f9 --- /dev/null +++ b/patches/Tuinity/patches/server/0026-Reduce-allocation-rate-from-crammed-entities.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 27 May 2020 14:26:26 -0700 +Subject: [PATCH] Reduce allocation rate from crammed entities + + +diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java +index d417878a1584c9e5ec33b94fc65b29f84fb3a5e9..4fe5a8d0201d662c68dd58eeb8cf1d304787edb4 100644 +--- a/src/main/java/net/minecraft/server/EntityLiving.java ++++ b/src/main/java/net/minecraft/server/EntityLiving.java +@@ -2858,7 +2858,11 @@ public abstract class EntityLiving extends Entity { + return; + } + // Paper - end don't run getEntities if we're not going to use its result +- List list = this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.pushable(this, world.paperConfig.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule ++ // Tuinity start - reduce memory allocation from collideNearby ++ List list = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); ++ this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.pushable(this, world.paperConfig.fixClimbingBypassingCrammingRule), list); // Paper - fix climbing bypassing cramming rule ++ try { ++ // Tuinity end - reduce memory allocation from collideNearby + + if (!list.isEmpty()) { + // Paper - move up +@@ -2887,6 +2891,9 @@ public abstract class EntityLiving extends Entity { + this.C(entity); + } + } ++ } finally { // Tuinity start - reduce memory allocation from collideNearby ++ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(list); ++ } // Tuinity end - reduce memory allocation from collideNearby + + } + diff --git a/patches/Tuinity/patches/server/0027-Optimise-chunk-tick-iteration.patch b/patches/Tuinity/patches/server/0027-Optimise-chunk-tick-iteration.patch new file mode 100644 index 00000000..d6ac0783 --- /dev/null +++ b/patches/Tuinity/patches/server/0027-Optimise-chunk-tick-iteration.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 7 May 2020 05:48:54 -0700 +Subject: [PATCH] Optimise chunk tick iteration + +Use a dedicated list of entity ticking chunks to reduce the cost + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 26db7d4f9c5e9bb5ecd7f79e263d365d98f70fd7..b7dd5cebab80bdd444a1e464e2092b345e54064a 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -22,6 +22,12 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + ++// Tuinity start ++import it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; ++import it.unimi.dsi.fastutil.objects.Object2BooleanMap; ++// Tuinity end ++ + public class ChunkProviderServer extends IChunkProvider { + + private static final List b = ChunkStatus.a(); static final List getPossibleChunkStatuses() { return ChunkProviderServer.b; } // Paper - OBFHELPER +@@ -972,19 +978,23 @@ public class ChunkProviderServer extends IChunkProvider { + //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper + //Collections.shuffle(list); // Paper + // Paper - moved up +- final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping +- Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); +- +- if (optional.isPresent()) { ++ // Tuinity start - optimise chunk tick iteration ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entityTickingChunks.iterator(); ++ try { ++ while (iterator.hasNext()) { ++ Chunk chunk = iterator.next(); ++ PlayerChunk playerchunk = chunk.playerChunk; ++ if (playerchunk != null) { // make sure load event has been called along with the load logic we put there ++ // Tuinity end - optimise chunk tick iteration + this.world.getMethodProfiler().enter("broadcast"); + this.world.timings.broadcastChunkUpdates.startTiming(); // Paper - timings +- playerchunk.a((Chunk) optional.get()); ++ playerchunk.a(chunk); // Tuinity + this.world.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings + this.world.getMethodProfiler().exit(); +- Optional optional1 = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); ++ // Tuinity + +- if (optional1.isPresent()) { +- Chunk chunk = (Chunk) optional1.get(); ++ if (true) { // Tuinity ++ // Tuinity + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + + if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange +@@ -1000,7 +1010,11 @@ public class ChunkProviderServer extends IChunkProvider { + } + } + } +- }); ++ } // Tuinity start - optimise chunk tick iteration ++ } finally { ++ iterator.finishedIterating(); ++ } ++ // Tuinity end - optimise chunk tick iteration + this.world.getMethodProfiler().enter("customSpawners"); + if (flag1) { + try (co.aikar.timings.Timing ignored = this.world.timings.miscMobSpawning.startTiming()) { // Paper - timings diff --git a/patches/Tuinity/patches/server/0028-Use-entity-ticking-chunk-map-for-entity-tracker.patch b/patches/Tuinity/patches/server/0028-Use-entity-ticking-chunk-map-for-entity-tracker.patch new file mode 100644 index 00000000..acddc530 --- /dev/null +++ b/patches/Tuinity/patches/server/0028-Use-entity-ticking-chunk-map-for-entity-tracker.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 13 May 2020 09:59:12 -0700 +Subject: [PATCH] Use entity ticking chunk map for entity tracker + +Should bring us back in-line with tracker performance +before the loaded entity list reversion. + +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 5c7fb3cb21478ff6a88e47d1e63c9baba4a8d5e7..4c57c9999960f7c4467407a7d5102476a3a9305c 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -2068,22 +2068,25 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private final void processTrackQueue() { + this.world.timings.tracker1.startTiming(); + try { +- for (EntityTracker tracker : this.trackedEntities.values()) { +- // update tracker entry +- tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange()); ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.world.getChunkProvider().entityTickingChunks.iterator(); ++ try { ++ while (iterator.hasNext()) { ++ Chunk chunk = iterator.next(); ++ Entity[] entities = chunk.entities.getRawData(); ++ for (int i = 0, len = chunk.entities.size(); i < len; ++i) { ++ Entity entity = entities[i]; ++ EntityTracker tracker = this.trackedEntities.get(entity.getId()); ++ if (tracker != null) { ++ tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange()); ++ tracker.trackerEntry.tick(); ++ } ++ } + } +- } finally { +- this.world.timings.tracker1.stopTiming(); +- } +- +- +- this.world.timings.tracker2.startTiming(); +- try { +- for (EntityTracker tracker : this.trackedEntities.values()) { +- tracker.trackerEntry.tick(); ++ } finally { ++ iterator.finishedIterating(); + } + } finally { +- this.world.timings.tracker2.stopTiming(); ++ this.world.timings.tracker1.stopTiming(); + } + } + // Paper end - optimised tracker diff --git a/patches/Tuinity/patches/server/0029-Improve-paper-prevent-moving-into-unloaded-chunk-che.patch b/patches/Tuinity/patches/server/0029-Improve-paper-prevent-moving-into-unloaded-chunk-che.patch new file mode 100644 index 00000000..ec3720f8 --- /dev/null +++ b/patches/Tuinity/patches/server/0029-Improve-paper-prevent-moving-into-unloaded-chunk-che.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 17 May 2020 23:52:38 -0700 +Subject: [PATCH] Improve paper prevent moving into unloaded chunk check + +Check the AABB of the move + +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 87b1ff21957d5d708209257e569785aeaf191181..f1be42b5f1d0eb403e6895e8ff1d106861fa4298 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -419,7 +419,9 @@ public class PlayerConnection implements PacketListenerPlayIn { + speed *= 2f; // TODO: Get the speed of the vehicle instead of the player + + // Paper start - Prevent moving into unloaded chunks +- if (player.world.paperConfig.preventMovingIntoUnloadedChunks && worldserver.getChunkIfLoadedImmediately((int) Math.floor(packetplayinvehiclemove.getX()) >> 4, (int) Math.floor(packetplayinvehiclemove.getZ()) >> 4) == null) { ++ if (player.world.paperConfig.preventMovingIntoUnloadedChunks // Tuinity - improve this check ++ && (!worldserver.areChunksLoadedForMove(this.player.getBoundingBoxAt(this.player.locX(), this.player.locY(), this.player.locZ()).expand(toX - this.player.locX(), toY - this.player.locY(), toZ - this.player.locZ()))) // Tuinity - improve this check ++ || !worldserver.areChunksLoadedForMove(entity.getBoundingBoxAt(entity.locX(), entity.locY(), entity.locZ()).expand(toX - entity.locX(), toY - entity.locY(), toZ - entity.locZ()))) { // Tuinity - improve this check + this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity)); + return; + } +@@ -1138,7 +1140,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + speed = player.abilities.walkSpeed * 10f; + } + // Paper start - Prevent moving into unloaded chunks +- if (player.world.paperConfig.preventMovingIntoUnloadedChunks && (this.player.locX() != toX || this.player.locZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately ++ if (player.world.paperConfig.preventMovingIntoUnloadedChunks && !((WorldServer)this.player.world).areChunksLoadedForMove(this.player.getBoundingBoxAt(this.player.locX(), this.player.locY(), this.player.locZ()).expand(toX - this.player.locX(), toY - this.player.locY(), toZ - this.player.locZ()))) { // Paper - use getIfLoadedImmediately // Tuinity - improve this check + this.internalTeleport(this.player.locX(), this.player.locY(), this.player.locZ(), this.player.yaw, this.player.pitch, Collections.emptySet()); + return; + } diff --git a/patches/Tuinity/patches/server/0030-Improve-async-tp-to-not-load-chunks-when-crossing-wo.patch b/patches/Tuinity/patches/server/0030-Improve-async-tp-to-not-load-chunks-when-crossing-wo.patch new file mode 100644 index 00000000..8bbbad23 --- /dev/null +++ b/patches/Tuinity/patches/server/0030-Improve-async-tp-to-not-load-chunks-when-crossing-wo.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 18 May 2020 00:30:48 -0700 +Subject: [PATCH] Improve async tp to not load chunks when crossing worlds + +Fixes an issue where a getCubes call would load neighbouring chunks. +Loads less chunks than paper's implementation + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 475dc1aa2cba77c13033938e719a66707f358914..a6d849facba1526ae2a2b7f3fb9a140d0b50289c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -508,27 +508,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + entity.setHeadRotation(yaw); + } + +- @Override// Paper start +- public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { +- net.minecraft.server.PlayerChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().playerChunkMap; +- java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); +- +- loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> { +- net.minecraft.server.ChunkCoordIntPair pair = new net.minecraft.server.ChunkCoordIntPair(chunk.getX(), chunk.getZ()); +- ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().addTicketAtLevel(net.minecraft.server.TicketType.POST_TELEPORT, pair, 31, 0); +- net.minecraft.server.PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pair.pair()); +- if (updatingChunk != null) { +- return updatingChunk.getEntityTickingFuture(); +- } else { +- return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle())); +- } +- }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> { +- future.completeExceptionally(ex); +- return null; +- }); +- return future; +- } +- // Paper end ++ // Tuinity + + @Override + public boolean teleport(Location location) { +@@ -562,6 +542,37 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return true; + } + ++ // Tuinity start - implement teleportAsync better ++ @Override ++ public java.util.concurrent.CompletableFuture teleportAsync(Location location, TeleportCause cause) { ++ Preconditions.checkArgument(location != null, "location"); ++ location.checkFinite(); ++ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. ++ ++ net.minecraft.server.WorldServer world = ((CraftWorld)locationClone.getWorld()).getHandle(); ++ java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); ++ ++ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), location.getX(), location.getZ(), (list) -> { ++ net.minecraft.server.ChunkProviderServer chunkProviderServer = world.getChunkProvider(); ++ for (net.minecraft.server.IChunkAccess chunk : list) { ++ chunkProviderServer.addTicketAtLevel(net.minecraft.server.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); ++ } ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { ++ try { ++ ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE); ++ } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ ret.completeExceptionally(throwable); ++ } ++ }); ++ }); ++ ++ return ret; ++ } ++ // Tuinity end - implement teleportAsync better ++ + @Override + public boolean teleport(org.bukkit.entity.Entity destination) { + return teleport(destination.getLocation()); diff --git a/patches/Tuinity/patches/server/0031-Revert-getChunkAt-Async-retaining-chunks-for-long-pe.patch b/patches/Tuinity/patches/server/0031-Revert-getChunkAt-Async-retaining-chunks-for-long-pe.patch new file mode 100644 index 00000000..f7870c11 --- /dev/null +++ b/patches/Tuinity/patches/server/0031-Revert-getChunkAt-Async-retaining-chunks-for-long-pe.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 9 Jun 2020 14:02:06 -0700 +Subject: [PATCH] Revert getChunkAt(Async) retaining chunks for long periods of + time + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 61c0ea8ec08c08793ddffd4a7fa84c29a75185fc..545a8bcf16215bde467d39881b19486bee7db873 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -422,14 +422,7 @@ public class CraftWorld implements World { + + @Override + public Chunk getChunkAt(int x, int z) { +- // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it +- net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z); +- if (chunk == null) { +- addTicket(x, z); +- chunk = this.world.getChunkProvider().getChunkAt(x, z, true); +- } +- return chunk.bukkitChunk; +- // Paper end ++ return this.world.getChunkProvider().getChunkAt(x, z, true).bukkitChunk; // Tuinity - revert paper diff + } + + // Paper start +@@ -2571,7 +2564,7 @@ public class CraftWorld implements World { + } + return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { + net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null); +- if (chunk != null) addTicket(x, z); // Paper ++ if (false && chunk != null) addTicket(x, z); // Paper // Tuinity - revert + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); + }, MinecraftServer.getServer()); + } diff --git a/patches/Tuinity/patches/server/0032-Rework-PlayerChunk-main-thread-checks.patch b/patches/Tuinity/patches/server/0032-Rework-PlayerChunk-main-thread-checks.patch new file mode 100644 index 00000000..e9676833 --- /dev/null +++ b/patches/Tuinity/patches/server/0032-Rework-PlayerChunk-main-thread-checks.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 10 Jun 2020 15:17:15 -0700 +Subject: [PATCH] Rework PlayerChunk main thread checks + +These need to fail instead of continuing, as hiding these errors +the way paper has is just going to allow unexpected reordering +of callbacks. + +For example, thanks to this patch incorrect future +completion (completion of the world gen future, +PlayerChunkMap#b(PlayerChunk, ChunkStatus)) was detected and fixed. + +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 12a207afc263f02ba1971777fe5c30d120ba9292..52e586a7e193b0012c9939554376f6e8f717091e 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -560,6 +560,7 @@ public class PlayerChunk { + } + + protected void a(PlayerChunkMap playerchunkmap) { ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticket level update"); // Tuinity + ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel); + ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel); + boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET; +@@ -569,7 +570,8 @@ public class PlayerChunk { + // CraftBukkit start + // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. + if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { +- this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main ++ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity + Chunk chunk = (Chunk)either.left().orElse(null); + if (chunk != null) { + playerchunkmap.callbackExecutor.execute(() -> { +@@ -634,7 +636,8 @@ public class PlayerChunk { + if (!flag2 && flag3) { + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; +- this.fullChunkFuture = playerchunkmap.b(this); ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main ++ this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { // Paper - ensure main // Tuinity - always fired on main ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full chunk future completion"); // Tuinity + if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk fullChunk = either.left().get(); +@@ -665,7 +668,8 @@ public class PlayerChunk { + + if (!flag4 && flag5) { + // Paper start - cache ticking ready status +- this.tickingFuture = playerchunkmap.a(this); ensureMain(this.tickingFuture).thenAccept((either) -> { // Paper - ensure main ++ this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { // Paper - ensure main // Tuinity - always completed on main ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticking chunk future completion"); // Tuinity + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk tickingChunk = either.left().get(); +@@ -705,7 +709,8 @@ public class PlayerChunk { + } + + // Paper start - cache ticking ready status +- this.entityTickingFuture = playerchunkmap.b(this.location); ensureMain(this.entityTickingFuture).thenAccept((either) -> { // Paper ensureMain ++ this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { // Paper ensureMain // Tuinity - always completed on main ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async entity ticking chunk future completion"); // Tuinity + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk entityTickingChunk = either.left().get(); +@@ -756,7 +761,8 @@ public class PlayerChunk { + // CraftBukkit start + // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. + if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { +- this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main ++ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main ++ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity + Chunk chunk = (Chunk)either.left().orElse(null); + if (chunk != null) { + playerchunkmap.callbackExecutor.execute(() -> { +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 4c57c9999960f7c4467407a7d5102476a3a9305c..68a0865fb2ef38f3bd2acc648855567a1d39ccc1 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -1270,7 +1270,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + // Paper end + this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); +- }); ++ }).thenComposeAsync((either) -> { // Tuinity start - force competion on the main thread ++ return CompletableFuture.completedFuture(either); ++ }, this.mainInvokingExecutor); ++ // Tuinity end - force competion on the main thread + } + + protected void c(ChunkCoordIntPair chunkcoordintpair) { diff --git a/patches/Tuinity/patches/server/0033-Allow-Entities-to-be-removed-from-a-world-while-tick.patch b/patches/Tuinity/patches/server/0033-Allow-Entities-to-be-removed-from-a-world-while-tick.patch new file mode 100644 index 00000000..a75d3588 --- /dev/null +++ b/patches/Tuinity/patches/server/0033-Allow-Entities-to-be-removed-from-a-world-while-tick.patch @@ -0,0 +1,177 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 18 Jun 2020 10:04:26 -0700 +Subject: [PATCH] Allow Entities to be removed from a world while ticking + +Fixes issues like disconnecting players while ticking them, or +issues where teleporting players across worlds while ticking. + +Also allows us to run mid tick while ticking entities. + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 48446407b70a8384c18eb0567f16dfb17c524312..80b2507414dc603deb0b542e5f5d86969af9d21b 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -61,7 +61,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + public static final BlockPosition a = new BlockPosition(100, 50, 0); + private static final Logger LOGGER = LogManager.getLogger(); +- public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); ++ public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet entitiesForIteration = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(2048, 0.5f, 2048, 0.2, true); // Tuinity - make removing entities while ticking safe + private final Map entitiesByUUID = Maps.newHashMap(); + private final Queue entitiesToAdd = Queues.newArrayDeque(); + public final List players = Lists.newArrayList(); // Paper - private -> public +@@ -85,7 +85,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final PortalTravelAgent portalTravelAgent; + private final TickListServer nextTickListBlock; + private final TickListServer nextTickListFluid; +- private final Set navigators; ++ private final Set navigators; final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigatorsForIteration = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(2048, 0.5f, 2048, 0.2, true); // Tuinity - make removing entities while ticking safe + protected final PersistentRaid persistentRaid; + private final ObjectLinkedOpenHashSet L; + private boolean ticking; +@@ -824,13 +824,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + this.tickingEntities = true; +- ObjectIterator objectiterator = this.entitiesById.int2ObjectEntrySet().iterator(); ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator objectiterator = this.entitiesForIteration.iterator(); // Tuinity + + org.spigotmc.ActivationRange.activateEntities(this); // Spigot + timings.entityTick.startTiming(); // Spigot + while (objectiterator.hasNext()) { +- Entry entry = (Entry) objectiterator.next(); +- Entity entity = (Entity) entry.getValue(); ++ Entity entity = (Entity) objectiterator.next(); // Tuinity + Entity entity1 = entity.getVehicle(); + + /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed +@@ -866,7 +865,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.enter("remove"); + if (entity.dead) { + this.removeEntityFromChunk(entity); +- objectiterator.remove(); ++ this.entitiesById.remove(entity.getId()); // Tuinity + this.unregisterEntity(entity); + } + +@@ -874,6 +873,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + timings.entityTick.stopTiming(); // Spigot + ++ objectiterator.finishedIterating(); // Tuinity + this.tickingEntities = false; + // Paper start + for (java.lang.Runnable run : this.afterEntityTickingTasks) { +@@ -1653,7 +1653,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + Entity entity = (Entity) iterator.next(); + + if (!(entity instanceof EntityPlayer)) { +- if (this.tickingEntities) { ++ if (false && this.tickingEntities) { // Tuinity + throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); + } + +@@ -1681,6 +1681,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + public void unregisterEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot ++ this.entitiesForIteration.remove(entity); // Tuinity + // Paper start - fix entity registration issues + if (entity instanceof EntityComplexPart) { + // Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways +@@ -1747,12 +1748,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.getScoreboard().a(entity); + // CraftBukkit start - SPIGOT-5278 + if (entity instanceof EntityDrowned) { +- this.navigators.remove(((EntityDrowned) entity).navigationWater); +- this.navigators.remove(((EntityDrowned) entity).navigationLand); ++ // Tuinity start ++ this.navigators.remove(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationWater); ++ this.navigators.remove(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationLand); ++ // Tuinity end + } else + // CraftBukkit end + if (entity instanceof EntityInsentient) { +- this.navigators.remove(((EntityInsentient) entity).getNavigation()); ++ // Tuinity start ++ this.navigators.remove(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.remove(((EntityInsentient) entity).getNavigation()); ++ // Tuinity end + } + new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid + entity.valid = false; // CraftBukkit +@@ -1768,7 +1773,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + return; + } + // Paper end +- if (this.tickingEntities) { ++ if (false && this.tickingEntities) { // Tuinity + if (!entity.isQueuedForRegister) { // Paper + this.entitiesToAdd.add(entity); + entity.isQueuedForRegister = true; // Paper +@@ -1776,6 +1781,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } else { + entity.isQueuedForRegister = false; // Paper + this.entitiesById.put(entity.getId(), entity); ++ this.entitiesForIteration.add(entity); // Tuinity + if (entity instanceof EntityEnderDragon) { + EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity).eJ(); + int i = aentitycomplexpart.length; +@@ -1784,6 +1790,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + EntityComplexPart entitycomplexpart = aentitycomplexpart[j]; + + this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart); ++ this.entitiesForIteration.add(entitycomplexpart); // Tuinity + } + } + +@@ -1808,12 +1815,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // this.getChunkProvider().addEntity(entity); // Paper - moved down below valid=true + // CraftBukkit start - SPIGOT-5278 + if (entity instanceof EntityDrowned) { +- this.navigators.add(((EntityDrowned) entity).navigationWater); +- this.navigators.add(((EntityDrowned) entity).navigationLand); ++ // Tuinity start ++ this.navigators.add(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.add(((EntityDrowned) entity).navigationWater); ++ this.navigators.add(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.add(((EntityDrowned) entity).navigationLand); ++ // Tuinity end + } else + // CraftBukkit end + if (entity instanceof EntityInsentient) { +- this.navigators.add(((EntityInsentient) entity).getNavigation()); ++ // Tuinity start ++ this.navigators.add(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.add(((EntityInsentient) entity).getNavigation()); ++ // Tuinity end + } + entity.valid = true; // CraftBukkit + this.getChunkProvider().addEntity(entity); // Paper - from above to be below valid=true +@@ -1829,7 +1840,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + + public void removeEntity(Entity entity) { +- if (this.tickingEntities) { ++ if (false && this.tickingEntities) { // Tuinity + throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("Removing entity while ticking!"))); + } else { + this.removeEntityFromChunk(entity); +@@ -1932,7 +1943,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) { + boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper +- Iterator iterator = this.navigators.iterator(); ++ // Tuinity start ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.navigatorsForIteration.iterator(); ++ try { // Tuinity end + + while (iterator.hasNext()) { + NavigationAbstract navigationabstract = (NavigationAbstract) iterator.next(); +@@ -1941,6 +1954,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { + navigationabstract.b(blockposition); + } + } ++ } finally { // Tuinity start ++ iterator.finishedIterating(); ++ } // Tuinity end + + this.tickingEntities = wasTicking; // Paper + } diff --git a/patches/Tuinity/patches/server/0034-Prevent-unload-calls-removing-tickets-for-sync-loads.patch b/patches/Tuinity/patches/server/0034-Prevent-unload-calls-removing-tickets-for-sync-loads.patch new file mode 100644 index 00000000..9107d9a4 --- /dev/null +++ b/patches/Tuinity/patches/server/0034-Prevent-unload-calls-removing-tickets-for-sync-loads.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 18 Jun 2020 18:23:20 -0700 +Subject: [PATCH] Prevent unload() calls removing tickets for sync loads + + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index b7dd5cebab80bdd444a1e464e2092b345e54064a..65047884a1b5edeef5777e329aa0a1b3f11e293e 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -701,6 +701,8 @@ public class ChunkProviderServer extends IChunkProvider { + Arrays.fill(this.cacheChunk, (Object) null); + } + ++ private long syncLoadCounter; // Tuinity - prevent plugin unloads from removing our ticket ++ + private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) { + // Paper start - add isUrgent - old sig left in place for dirty nms plugins + return getChunkFutureMainThread(i, j, chunkstatus, flag, false); +@@ -719,9 +721,12 @@ public class ChunkProviderServer extends IChunkProvider { + PlayerChunk.State currentChunkState = PlayerChunk.getChunkState(playerchunk.getTicketLevel()); + currentlyUnloading = (oldChunkState.isAtLeast(PlayerChunk.State.BORDER) && !currentChunkState.isAtLeast(PlayerChunk.State.BORDER)); + } ++ final Long identifier; // Tuinity - prevent plugin unloads from removing our ticket + if (flag && !currentlyUnloading) { + // CraftBukkit end + this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); ++ identifier = Long.valueOf(this.syncLoadCounter++); // Tuinity - prevent plugin unloads from removing our ticket ++ this.chunkMapDistance.addTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity - prevent plugin unloads from removing our ticket + if (isUrgent) this.chunkMapDistance.markUrgent(chunkcoordintpair); // Paper + if (this.a(playerchunk, l)) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); +@@ -732,12 +737,20 @@ public class ChunkProviderServer extends IChunkProvider { + playerchunk = this.getChunk(k); + gameprofilerfiller.exit(); + if (this.a(playerchunk, l)) { ++ this.chunkMapDistance.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity + throw (IllegalStateException) SystemUtils.c((Throwable) (new IllegalStateException("No chunk holder after ticket has been added"))); + } + } +- } ++ } else { identifier = null; } // Tuinity - prevent plugin unloads from removing our ticket + // Paper start + CompletableFuture> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); ++ // Tuinity start - prevent plugin unloads from removing our ticket ++ if (flag && !currentlyUnloading) { ++ future.thenAcceptAsync((either) -> { ++ ChunkProviderServer.this.chunkMapDistance.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); ++ }, ChunkProviderServer.this.serverThreadQueue); ++ } ++ // Tuinity end - prevent plugin unloads from removing our ticket + if (isUrgent) { + future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair)); + } +diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java +index ab0417b3897911ba29602d696f4842bfb77cee16..25cff70b45aa2c92a9ffb2cd968ffef5bb1a6c2f 100644 +--- a/src/main/java/net/minecraft/server/TicketType.java ++++ b/src/main/java/net/minecraft/server/TicketType.java +@@ -27,6 +27,7 @@ public class TicketType { + public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper + public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper + public static final TicketType DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads ++ public static final TicketType REQUIRED_LOAD = a("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail + + // Tuinity start - delay chunk unloads + boolean delayUnloadViable = true; diff --git a/patches/Tuinity/patches/server/0035-Optimise-collision-checking-in-player-move-packet-ha.patch b/patches/Tuinity/patches/server/0035-Optimise-collision-checking-in-player-move-packet-ha.patch new file mode 100644 index 00000000..b8b683ad --- /dev/null +++ b/patches/Tuinity/patches/server/0035-Optimise-collision-checking-in-player-move-packet-ha.patch @@ -0,0 +1,136 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 2 Jul 2020 12:02:43 -0700 +Subject: [PATCH] Optimise collision checking in player move packet handling + +Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision + +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index f1be42b5f1d0eb403e6895e8ff1d106861fa4298..4058c1f7ada7d0c9e4ba73a0073b4f94bf410a8f 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -434,12 +434,14 @@ public class PlayerConnection implements PacketListenerPlayIn { + return; + } + +- boolean flag = worldserver.getCubes(entity, entity.getBoundingBox().shrink(0.0625D)); ++ //boolean flag = worldserver.getCubes(entity, entity.getBoundingBox().shrink(0.0625D)); // Tuinity - replace with different checks ++ AxisAlignedBB oldBox = entity.getBoundingBox(); // Tuinity - copy from player movement packet + + d6 = d3 - this.v; + d7 = d4 - this.w - 1.0E-6D; + d8 = d5 - this.x; + entity.move(EnumMoveType.PLAYER, new Vec3D(d6, d7, d8)); ++ boolean didCollide = toX != entity.locX() || toY != entity.locY() || toZ != entity.locZ(); // Tuinity - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... + double d11 = d7; + + d6 = d3 - entity.locX(); +@@ -453,16 +455,25 @@ public class PlayerConnection implements PacketListenerPlayIn { + boolean flag1 = false; + + if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot +- flag1 = true; ++ flag1 = true; // Tuinity - diff on change, this should be moved wrongly + PlayerConnection.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", entity.getDisplayName().getString(), this.player.getDisplayName().getString(), Math.sqrt(d10)); + } + Location curPos = this.getPlayer().getLocation(); // Spigot + + entity.setLocation(d3, d4, d5, f, f1); + player.setLocation(d3, d4, d5, this.player.yaw, this.player.pitch); // CraftBukkit +- boolean flag2 = worldserver.getCubes(entity, entity.getBoundingBox().shrink(0.0625D)); +- +- if (flag && (flag1 || !flag2)) { ++ //boolean flag2 = worldserver.getCubes(entity, entity.getBoundingBox().shrink(0.0625D)); // Tuinity - replace with different checks ++ ++ // Tuinity start - optimise out extra getCubes ++ boolean teleportBack = flag1; // violating this is always a fail ++ if (!teleportBack) { ++ // note: only call after setLocation, or else getBoundingBox is wrong ++ AxisAlignedBB newBox = entity.getBoundingBox(); ++ if (didCollide || !oldBox.equals(newBox)) { ++ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox); ++ } // else: no collision at all detected, why do we care? ++ } ++ if (teleportBack) { // Tuinity end - optimise out extra getCubes + entity.setLocation(d0, d1, d2, f, f1); + player.setLocation(d0, d1, d2, this.player.yaw, this.player.pitch); // CraftBukkit + this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity)); +@@ -1070,7 +1081,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + if (this.teleportPos != null) { +- if (this.e - this.A > 20) { ++ if (false && this.e - this.A > 20) { // Tuinity - this will greatly screw with clients with > 1000ms RTT + this.A = this.e; + this.a(this.teleportPos.x, this.teleportPos.y, this.teleportPos.z, this.player.yaw, this.player.pitch); + } +@@ -1157,7 +1168,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } + +- AxisAlignedBB axisalignedbb = this.player.getBoundingBox(); ++ AxisAlignedBB axisalignedbb = this.player.getBoundingBox(); // Tuinity - diff on change, should be old AABB + + d7 = d4 - this.o; + d8 = d5 - this.p; +@@ -1196,6 +1207,7 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + this.player.move(EnumMoveType.PLAYER, new Vec3D(d7, d8, d9)); ++ boolean didCollide = toX != this.player.locX() || toY != this.player.locY() || toZ != this.player.locZ(); // Tuinity - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... + this.player.setOnGround(packetplayinflying.b()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move + // Paper start - prevent position desync + if (this.teleportPos != null) { +@@ -1215,12 +1227,23 @@ public class PlayerConnection implements PacketListenerPlayIn { + boolean flag1 = false; + + if (!this.player.H() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.playerInteractManager.isCreative() && this.player.playerInteractManager.getGameMode() != EnumGamemode.SPECTATOR) { // Spigot +- flag1 = true; ++ flag1 = true; // Tuinity - diff on change, this should be moved wrongly + PlayerConnection.LOGGER.warn("{} moved wrongly!", this.player.getDisplayName().getString()); + } + + this.player.setLocation(d4, d5, d6, f, f1); +- if (!this.player.noclip && !this.player.isSleeping() && (flag1 && worldserver.getCubes(this.player, axisalignedbb) || this.a((IWorldReader) worldserver, axisalignedbb))) { ++ // Tuinity start - optimise out extra getCubes ++ // Original for reference: ++ // boolean teleportBack = flag1 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb)); ++ boolean teleportBack = flag1; // violating this is always a fail ++ if (!this.player.noclip && !this.player.isSleeping() && !teleportBack) { ++ AxisAlignedBB newBox = this.player.getBoundingBox(); ++ if (didCollide || !axisalignedbb.equals(newBox)) { ++ // note: only call after setLocation, or else getBoundingBox is wrong ++ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox); ++ } // else: no collision at all detected, why do we care? ++ } ++ if (!this.player.noclip && !this.player.isSleeping() && teleportBack) { // Tuinity end - optimise out extra getCubes + this.a(d0, d1, d2, f, f1); + } else { + // CraftBukkit start - fire PlayerMoveEvent +@@ -1307,6 +1330,26 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } + ++ // Tuinity start - optimise out extra getCubes ++ private boolean hasNewCollision(final WorldServer world, final Entity entity, final AxisAlignedBB oldBox, final AxisAlignedBB newBox) { ++ final List collisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList(); ++ try { ++ world.getCollisions(entity, newBox, collisions, true); ++ ++ for (int i = 0, len = collisions.size(); i < len; ++i) { ++ final AxisAlignedBB box = collisions.get(i); ++ if (!box.voxelShapeIntersect(oldBox)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } finally { ++ com.tuinity.tuinity.util.CachedLists.returnTempCollisionList(collisions); ++ } ++ } ++ // Tuinity end - optimise out extra getCubes ++ + private boolean a(IWorldReader iworldreader, AxisAlignedBB axisalignedbb) { + Stream stream = iworldreader.d(this.player, this.player.getBoundingBox().shrink(9.999999747378752E-6D), (entity) -> { + return true; diff --git a/patches/Tuinity/patches/server/0036-Manually-inline-methods-in-BlockPosition.patch b/patches/Tuinity/patches/server/0036-Manually-inline-methods-in-BlockPosition.patch new file mode 100644 index 00000000..9a48fb6c --- /dev/null +++ b/patches/Tuinity/patches/server/0036-Manually-inline-methods-in-BlockPosition.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Jul 2020 22:48:48 -0700 +Subject: [PATCH] Manually inline methods in BlockPosition + + +diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java +index 6b655b744d31d9660c7521ab596b27bcd92f4d58..e811295b4d6afcd920f60e0ce5440e43300d9085 100644 +--- a/src/main/java/net/minecraft/server/BaseBlockPosition.java ++++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java +@@ -16,9 +16,9 @@ public class BaseBlockPosition implements Comparable { + return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); + }); + public static final BaseBlockPosition ZERO = new BaseBlockPosition(0, 0, 0); +- private int a;public final void setX(final int x) { this.a = x; } // Paper - OBFHELPER +- private int b;public final void setY(final int y) { this.b = y; } // Paper - OBFHELPER +- private int e;public final void setZ(final int z) { this.e = z; } // Paper - OBFHELPER ++ protected int a; // Paper - OBFHELPER // Tuinity - private->protected - diff on change, this is the x coordinate - Also revert the decision to expose set on an immutable type ++ protected int b; // Paper - OBFHELPER // Tuinity - private->protected - diff on change, this is the y coordinate - Also revert the decision to expose set on an immutable type ++ protected int e; // Paper - OBFHELPER // Tuinity - private->protected - diff on change, this is the z coordinate - Also revert the decision to expose set on an immutable type + + // Paper start + public boolean isValidLocation() { +@@ -71,15 +71,15 @@ public class BaseBlockPosition implements Comparable { + return this.e; + } + +- public void o(int i) { // Paper - protected -> public ++ protected void o_unused(int i) { // Paper - protected -> public // Tuinity - not needed here - Also revert the decision to expose set on an immutable type + this.a = i; + } + +- public void p(int i) { // Paper - protected -> public ++ protected void p_unused(int i) { // Paper - protected -> public // Tuinity - not needed here - Also revert the decision to expose set on an immutable type + this.b = i; + } + +- public void q(int i) { // Paper - protected -> public ++ protected void q_unused(int i) { // Paper - protected -> public // Tuinity - not needed here - Also revert the decision to expose set on an immutable type + this.e = i; + } + +diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java +index 2d887af902a33b0e28d8f0a6ac2e59c815a7856e..2291135eaef64c403183724cb6e413cd7e472672 100644 +--- a/src/main/java/net/minecraft/server/BlockPosition.java ++++ b/src/main/java/net/minecraft/server/BlockPosition.java +@@ -449,10 +449,10 @@ public class BlockPosition extends BaseBlockPosition { + } + + public final BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER +- public BlockPosition.MutableBlockPosition d(int i, int j, int k) { +- this.o(i); +- this.p(j); +- this.q(k); ++ public final BlockPosition.MutableBlockPosition d(int i, int j, int k) { // Tuinity ++ ((BaseBlockPosition)this).a = i; // Tuinity - force inline ++ ((BaseBlockPosition)this).b = j; // Tuinity - force inline ++ ((BaseBlockPosition)this).e = k; // Tuinity - force inline + return this; + } + +@@ -462,12 +462,18 @@ public class BlockPosition extends BaseBlockPosition { + } + + public final BlockPosition.MutableBlockPosition setValues(final BaseBlockPosition baseblockposition) { return this.g(baseblockposition); } // Paper - OBFHELPER +- public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { +- return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); ++ public final BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { // Tuinity ++ ((BaseBlockPosition)this).a = baseblockposition.a; // Tuinity - force inline ++ ((BaseBlockPosition)this).b = baseblockposition.b; // Tuinity - force inline ++ ((BaseBlockPosition)this).e = baseblockposition.e; // Tuinity - force inline ++ return this; + } + +- public BlockPosition.MutableBlockPosition g(long i) { +- return this.d(b(i), c(i), d(i)); ++ public final BlockPosition.MutableBlockPosition g(long i) { // Tuinity ++ ((BaseBlockPosition)this).a = (int)(i >> 38); // Tuinity - force inline ++ ((BaseBlockPosition)this).b = (int)((i << 52) >> 52); // Tuinity - force inline ++ ((BaseBlockPosition)this).e = (int)((i << 26) >> 38); // Tuinity - force inline ++ return this; + } + + public BlockPosition.MutableBlockPosition a(EnumAxisCycle enumaxiscycle, int i, int j, int k) { +@@ -482,8 +488,11 @@ public class BlockPosition extends BaseBlockPosition { + return this.d(baseblockposition.getX() + i, baseblockposition.getY() + j, baseblockposition.getZ() + k); + } + +- public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) { +- return this.c(enumdirection, 1); ++ public final BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) { // Tuinity ++ ((BaseBlockPosition)this).a += enumdirection.getAdjacentX(); // Tuinity - force inline ++ ((BaseBlockPosition)this).b += enumdirection.getAdjacentY(); // Tuinity - force inline ++ ((BaseBlockPosition)this).e += enumdirection.getAdjacentZ(); // Tuinity - force inline ++ return this; + } + + public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection, int i) { +@@ -511,21 +520,30 @@ public class BlockPosition extends BaseBlockPosition { + } + } + +- /* // Paper start - comment out useless overrides @Override +- @Override +- public void o(int i) { +- super.o(i); ++ // Tuinity start ++ // only expose set on the mutable blockpos ++ public final void setX(int value) { ++ ((BaseBlockPosition)this).a = value; ++ } ++ public final void setY(int value) { ++ ((BaseBlockPosition)this).b = value; ++ } ++ public final void setZ(int value) { ++ ((BaseBlockPosition)this).e = value; + } + +- @Override +- public void p(int i) { +- super.p(i); ++ public final void o(int i) { ++ ((BaseBlockPosition)this).a = i; // need cast thanks to name conflict ++ } ++ ++ public final void p(int i) { ++ ((BaseBlockPosition)this).b = i; + } + +- public void q(int i) { +- super.q(i); ++ public final void q(int i) { ++ ((BaseBlockPosition)this).e = i; + } +- */ // Paper end ++ // Tuinity end + + @Override + public BlockPosition immutableCopy() { diff --git a/patches/Tuinity/patches/server/0037-Separate-lookup-locking-from-state-access-in-UserCac.patch b/patches/Tuinity/patches/server/0037-Separate-lookup-locking-from-state-access-in-UserCac.patch new file mode 100644 index 00000000..29b37f21 --- /dev/null +++ b/patches/Tuinity/patches/server/0037-Separate-lookup-locking-from-state-access-in-UserCac.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 11 Jul 2020 05:09:28 -0700 +Subject: [PATCH] Separate lookup locking from state access in UserCache + +Prevent lookups from stalling simple state access/write calls + +diff --git a/src/main/java/net/minecraft/server/UserCache.java b/src/main/java/net/minecraft/server/UserCache.java +index 2484293b12d9ec88b8a2570aa853a12f0d858193..1496c43fc9487caf6ddb3782a9d1c79ef6ca1e94 100644 +--- a/src/main/java/net/minecraft/server/UserCache.java ++++ b/src/main/java/net/minecraft/server/UserCache.java +@@ -49,6 +49,11 @@ public class UserCache { + private final File g; + private final AtomicLong h = new AtomicLong(); + ++ // Tuinity start ++ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); ++ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); ++ // Tuinity end ++ + public UserCache(GameProfileRepository gameprofilerepository, File file) { + this.e = gameprofilerepository; + this.g = file; +@@ -56,6 +61,7 @@ public class UserCache { + } + + private void a(UserCache.UserCacheEntry usercache_usercacheentry) { ++ try { this.stateLock.lock(); // Tuinity - allow better concurrency + GameProfile gameprofile = usercache_usercacheentry.a(); + + usercache_usercacheentry.a(this.d()); +@@ -70,6 +76,7 @@ public class UserCache { + if (uuid != null) { + this.d.put(uuid, usercache_usercacheentry); + } ++ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency + + } + +@@ -107,7 +114,7 @@ public class UserCache { + } + + public void saveProfile(GameProfile gameprofile) { a(gameprofile); } // Paper - OBFHELPER +- public synchronized void a(GameProfile gameprofile) { // Paper - synchronize ++ public void a(GameProfile gameprofile) { // Paper - synchronize // Tuinity - allow better concurrency + Calendar calendar = Calendar.getInstance(); + + calendar.setTime(new Date()); +@@ -124,8 +131,9 @@ public class UserCache { + } + + @Nullable +- public synchronized GameProfile getProfile(String s) { // Paper - synchronize ++ public GameProfile getProfile(String s) { // Paper - synchronize // Tuinity start - allow better concurrency + String s1 = s.toLowerCase(Locale.ROOT); ++ boolean stateLocked = true; try { this.stateLock.lock(); // Tuinity - allow better concurrency + UserCache.UserCacheEntry usercache_usercacheentry = (UserCache.UserCacheEntry) this.c.get(s1); + boolean flag = false; + +@@ -139,10 +147,14 @@ public class UserCache { + GameProfile gameprofile; + + if (usercache_usercacheentry != null) { ++ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency + usercache_usercacheentry.a(this.d()); + gameprofile = usercache_usercacheentry.a(); + } else { ++ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency ++ try { this.lookupLock.lock(); // Tuinity - allow better concurrency + gameprofile = a(this.e, s); // Spigot - use correct case for offline players ++ } finally { this.lookupLock.unlock(); } // Tuinity - allow better concurrency + if (gameprofile != null) { + this.a(gameprofile); + flag = false; +@@ -154,6 +166,7 @@ public class UserCache { + } + + return gameprofile; ++ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Tuinity - allow better concurrency + } + + // Paper start +@@ -287,7 +300,9 @@ public class UserCache { + } + + private Stream a(int i) { ++ try { this.stateLock.lock(); // Tuinity - allow better concurrency + return ImmutableList.copyOf(this.d.values()).stream().sorted(Comparator.comparing(UserCache.UserCacheEntry::c).reversed()).limit((long) i); ++ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency + } + + private static JsonElement a(UserCache.UserCacheEntry usercache_usercacheentry, DateFormat dateformat) { diff --git a/patches/Tuinity/patches/server/0038-Distance-manager-tick-timings.patch b/patches/Tuinity/patches/server/0038-Distance-manager-tick-timings.patch new file mode 100644 index 00000000..cb2708ec --- /dev/null +++ b/patches/Tuinity/patches/server/0038-Distance-manager-tick-timings.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 18 Jul 2020 16:03:57 -0700 +Subject: [PATCH] Distance manager tick timings + +Recently this has been taking up more time, so add a timings to +really figure out how much. + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 7991c66a8fe7ee9725ab75fb80d1363cd7348532..68ab5ccb2fcfe1de0503c9336572f28e11832b2d 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -44,6 +44,7 @@ public final class MinecraftTimings { + public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + + public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Tuinity - add timings for scoreboard search ++ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Tuinity - add timings for distance manager + + private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 65047884a1b5edeef5777e329aa0a1b3f11e293e..c8806d4cb24e32abdb2f800ddc36ac78c4a0328a 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -807,6 +807,7 @@ public class ChunkProviderServer extends IChunkProvider { + + public boolean tickDistanceManager() { // Paper - private -> public + if (chunkMapDistance.delayDistanceManagerTick) return false; // Paper ++ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Tuinity - add timings for distance manager + boolean flag = this.chunkMapDistance.a(this.playerChunkMap); + boolean flag1 = this.playerChunkMap.b(); + +@@ -816,6 +817,7 @@ public class ChunkProviderServer extends IChunkProvider { + this.clearCache(); + return true; + } ++ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Tuinity - add timings for distance manager + } + + public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER diff --git a/patches/Tuinity/patches/server/0039-Name-craft-scheduler-threads-according-to-the-plugin.patch b/patches/Tuinity/patches/server/0039-Name-craft-scheduler-threads-according-to-the-plugin.patch new file mode 100644 index 00000000..21da2894 --- /dev/null +++ b/patches/Tuinity/patches/server/0039-Name-craft-scheduler-threads-according-to-the-plugin.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 19 Jul 2020 15:17:01 -0700 +Subject: [PATCH] Name craft scheduler threads according to the plugin using + them + +Provides quick access to culprits running far more threads than +they should be + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +index fd32d1450a6a2ede3405be7d31697cd16957f553..c38e514b004a4684026d5a89c606399a4fd7efe1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +@@ -25,6 +25,10 @@ class CraftAsyncTask extends CraftTask { + @Override + public void run() { + final Thread thread = Thread.currentThread(); ++ // Tuinity start - name threads according to running plugin ++ final String nameBefore = thread.getName(); ++ thread.setName(nameBefore + " - " + this.getOwner().getName()); try { ++ // Tuinity end - name threads according to running plugin + synchronized (workers) { + if (getPeriod() == CraftTask.CANCEL) { + // Never continue running after cancelled. +@@ -92,6 +96,7 @@ class CraftAsyncTask extends CraftTask { + } + } + } ++ } finally { thread.setName(nameBefore); } // Tuinity - name worker thread according + } + + LinkedList getWorkers() { diff --git a/patches/Tuinity/patches/server/0040-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/Tuinity/patches/server/0040-Fix-swamp-hut-cat-generation-deadlock.patch new file mode 100644 index 00000000..5e20e297 --- /dev/null +++ b/patches/Tuinity/patches/server/0040-Fix-swamp-hut-cat-generation-deadlock.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 13 Aug 2020 18:56:36 -0700 +Subject: [PATCH] Fix swamp hut cat generation deadlock + +The worldgen thread will attempt to get structure references +via the world's getChunkAt method, which is fine if the gen is +not cancelled - but if the chunk was unloaded, the call will block +indefinitely. Instead of using the world state, we use the already +supplied generatoraccess which will always have the chunk available. + +diff --git a/src/main/java/net/minecraft/server/EntityCat.java b/src/main/java/net/minecraft/server/EntityCat.java +index e80ec303198abade19645267e24cd5a4a8b75d70..35f511c398795a0edeb5fe6d802f2a2bf754bf3a 100644 +--- a/src/main/java/net/minecraft/server/EntityCat.java ++++ b/src/main/java/net/minecraft/server/EntityCat.java +@@ -292,7 +292,7 @@ public class EntityCat extends EntityTameableAnimal { + + WorldServer worldserver = worldaccess.getMinecraftWorld(); + +- if (worldserver instanceof WorldServer && ((WorldServer) worldserver).getStructureManager().a(this.getChunkCoordinates(), true, StructureGenerator.SWAMP_HUT).e()) { ++ if (worldserver instanceof WorldServer && ((WorldServer) worldserver).getStructureManager().getStructureStarts(this.getChunkCoordinates(), true, StructureGenerator.SWAMP_HUT, worldaccess).e()) { // Tuinity - fix deadlock on chunk gen + this.setCatType(10); + this.setPersistent(); + } +diff --git a/src/main/java/net/minecraft/server/StructureManager.java b/src/main/java/net/minecraft/server/StructureManager.java +index f199368a6d78b0cd52f11ca2c8509d729b918852..2598ae3710d46c2cfd2be5d6be2a56e59ceef6ea 100644 +--- a/src/main/java/net/minecraft/server/StructureManager.java ++++ b/src/main/java/net/minecraft/server/StructureManager.java +@@ -35,8 +35,13 @@ public class StructureManager { + + // Paper start - remove structure streams + public java.util.List> getFeatureStarts(SectionPosition sectionPosition, StructureGenerator structureGenerator) { ++ // Tuinity start - add world parameter ++ return this.getFeatureStarts(sectionPosition, structureGenerator, null); ++ } ++ public java.util.List> getFeatureStarts(SectionPosition sectionPosition, StructureGenerator structureGenerator, IWorldReader world) { ++ // Tuinity end - add world parameter + java.util.List> list = new ObjectArrayList<>(); +- for (Long curLong: getLevel().getChunkAt(sectionPosition.a(), sectionPosition.c(), ChunkStatus.STRUCTURE_REFERENCES).b(structureGenerator)) { ++ for (Long curLong: (world == null ? getLevel() : world).getChunkAt(sectionPosition.a(), sectionPosition.c(), ChunkStatus.STRUCTURE_REFERENCES).b(structureGenerator)) { // Tuinity - fix deadlock on world gen - chunk can be unloaded while generating, so we should be using the generator's regionlimitedaccess so we always get the chunk + SectionPosition sectionPosition1 = SectionPosition.a(new ChunkCoordIntPair(curLong), 0); + StructureStart structurestart = a(sectionPosition1, structureGenerator, getLevel().getChunkAt(sectionPosition1.a(), sectionPosition1.c(), ChunkStatus.STRUCTURE_STARTS)); + if (structurestart != null && structurestart.e()) { +@@ -65,8 +70,12 @@ public class StructureManager { + } + + public StructureStart a(BlockPosition blockposition, boolean flag, StructureGenerator structuregenerator) { ++ // Tuinity start - add world parameter ++ return this.getStructureStarts(blockposition,flag, structuregenerator, null); ++ } ++ public StructureStart getStructureStarts(BlockPosition blockposition, boolean flag, StructureGenerator structuregenerator, IWorldReader world) { + // Paper start - remove structure streams +- for (StructureStart structurestart : getFeatureStarts(SectionPosition.a(blockposition), structuregenerator)) { ++ for (StructureStart structurestart : getFeatureStarts(SectionPosition.a(blockposition), structuregenerator, world)) { // Tuinity end - add world parameter + if (structurestart.c().b(blockposition)) { + if (!flag) { + return structurestart; diff --git a/patches/Tuinity/patches/server/0041-Range-check-flag-dirty-calls-in-PlayerChunk.patch b/patches/Tuinity/patches/server/0041-Range-check-flag-dirty-calls-in-PlayerChunk.patch new file mode 100644 index 00000000..c4f70e71 --- /dev/null +++ b/patches/Tuinity/patches/server/0041-Range-check-flag-dirty-calls-in-PlayerChunk.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 19 Aug 2020 10:57:08 -0700 +Subject: [PATCH] Range check flag dirty calls in PlayerChunk + +Simply return. + +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 52e586a7e193b0012c9939554376f6e8f717091e..a072208bcd92ffa9ed47757de291b82be2e71e8e 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -362,7 +362,7 @@ public class PlayerChunk { + if (!blockposition.isValidLocation()) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks + Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + +- if (chunk != null) { ++ if (chunk != null && (blockposition.getY() >= 0 && blockposition.getY() <= 255)) { // Tuinity - updates cannot happen in sections that don't exist + byte b0 = (byte) SectionPosition.a(blockposition.getY()); + + if (b0 < 0 || b0 >= this.dirtyBlocks.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 +@@ -421,7 +421,7 @@ public class PlayerChunk { + this.a(world, blockposition, iblockdata); + } else { + ChunkSection chunksection = chunk.getSections()[sectionposition.getY()]; +- if (chunksection == null) chunksection = new ChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found ++ //if (chunksection == null) chunksection = new ChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found // Tuinity - handled better by spigot + PacketPlayOutMultiBlockChange packetplayoutmultiblockchange = new PacketPlayOutMultiBlockChange(sectionposition, shortset, chunksection, this.x); + + this.a(packetplayoutmultiblockchange, false); diff --git a/patches/Tuinity/patches/server/0042-Optimise-tab-complete.patch b/patches/Tuinity/patches/server/0042-Optimise-tab-complete.patch new file mode 100644 index 00000000..9a612f17 --- /dev/null +++ b/patches/Tuinity/patches/server/0042-Optimise-tab-complete.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 12 Sep 2020 16:55:21 -0700 +Subject: [PATCH] Optimise tab complete + +Some of the toLowerCase calls can be expensive. + +diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +index 103576715ef6ae4df4b216ae9ae31b6fb1086bd5..e8fdbe7b8d8192a3247d98534e78ede7a7314a91 100644 +--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java ++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +@@ -590,10 +590,11 @@ public class CommandDispatcher { + final String truncatedInput = fullInput.substring(0, cursor); + @SuppressWarnings("unchecked") final CompletableFuture[] futures = new CompletableFuture[parent.getChildren().size()]; + int i = 0; ++ final String remainingLower = truncatedInput.substring(start).toLowerCase(); // Tuinity + for (final CommandNode node : parent.getChildren()) { + CompletableFuture future = Suggestions.empty(); + try { +- future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start)); ++ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start, remainingLower)); // Tuinity + } catch (final CommandSyntaxException ignored) { + } + futures[i++] = future; +diff --git a/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java b/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java +index cb993ca102402d9c19ea9fa04e5db09c21205896..849686f7b2a8b0044f7cd14c8c2e401e80966462 100644 +--- a/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java ++++ b/src/main/java/com/mojang/brigadier/arguments/BoolArgumentType.java +@@ -34,10 +34,10 @@ public class BoolArgumentType implements ArgumentType { + + @Override + public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { +- if ("true".startsWith(builder.getRemaining().toLowerCase())) { ++ if ("true".startsWith(builder.getRemainingLowercase())) { // Tuinity + builder.suggest("true"); + } +- if ("false".startsWith(builder.getRemaining().toLowerCase())) { ++ if ("false".startsWith(builder.getRemainingLowercase())) { // Tuinity + builder.suggest("false"); + } + return builder.buildFuture(); +diff --git a/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java b/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java +index bc0024adb804ac055a4f8afb7f85d00ec13931e9..0343f6663c450c3f0d9c57d817eef9c979055939 100644 +--- a/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java ++++ b/src/main/java/com/mojang/brigadier/suggestion/SuggestionsBuilder.java +@@ -14,9 +14,16 @@ public class SuggestionsBuilder { + private final String input; + private final int start; + private final String remaining; ++ private String remainingLowercase; public final String getRemainingLowercase() { return this.remainingLowercase == null ? this.remainingLowercase = this.remaining.toLowerCase() : this.remainingLowercase; } // Tuinity + private final List result = new ArrayList<>(); + + public SuggestionsBuilder(final String input, final int start) { ++ // Tuinity start ++ this(input, start, null); ++ } ++ public SuggestionsBuilder(final String input, final int start, final String remainingLowercase) { ++ this.remainingLowercase = remainingLowercase; ++ // Tuinity end + this.input = input; + this.start = start; + this.remaining = input.substring(start); +diff --git a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java +index 7720578796e28d28e8c0c9aa40155cd205c17d54..e5db29d4cadb5702c7d06b0b6e2d05586a652ec8 100644 +--- a/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/LiteralCommandNode.java +@@ -20,11 +20,11 @@ import java.util.concurrent.CompletableFuture; + import java.util.function.Predicate; + + public class LiteralCommandNode extends CommandNode { +- private final String literal; ++ private final String literal; private final String literalLower; // Tuinity + + public LiteralCommandNode(final String literal, final Command command, final Predicate requirement, final CommandNode redirect, final RedirectModifier modifier, final boolean forks) { + super(command, requirement, redirect, modifier, forks); +- this.literal = literal; ++ this.literal = literal; this.literalLower = this.literal.toLowerCase(); // Tuinity + } + + public String getLiteral() { +@@ -66,7 +66,7 @@ public class LiteralCommandNode extends CommandNode { + + @Override + public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { +- if (literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) { ++ if (literalLower.startsWith(builder.getRemainingLowercase())) { // Tuinity + return builder.suggest(literal).buildFuture(); + } else { + return Suggestions.empty(); diff --git a/patches/Tuinity/patches/server/0043-Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/Tuinity/patches/server/0043-Do-not-allow-ticket-level-changes-while-unloading-pl.patch new file mode 100644 index 00000000..53ef6c1f --- /dev/null +++ b/patches/Tuinity/patches/server/0043-Do-not-allow-ticket-level-changes-while-unloading-pl.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 19 Sep 2020 15:29:16 -0700 +Subject: [PATCH] Do not allow ticket level changes while unloading + playerchunks + +Sync loading the chunk at this stage would cause it to load +older data, as well as screwing our region state. + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index c8806d4cb24e32abdb2f800ddc36ac78c4a0328a..a44fbfaf42170e57729ecbc23e034d8745d45785 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -807,6 +807,7 @@ public class ChunkProviderServer extends IChunkProvider { + + public boolean tickDistanceManager() { // Paper - private -> public + if (chunkMapDistance.delayDistanceManagerTick) return false; // Paper ++ if (this.playerChunkMap.unloadingPlayerChunk) { MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Tuinity + co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Tuinity - add timings for distance manager + boolean flag = this.chunkMapDistance.a(this.playerChunkMap); + boolean flag1 = this.playerChunkMap.b(); +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 68a0865fb2ef38f3bd2acc648855567a1d39ccc1..83f070229098ad31b8ae65ffcebe52886ef2884d 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -775,6 +775,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + @Nullable + private PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Chunk holder update"); // Tuinity ++ if (this.unloadingPlayerChunk) { MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Tuinity + if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) { + return playerchunk; + } else { +@@ -1032,6 +1033,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + // Paper end + ++ boolean unloadingPlayerChunk = false; // Tuinity - do not allow ticket level changes while unloading chunks ++ + private void a(long i, PlayerChunk playerchunk) { + CompletableFuture completablefuture = playerchunk.getChunkSave(); + Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error +@@ -1040,6 +1043,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + if (completablefuture1 != completablefuture) { + this.a(i, playerchunk); + } else { ++ // Tuinity start - do not allow ticket level changes while unloading chunks ++ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload"); ++ boolean unloadingBefore = this.unloadingPlayerChunk; ++ this.unloadingPlayerChunk = true; ++ try { ++ // Tuinity end - do not allow ticket level changes while unloading chunks + // Tuinity start + boolean removed; + if ((removed = this.pendingUnload.remove(i, playerchunk)) && ichunkaccess != null) { // Tuinity end +@@ -1067,6 +1076,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null); + } + if (removed) this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity ++ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks + + } + }; diff --git a/patches/Tuinity/patches/server/0044-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/Tuinity/patches/server/0044-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch new file mode 100644 index 00000000..5f742b05 --- /dev/null +++ b/patches/Tuinity/patches/server/0044-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Sep 2020 16:10:49 -0700 +Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded + chunks + +Tux did some profiling some time ago and showed that the +previous getChunkAt method which had inlined logic for loaded +chunks did get inlined, but the standard CPS.getChunkAt +method was not inlined. + +Paper recently reverted this optimisation, so it's been reintroduced +here. + +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 9d9ff42a4572da8f62cc9fc5f11053b578858767..6c3f496ba6c0c6c4911e2592ded27df08ae54b14 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -289,6 +289,15 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + @Override + public final Chunk getChunkAt(int i, int j) { // Paper - final to help inline ++ // Tuinity start - make sure loaded chunks get the inlined variant of this function ++ ChunkProviderServer cps = ((WorldServer)this).chunkProvider; ++ if (cps.serverThread == Thread.currentThread()) { ++ Chunk ifLoaded = cps.getChunkAtIfLoadedMainThread(i, j); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ } ++ // Tuinity end - make sure loaded chunks get the inlined variant of this function + return (Chunk) this.getChunkAt(i, j, ChunkStatus.FULL, true); // Paper - avoid a method jump + } + diff --git a/patches/Tuinity/patches/server/0045-Add-packet-limiter-config.patch b/patches/Tuinity/patches/server/0045-Add-packet-limiter-config.patch new file mode 100644 index 00000000..ae687484 --- /dev/null +++ b/patches/Tuinity/patches/server/0045-Add-packet-limiter-config.patch @@ -0,0 +1,195 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 30 Oct 2020 22:37:16 -0700 +Subject: [PATCH] Add packet limiter config + +Example config: +packet-limiter: + kick-message: '&cSent too many packets' + limits: + all: + interval: 7.0 + max-packet-rate: 500.0 + PacketPlayInAutoRecipe: + interval: 4.0 + max-packet-rate: 5.0 + action: DROP + +all section refers to all incoming packets, the action for all is +hard coded to KICK. + +For specific limits, the section name is the class's name, +and an action can be defined: DROP or KICK + +If interval or rate are less-than 0, the limit is ignored + +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index a302cda14aa2dd6550cca03b07be21cdcb993061..03a59aabc2a35daf7eee899967b569a8ac3b632e 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -1,6 +1,7 @@ + package com.tuinity.tuinity.config; + + import com.destroystokyo.paper.util.SneakyThrow; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.TicketType; + import org.bukkit.Bukkit; + import org.bukkit.configuration.ConfigurationSection; +@@ -139,6 +140,89 @@ public final class TuinityConfig { + lagCompensateBlockBreaking = TuinityConfig.getBoolean("lag-compensate-block-breaking", true); + } + ++ public static final class PacketLimit { ++ public final double packetLimitInterval; ++ public final double maxPacketRate; ++ public final ViolateAction violateAction; ++ ++ public PacketLimit(final double packetLimitInterval, final double maxPacketRate, final ViolateAction violateAction) { ++ this.packetLimitInterval = packetLimitInterval; ++ this.maxPacketRate = maxPacketRate; ++ this.violateAction = violateAction; ++ } ++ ++ public static enum ViolateAction { ++ KICK, DROP; ++ } ++ } ++ ++ public static String kickMessage; ++ public static PacketLimit allPacketsLimit; ++ public static java.util.Map>, PacketLimit> packetSpecificLimits = new java.util.HashMap<>(); ++ ++ private static void packetLimiter() { ++ packetSpecificLimits.clear(); ++ kickMessage = org.bukkit.ChatColor.translateAlternateColorCodes('&', TuinityConfig.getString("packet-limiter.kick-message", "&cSent too many packets")); ++ allPacketsLimit = new PacketLimit( ++ TuinityConfig.getDouble("packet-limiter.limits.all.interval", 7.0), ++ TuinityConfig.getDouble("packet-limiter.limits.all.max-packet-rate", 500.0), ++ PacketLimit.ViolateAction.KICK ++ ); ++ if (allPacketsLimit.maxPacketRate <= 0.0 || allPacketsLimit.packetLimitInterval <= 0.0) { ++ allPacketsLimit = null; ++ } ++ final ConfigurationSection section = TuinityConfig.config.getConfigurationSection("packet-limiter.limits"); ++ ++ // add default packets ++ ++ // auto recipe limiting ++ TuinityConfig.getDouble("packet-limiter.limits." + ++ net.minecraft.server.PacketPlayInAutoRecipe.class.getSimpleName() + ".interval", 4.0); ++ TuinityConfig.getDouble("packet-limiter.limits." + ++ net.minecraft.server.PacketPlayInAutoRecipe.class.getSimpleName() + ".max-packet-rate", 5.0); ++ TuinityConfig.getString("packet-limiter.limits." + ++ net.minecraft.server.PacketPlayInAutoRecipe.class.getSimpleName() + ".action", PacketLimit.ViolateAction.DROP.name()); ++ ++ for (final String packetClassName : section.getKeys(false)) { ++ if (packetClassName.equals("all")) { ++ continue; ++ } ++ final Class packetClazz; ++ ++ try { ++ packetClazz = Class.forName("net.minecraft.server." + packetClassName); ++ } catch (final ClassNotFoundException ex) { ++ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update tuinity.yml"); ++ continue; ++ } ++ ++ if (!net.minecraft.server.Packet.class.isAssignableFrom(packetClazz)) { ++ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update tuinity.yml"); ++ continue; ++ } ++ ++ if (!(section.get(packetClassName.concat(".interval")) instanceof Number) || !(section.get(packetClassName.concat(".max-packet-rate")) instanceof Number)) { ++ throw new RuntimeException("Packet limit setting " + packetClassName + " is missing interval or max-packet-rate!"); ++ } ++ ++ final String actionString = section.getString(packetClassName.concat(".action"), "KICK"); ++ PacketLimit.ViolateAction action = PacketLimit.ViolateAction.KICK; ++ for (PacketLimit.ViolateAction test : PacketLimit.ViolateAction.values()) { ++ if (actionString.equalsIgnoreCase(test.name())) { ++ action = test; ++ break; ++ } ++ } ++ ++ final double interval = section.getDouble(packetClassName.concat(".interval")); ++ final double rate = section.getDouble(packetClassName.concat(".max-packet-rate")); ++ ++ if (interval > 0.0 && rate > 0.0) { ++ packetSpecificLimits.put((Class)packetClazz, new PacketLimit(interval, rate, action)); ++ } ++ } ++ } ++ + public static final class WorldConfig { + + public final String worldName; +diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java +index 39f19c82bbd3c17c64c8b2ed06727a95d28a4674..c918eba753180670a22e25afe58307cd2d289952 100644 +--- a/src/main/java/net/minecraft/server/NetworkManager.java ++++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -178,8 +178,63 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + if (MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot + } + ++ // Tuinity start - packet limiter ++ protected final Object PACKET_LIMIT_LOCK = new Object(); ++ protected final com.tuinity.tuinity.util.IntervalledCounter allPacketCounts = com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit != null ? new com.tuinity.tuinity.util.IntervalledCounter( ++ (long)(com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit.packetLimitInterval * 1.0e9) ++ ) : null; ++ protected final java.util.Map>, com.tuinity.tuinity.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); ++ ++ private boolean stopReadingPackets; ++ private void killForPacketSpam() { ++ this.sendPacket(new PacketPlayOutKickDisconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.tuinity.tuinity.config.TuinityConfig.kickMessage, true)[0]), (future) -> { ++ this.close(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.tuinity.tuinity.config.TuinityConfig.kickMessage, true)[0]); ++ }); ++ this.stopReading(); ++ this.stopReadingPackets = true; ++ } ++ // Tuinity end - packet limiter + protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) throws Exception { + if (this.channel.isOpen()) { ++ // Tuinity start - packet limiter ++ if (this.stopReadingPackets) { ++ return; ++ } ++ if (this.allPacketCounts != null || ++ com.tuinity.tuinity.config.TuinityConfig.packetSpecificLimits.containsKey(packet.getClass())) { ++ long time = System.nanoTime(); ++ synchronized (PACKET_LIMIT_LOCK) { ++ if (this.allPacketCounts != null) { ++ this.allPacketCounts.updateAndAdd(1, time); ++ if (this.allPacketCounts.getRate() >= com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit.maxPacketRate) { ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ ++ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { ++ com.tuinity.tuinity.config.TuinityConfig.PacketLimit packetSpecificLimit = ++ com.tuinity.tuinity.config.TuinityConfig.packetSpecificLimits.get(check); ++ if (packetSpecificLimit == null) { ++ continue; ++ } ++ com.tuinity.tuinity.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { ++ return new com.tuinity.tuinity.util.IntervalledCounter((long)(packetSpecificLimit.packetLimitInterval * 1.0e9)); ++ }); ++ counter.updateAndAdd(1, time); ++ if (counter.getRate() >= packetSpecificLimit.maxPacketRate) { ++ switch (packetSpecificLimit.violateAction) { ++ case DROP: ++ return; ++ case KICK: ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ } ++ } ++ } ++ // Tuinity end - packet limiter + try { + a(packet, this.packetListener); + } catch (CancelledPacketHandleException cancelledpackethandleexception) { diff --git a/patches/Tuinity/patches/server/0046-Optimise-closest-entity-lookup-used-by-AI-goals.patch b/patches/Tuinity/patches/server/0046-Optimise-closest-entity-lookup-used-by-AI-goals.patch new file mode 100644 index 00000000..c013768f --- /dev/null +++ b/patches/Tuinity/patches/server/0046-Optimise-closest-entity-lookup-used-by-AI-goals.patch @@ -0,0 +1,472 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 27 Aug 2020 09:40:16 -0700 +Subject: [PATCH] Optimise closest entity lookup used by AI goals + +Use a special entity slice for tracking entities by class as well +as counts per chunk. This should reduce the number of entities searched. + +diff --git a/src/main/java/com/tuinity/tuinity/chunk/ChunkEntitiesByClass.java b/src/main/java/com/tuinity/tuinity/chunk/ChunkEntitiesByClass.java +new file mode 100644 +index 0000000000000000000000000000000000000000..37428f4b9ae45175fda545e9d8b55cf8a3b8c87b +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/ChunkEntitiesByClass.java +@@ -0,0 +1,186 @@ ++package com.tuinity.tuinity.chunk; ++ ++import com.destroystokyo.paper.util.maplist.EntityList; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; ++import net.minecraft.server.AxisAlignedBB; ++import net.minecraft.server.Chunk; ++import net.minecraft.server.Entity; ++import net.minecraft.server.MathHelper; ++import org.spigotmc.AsyncCatcher; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public final class ChunkEntitiesByClass { ++ ++ // this class attempts to restore the original intent of nms.EntitySlice and improve upon it: ++ // fast lookups for specific entity types in a chunk. However vanilla does not track things on a ++ // chunk-wide basis, which is very important to our optimisations here: we want to eliminate chunks ++ // before searching multiple slices. We also want to maintain only lists that we need to maintain for memory purposes: ++ // so we have no choice but to lazily initialise mappings of class -> entity. ++ // Typically these are used for entity AI lookups, which means we take a heavy initial cost but ultimately win ++ // since AI lookups happen a lot. ++ ++ // This optimisation is only half of the battle with entity AI, we need to be smarter about picking the closest entity. ++ // See World#getClosestEntity ++ ++ // aggressively high load factors for each map here + fastutil collections: we want the smallest memory footprint ++ private final ExposedReference2IntOpenHashMap> chunkWideCount = new ExposedReference2IntOpenHashMap<>(4, 0.9f); ++ { ++ this.chunkWideCount.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ private final Reference2ObjectOpenHashMap, ArrayList>[] slices = new Reference2ObjectOpenHashMap[16]; ++ private final Chunk chunk; ++ ++ public ChunkEntitiesByClass(final Chunk chunk) { ++ this.chunk = chunk; ++ } ++ ++ public boolean hasEntitiesMaybe(final Class clazz) { ++ final int count = this.chunkWideCount.getInt(clazz); ++ return count == Integer.MIN_VALUE || count > 0; ++ } ++ ++ public void addEntity(final Entity entity, final int sectionY) { ++ AsyncCatcher.catchOp("Add entity call"); ++ if (this.chunkWideCount.isEmpty()) { ++ return; ++ } ++ ++ final Object[] keys = this.chunkWideCount.getKey(); ++ final int[] values = this.chunkWideCount.getValue(); ++ ++ Reference2ObjectOpenHashMap, ArrayList> slice = this.slices[sectionY]; ++ if (slice == null) { ++ slice = this.slices[sectionY] = new Reference2ObjectOpenHashMap<>(4, 0.9f); ++ } ++ ++ for (int i = 0, len = keys.length; i < len; ++i) { ++ final Object _key = keys[i]; ++ if (!(_key instanceof Class)) { ++ continue; ++ } ++ final Class key = (Class)_key; ++ if (key.isInstance(entity)) { ++ ++values[i]; ++ slice.computeIfAbsent(key, (keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(entity); ++ } ++ } ++ } ++ ++ public void removeEntity(final Entity entity, final int sectionY) { ++ AsyncCatcher.catchOp("Remove entity call"); ++ if (this.chunkWideCount.isEmpty()) { ++ return; ++ } ++ ++ final Object[] keys = this.chunkWideCount.getKey(); ++ final int[] values = this.chunkWideCount.getValue(); ++ ++ Reference2ObjectOpenHashMap, ArrayList> slice = this.slices[sectionY]; ++ if (slice == null) { ++ return; // seriously brain damaged plugins ++ } ++ ++ for (int i = 0, len = keys.length; i < len; ++i) { ++ final Object _key = keys[i]; ++ if (!(_key instanceof Class)) { ++ continue; ++ } ++ final Class key = (Class)_key; ++ if (key.isInstance(entity)) { ++ --values[i]; ++ final ArrayList list = slice.get(key); ++ if (list == null) { ++ return; // seriously brain damaged plugins ++ } ++ list.remove(entity); ++ } ++ } ++ } ++ ++ ++ private void computeClass(final Class clazz) { ++ AsyncCatcher.catchOp("Entity class compute call"); ++ int totalCount = 0; ++ ++ EntityList entityList = this.chunk.entities; ++ Entity[] entities = entityList.getRawData(); ++ for (int i = 0, len = entityList.size(); i < len; ++i) { ++ final Entity entity = entities[i]; ++ ++ if (clazz.isInstance(entity)) { ++ ++totalCount; ++ Reference2ObjectOpenHashMap, ArrayList> slice = this.slices[entity.chunkY]; ++ if (slice == null) { ++ slice = this.slices[entity.chunkY] = new Reference2ObjectOpenHashMap<>(4, 0.9f); ++ } ++ slice.computeIfAbsent(clazz, (keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(entity); ++ } ++ } ++ ++ this.chunkWideCount.put(clazz, totalCount); ++ } ++ ++ public void lookupClass(final Class clazz, final Entity entity, final AxisAlignedBB boundingBox, final Predicate predicate, final List into) { ++ final int count = this.chunkWideCount.getInt(clazz); ++ if (count == Integer.MIN_VALUE) { ++ this.computeClass(clazz); ++ if (this.chunkWideCount.getInt(clazz) <= 0) { ++ return; ++ } ++ } else if (count <= 0) { ++ return; ++ } ++ ++ // copied from getEntities ++ int min = MathHelper.floor((boundingBox.minY - 2.0D) / 16.0D); ++ int max = MathHelper.floor((boundingBox.maxY + 2.0D) / 16.0D); ++ ++ min = MathHelper.clamp(min, 0, this.slices.length - 1); ++ max = MathHelper.clamp(max, 0, this.slices.length - 1); ++ ++ for (int y = min; y <= max; ++y) { ++ final Reference2ObjectOpenHashMap, ArrayList> slice = this.slices[y]; ++ if (slice == null) { ++ continue; ++ } ++ ++ final ArrayList entities = slice.get(clazz); ++ if (entities == null) { ++ continue; ++ } ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity entity1 = entities.get(i); ++ if (entity1.shouldBeRemoved) continue; // Paper ++ ++ if (entity1 != entity && entity1.getBoundingBox().intersects(boundingBox)) { ++ if (predicate == null || predicate.test(entity1)) { ++ into.add(entity1); ++ } ++ } ++ } ++ } ++ } ++ ++ static final class ExposedReference2IntOpenHashMap extends Reference2IntOpenHashMap { ++ ++ public ExposedReference2IntOpenHashMap(final int expected, final float loadFactor) { ++ super(expected, loadFactor); ++ } ++ ++ public Object[] getKey() { ++ return this.key; ++ } ++ ++ public int[] getValue() { ++ return this.value; ++ } ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/CachedLists.java b/src/main/java/com/tuinity/tuinity/util/CachedLists.java +index 387eeb5d770ba9fe564c61df8cc92ac8b1569f61..21e50c75e0bffaa5cc5faf6aa81ae7428caca731 100644 +--- a/src/main/java/com/tuinity/tuinity/util/CachedLists.java ++++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java +@@ -1,6 +1,7 @@ + package com.tuinity.tuinity.util; + + import net.minecraft.server.AxisAlignedBB; ++import net.minecraft.server.Chunk; + import net.minecraft.server.Entity; + import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.util.UnsafeList; +@@ -46,8 +47,28 @@ public class CachedLists { + tempGetEntitiesListInUse = false; + } + ++ static final UnsafeList TEMP_GET_CHUNKS_LIST = new UnsafeList<>(1024); ++ static boolean tempGetChunksListInUse; ++ ++ public static UnsafeList getTempGetChunksList() { ++ if (!Bukkit.isPrimaryThread() || tempGetChunksListInUse) { ++ return new UnsafeList<>(); ++ } ++ tempGetChunksListInUse = true; ++ return TEMP_GET_CHUNKS_LIST; ++ } ++ ++ public static void returnTempGetChunksList(List list) { ++ if (list != TEMP_GET_CHUNKS_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempGetChunksListInUse = false; ++ } ++ + public static void reset() { + TEMP_COLLISION_LIST.completeReset(); + TEMP_GET_ENTITIES_LIST.completeReset(); ++ TEMP_GET_CHUNKS_LIST.completeReset(); + } + } +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index 2e25102b6d7e71b0a536e77c2116981aed8623e2..8c6c653b3454f59cf823fd92f7b464a8f998b675 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -141,6 +141,22 @@ public class Chunk implements IChunkAccess { + } + // Tuinity end - optimise hard collision handling + ++ // Tuinity start - entity slices by class ++ private final com.tuinity.tuinity.chunk.ChunkEntitiesByClass entitiesByClass = new com.tuinity.tuinity.chunk.ChunkEntitiesByClass(this); ++ ++ public boolean hasEntitiesMaybe(Class clazz) { ++ return this.entitiesByClass.hasEntitiesMaybe(clazz); ++ } ++ ++ public final void getEntitiesClass(Class clazz, Entity entity, AxisAlignedBB boundingBox, Predicate predicate, List into) { ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ this.getEntities((Class)clazz, boundingBox, (List)into, (Predicate)predicate); ++ return; ++ } ++ this.entitiesByClass.lookupClass(clazz, entity, boundingBox, predicate, into); ++ } ++ // Tuinity end - entity slices by class ++ + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { + this.sections = new ChunkSection[16]; + this.e = Maps.newHashMap(); +@@ -644,7 +660,7 @@ public class Chunk implements IChunkAccess { + entity.chunkX = this.loc.x; + entity.chunkY = k; + entity.chunkZ = this.loc.z; +- this.entities.add(entity); // Paper - per chunk entity list ++ this.entities.add(entity); this.entitiesByClass.addEntity(entity, entity.chunkY); // Paper - per chunk entity list // Tuinity - entities by class + this.entitySlices[k].add(entity); if (entity.hardCollides()) this.hardCollidingEntities[k].add(entity); // Tuinity - optimise hard colliding entities + // Paper start + if (entity instanceof EntityItem) { +@@ -683,7 +699,7 @@ public class Chunk implements IChunkAccess { + entity.entitySlice = null; + entity.inChunk = false; + } +- if (entity.hardCollides()) this.hardCollidingEntities[i].remove(entity); if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities ++ if (entity.hardCollides()) this.hardCollidingEntities[i].remove(entity); this.entitiesByClass.removeEntity(entity, i); if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities // Tuinity - entities by class + return; + } + if (entity instanceof EntityItem) { +@@ -996,6 +1012,7 @@ public class Chunk implements IChunkAccess { + + } + ++ public final void getEntities(Class oclass, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { this.a(oclass, axisalignedbb, list, predicate); } // Tuinity - OBFHELPER + public void a(Class oclass, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { + org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot + int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); +diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java +index 64b59b17d28803f510b8b088ebafe446c450d486..cbaf18af1066e8bde10293bba5eb3060bae1e66f 100644 +--- a/src/main/java/net/minecraft/server/IEntityAccess.java ++++ b/src/main/java/net/minecraft/server/IEntityAccess.java +@@ -214,12 +214,12 @@ public interface IEntityAccess { + } + + @Nullable +- default T a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { ++ default T a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { // Tuinity - diff on change, override in World - this should be "get closest entity by class that matches path finder target condition" + return this.a(this.a(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix + } + + @Nullable +- default T b(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { ++ default T b(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { // Tuinity - diff on change, override in World - this should be "get closest entity by class that matches path finder target condition" + return this.a(this.b(oclass, axisalignedbb, null), pathfindertargetcondition, entityliving, d0, d1, d2); // Paper - decompile fix + } + +diff --git a/src/main/java/net/minecraft/server/PathfinderTargetCondition.java b/src/main/java/net/minecraft/server/PathfinderTargetCondition.java +index 253377c6238594de1f76cafcbf8223592e4d3f6b..3ebe3d0dc4c2c6aee6ea349006a74cbe5aa8e78f 100644 +--- a/src/main/java/net/minecraft/server/PathfinderTargetCondition.java ++++ b/src/main/java/net/minecraft/server/PathfinderTargetCondition.java +@@ -51,6 +51,7 @@ public class PathfinderTargetCondition { + return this; + } + ++ public final boolean test(@Nullable EntityLiving entityliving, EntityLiving entityliving1) { return this.a(entityliving, entityliving1); } // Tuinity - OBFHELPER + public boolean a(@Nullable EntityLiving entityliving, EntityLiving entityliving1) { + if (entityliving == entityliving1) { + return false; +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 6c3f496ba6c0c6c4911e2592ded27df08ae54b14..3ad08863c00d96c827c26c8d4c9a206a43bb1db9 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1187,7 +1187,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { +- chunk.a(oclass, axisalignedbb, list, predicate); ++ chunk.getEntitiesClass(oclass, null, axisalignedbb, (Predicate)predicate, (List)list); // Tuinity - optimise lookup by entity class + } + } + } +@@ -1210,7 +1210,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + Chunk chunk = (Chunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper + + if (chunk != null) { +- chunk.a(oclass, axisalignedbb, list, predicate); ++ chunk.getEntitiesClass(oclass, null, axisalignedbb, (Predicate)predicate, (List)list); // Tuinity - optimise lookup by entity class + } + } + } +@@ -1218,6 +1218,106 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return list; + } + ++ // Tuinity start ++ @Override ++ public T b(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { ++ return this.getClosestEntity(oclass, pathfindertargetcondition, entityliving, d0, d1, d2, axisalignedbb); ++ } ++ ++ @Override ++ public T a(Class oclass, PathfinderTargetCondition pathfindertargetcondition, @Nullable EntityLiving entityliving, double d0, double d1, double d2, AxisAlignedBB axisalignedbb) { ++ return this.getClosestEntity(oclass, pathfindertargetcondition, entityliving, d0, d1, d2, axisalignedbb); ++ } ++ ++ public final T getClosestEntity(Class clazz, ++ PathfinderTargetCondition condition, ++ @Nullable EntityLiving source, ++ double x, double y, double z, ++ AxisAlignedBB boundingBox) { ++ org.bukkit.craftbukkit.util.UnsafeList entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); ++ try { ++ int lowerX = MCUtil.fastFloor((boundingBox.minX - 2.0D)) >> 4; ++ int upperX = MCUtil.fastFloor((boundingBox.maxX + 2.0D)) >> 4; ++ int lowerZ = MCUtil.fastFloor((boundingBox.minZ - 2.0D)) >> 4; ++ int upperZ = MCUtil.fastFloor((boundingBox.maxZ + 2.0D)) >> 4; ++ ++ org.bukkit.craftbukkit.util.UnsafeList chunks = com.tuinity.tuinity.util.CachedLists.getTempGetChunksList(); ++ try { ++ T closest = null; ++ double closestDistance = Double.MAX_VALUE; ++ ChunkProviderServer chunkProvider = ((WorldServer)this).getChunkProvider(); ++ ++ int centerX = (lowerX + upperX) >> 1; ++ int centerZ = (lowerZ + upperZ) >> 1; ++ // Copied from MCUtil.getSpiralOutChunks ++ Chunk temp; ++ if ((temp = chunkProvider.getChunkAtIfLoadedImmediately(centerX, centerZ)) != null && temp.hasEntitiesMaybe(clazz)) { ++ chunks.add(temp); ++ } ++ int radius = Math.max((upperX - lowerX + 1) >> 1, (upperZ - lowerZ + 1) >> 1); ++ for (int r = 1; r <= radius; r++) { ++ int ox = -r; ++ int oz = r; ++ ++ // Iterates the edge of half of the box; then negates for other half. ++ while (ox <= r && oz > -r) { ++ { ++ int cx = centerX + ox; ++ int cz = centerZ + oz; ++ if (cx >= lowerX && cx <= upperX && cz >= lowerZ && cz <= upperZ && ++ (temp = chunkProvider.getChunkAtIfLoadedImmediately(cx, cz)) != null && ++ temp.hasEntitiesMaybe(clazz)) { ++ chunks.add(temp); ++ } ++ } ++ { ++ int cx = centerX - ox; ++ int cz = centerZ - oz; ++ if (cx >= lowerX && cx <= upperX && cz >= lowerZ && cz <= upperZ && ++ (temp = chunkProvider.getChunkAtIfLoadedImmediately(cx, cz)) != null && ++ temp.hasEntitiesMaybe(clazz)) { ++ chunks.add(temp); ++ } ++ } ++ ++ if (ox < r) { ++ ox++; ++ } else { ++ oz--; ++ } ++ } ++ } ++ ++ Object[] chunkData = chunks.getRawDataArray(); ++ for (int cindex = 0, clen = chunks.size(); cindex < clen; ++cindex) { ++ final Chunk chunk = (Chunk)chunkData[cindex]; ++ ++ chunk.getEntitiesClass(clazz, source, boundingBox, null, entities); ++ ++ Object[] entityData = entities.getRawDataArray(); ++ for (int eindex = 0, entities_len = entities.size(); eindex < entities_len; ++eindex) { ++ T entity = (T)entityData[eindex]; ++ double distance = entity.getDistanceSquared(x, y, z); ++ // check distance first, as it's the least expensive ++ if (distance < closestDistance && condition.test(source, entity)) { ++ closest = entity; ++ closestDistance = distance; ++ } ++ } ++ ++ entities.setSize(0); ++ } ++ ++ return closest; ++ } finally { ++ com.tuinity.tuinity.util.CachedLists.returnTempGetChunksList(chunks); ++ } ++ } finally { ++ com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities); ++ } ++ } ++ // Tuinity end ++ + @Nullable + public abstract Entity getEntity(int i); + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +index 50f855b931dba60754fff9c7cdf5e0e744f00fdd..7c0d90552eeb6de7dab174e2ba4acfc89a7b3db0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +@@ -35,6 +35,13 @@ public class UnsafeList extends AbstractList implements List, RandomAcc + iterPool[0] = new Itr(); + } + ++ // Tuinity start ++ @Override ++ public void sort(java.util.Comparator c) { ++ Arrays.sort((E[])this.data, 0, size, c); ++ } ++ // Tuinity end ++ + public UnsafeList(int capacity) { + this(capacity, 5); + } diff --git a/patches/Tuinity/patches/server/0047-Optimise-EntityInsentient-checkDespawn.patch b/patches/Tuinity/patches/server/0047-Optimise-EntityInsentient-checkDespawn.patch new file mode 100644 index 00000000..7c58d31f --- /dev/null +++ b/patches/Tuinity/patches/server/0047-Optimise-EntityInsentient-checkDespawn.patch @@ -0,0 +1,273 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 27 Aug 2020 16:22:52 -0700 +Subject: [PATCH] Optimise EntityInsentient#checkDespawn + +Use a distance map to map out close players. +Note that it's important that we cache the distance map value per chunk +since the penalty of a map lookup could outweigh the benefits of +searching less players (as it basically did in the outside range patch). + +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index 8c6c653b3454f59cf823fd92f7b464a8f998b675..781d74cf7e3669d71727cce781a8f8ce088c5547 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -157,6 +157,85 @@ public class Chunk implements IChunkAccess { + } + // Tuinity end - entity slices by class + ++ // Tuinity start - optimise checkDespawn ++ private boolean playerGeneralAreaCacheSet; ++ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playerGeneralAreaCache; ++ ++ void updateGeneralAreaCache() { ++ this.updateGeneralAreaCache(((WorldServer)this.world).getChunkProvider().playerChunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey)); ++ } ++ ++ void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet value) { ++ this.playerGeneralAreaCacheSet = true; ++ this.playerGeneralAreaCache = value; ++ } ++ ++ public EntityPlayer findNearestPlayer(Entity to, Predicate predicate) { ++ if (!this.playerGeneralAreaCacheSet) { ++ this.updateGeneralAreaCache(); ++ } ++ ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; ++ ++ if (nearby == null) { ++ return null; ++ } ++ ++ Object[] backingSet = nearby.getBackingSet(); ++ double closestDistance = Double.MAX_VALUE; ++ EntityPlayer closest = null; ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object _player = backingSet[i]; ++ if (!(_player instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)_player; ++ ++ double distance = to.getDistanceSquared(player.locX(), player.locY(), player.locZ()); ++ if (distance < closestDistance && predicate.test(player)) { ++ closest = player; ++ closestDistance = distance; ++ } ++ } ++ ++ return closest; ++ } ++ ++ public void getNearestPlayers(Entity source, Predicate predicate, double range, List ret) { ++ if (!this.playerGeneralAreaCacheSet) { ++ this.updateGeneralAreaCache(); ++ } ++ ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; ++ ++ if (nearby == null) { ++ return; ++ } ++ ++ double rangeSquared = range * range; ++ ++ Object[] backingSet = nearby.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object _player = backingSet[i]; ++ if (!(_player instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer)_player; ++ ++ if (range >= 0.0) { ++ double distanceSquared = player.getDistanceSquared(source.locX(), source.locY(), source.locZ()); ++ if (distanceSquared > rangeSquared) { ++ continue; ++ } ++ } ++ ++ if (predicate == null || predicate.test(player)) { ++ ret.add(player); ++ } ++ } ++ } ++ // Tuinity end - optimise checkDespawn ++ + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { + this.sections = new ChunkSection[16]; + this.e = Maps.newHashMap(); +diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java +index eb5c3a1f0d9ff665631caf5bf579e83d1ed25e4f..7582a3a0955db2bc79daeced8e9c869f4276815a 100644 +--- a/src/main/java/net/minecraft/server/EntityInsentient.java ++++ b/src/main/java/net/minecraft/server/EntityInsentient.java +@@ -710,7 +710,13 @@ public abstract class EntityInsentient extends EntityLiving { + if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.L()) { + this.die(); + } else if (!this.isPersistent() && !this.isSpecialPersistence()) { +- EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D, IEntitySelector.affectsSpawning); // Paper ++ // Tuinity start - optimise checkDespawn ++ Chunk chunk = this.getCurrentChunk(); ++ EntityHuman entityhuman = chunk == null || this.world.paperConfig.hardDespawnDistance >= (31 * 16 * 31 * 16) ? this.world.findNearbyPlayer(this, -1.0D, IEntitySelector.affectsSpawning) : chunk.findNearestPlayer(this, IEntitySelector.affectsSpawning); // Paper ++ if (entityhuman == null) { ++ entityhuman = ((WorldServer)this.world).playersAffectingSpawning.isEmpty() ? null : ((WorldServer)this.world).playersAffectingSpawning.get(0); ++ } ++ // Tuinity end - optimise checkDespawn + + if (entityhuman != null) { + double d0 = entityhuman.h((Entity) this); // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index a072208bcd92ffa9ed47757de291b82be2e71e8e..8b4ab23563a9a0c047267143dc3c6c5545d6c125 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -56,6 +56,12 @@ public class PlayerChunk { + long key = net.minecraft.server.MCUtil.getCoordinateKey(this.location); + this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ // Tuinity start - optimise checkDespawn ++ Chunk chunk = this.getFullChunkIfCached(); ++ if (chunk != null) { ++ chunk.updateGeneralAreaCache(); ++ } ++ // Tuinity end - optimise checkDespawn + } + // Paper end - optimise isOutsideOfRange + // Paper start - optimize chunk status progression without jumping through thread pool +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 83f070229098ad31b8ae65ffcebe52886ef2884d..f4d5ff1d0f1ad34032aaab96e1077f4be43d4bf3 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -195,6 +195,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap; + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap; + // Paper end - no-tick view distance ++ // Tuinity start - optimise checkDespawn ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap; ++ // Tuinity end - optimise checkDespawn + + void addPlayerToDistanceMaps(EntityPlayer player) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity +@@ -225,6 +228,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured + player.needsChunkCenterUpdate = false; + // Paper end - no-tick view distance ++ // Tuinity start - optimise checkDespawn ++ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, 33); ++ // Tuinity end - optimise checkDespawn + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { +@@ -243,6 +249,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerViewDistanceTickMap.remove(player); + this.playerViewDistanceNoTickMap.remove(player); + // Paper end - no-tick view distance ++ // Tuinity start - optimise checkDespawn ++ this.playerGeneralAreaMap.remove(player); ++ // Tuinity end - optimise checkDespawn + } + + void updateMaps(EntityPlayer player) { +@@ -274,6 +283,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured + player.needsChunkCenterUpdate = false; + // Paper end - no-tick view distance ++ // Tuinity start - optimise checkDespawn ++ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, 33); ++ // Tuinity end - optimise checkDespawn + } + // Paper end + +@@ -462,6 +474,23 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + // Tuinity start + this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager<>(this.world, RegionData.class, 2, (1.0 / 3.0), "Data"); + // Tuinity end ++ // Tuinity start - optimise checkDespawn ++ this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfCachedImmediately(rangeX, rangeZ); ++ if (chunk != null) { ++ chunk.updateGeneralAreaCache(newState); ++ } ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfCachedImmediately(rangeX, rangeZ); ++ if (chunk != null) { ++ chunk.updateGeneralAreaCache(newState); ++ } ++ }); ++ // Tuinity end - optimise checkDespawn + } + // Paper start - Chunk Prioritization + public void queueHolderUpdate(PlayerChunk playerchunk) { +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 3ad08863c00d96c827c26c8d4c9a206a43bb1db9..28ee325fcc8b50397768363403823f2e3391d8c8 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -123,6 +123,34 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + return typeKey; + } + ++ // Tuinity start - optimise checkDespawn ++ public final List getNearbyPlayers(Entity source, double maxRange, Predicate predicate) { ++ Chunk chunk = source.getCurrentChunk(); ++ if (chunk == null || maxRange < 0.0 || maxRange > 31.0*16.0) { ++ return this.getNearbyPlayersSlow(source, maxRange, predicate); ++ } ++ ++ List ret = new java.util.ArrayList<>(); ++ chunk.getNearestPlayers(source, predicate, maxRange, ret); ++ return ret; ++ } ++ ++ private List getNearbyPlayersSlow(Entity source, double maxRange, Predicate predicate) { ++ List ret = new java.util.ArrayList<>(); ++ double maxRangeSquared = maxRange * maxRange; ++ ++ for (EntityHuman player : this.getPlayers()) { ++ if ((maxRange < 0.0 || player.getDistanceSquared(source.locX(), source.locY(), source.locZ()) < maxRangeSquared)) { ++ if (predicate == null || predicate.test(player)) { ++ ret.add((EntityPlayer)player); ++ } ++ } ++ } ++ ++ return ret; ++ } ++ // Tuinity end - optimise checkDespawn ++ + protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot + this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 80b2507414dc603deb0b542e5f5d86969af9d21b..c0617e513d321439db3d5fd9517da1f38f1fbd88 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -300,6 +300,10 @@ public class WorldServer extends World implements GeneratorAccessSeed { + long lastMidTickExecuteFailure; + // Tuinity end - execute chunk tasks mid tick + ++ // Tuinity start - optimise checkDespawn ++ final List playersAffectingSpawning = new java.util.ArrayList<>(); ++ // Tuinity end - optimise checkDespawn ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor +@@ -650,6 +654,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { + + public void doTick(BooleanSupplier booleansupplier) { + GameProfilerFiller gameprofilerfiller = this.getMethodProfiler(); ++ // Tuinity start - optimise checkDespawn ++ this.playersAffectingSpawning.clear(); ++ for (EntityPlayer player : this.getPlayers()) { ++ if (IEntitySelector.affectsSpawning.test(player)) { ++ this.playersAffectingSpawning.add(player); ++ } ++ } ++ // Tuinity end - optimise checkDespawn + + this.ticking = true; + gameprofilerfiller.enter("world border"); diff --git a/patches/Tuinity/patches/server/0048-Remove-streams-for-villager-AI.patch b/patches/Tuinity/patches/server/0048-Remove-streams-for-villager-AI.patch new file mode 100644 index 00000000..2a7b5e17 --- /dev/null +++ b/patches/Tuinity/patches/server/0048-Remove-streams-for-villager-AI.patch @@ -0,0 +1,760 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 27 Aug 2020 20:51:40 -0700 +Subject: [PATCH] Remove streams for villager AI + +POI searching: +Turns out chaining a lot of streams together has inane amounts of +overheard. Use the good ol iterator method to remove that overhead. + +The rest is just standard stream removal. + +Also remove streams for poi searching in some zombie pathfinding. + +diff --git a/src/main/java/net/minecraft/server/Behavior.java b/src/main/java/net/minecraft/server/Behavior.java +index 65af976527133ee5c2f52e411e19c4f7f06df3ef..0b9d469a92decfb0632805791868ef7faa88c535 100644 +--- a/src/main/java/net/minecraft/server/Behavior.java ++++ b/src/main/java/net/minecraft/server/Behavior.java +@@ -7,7 +7,7 @@ import java.util.Map.Entry; + public abstract class Behavior { + + protected final Map, MemoryStatus> a; +- private Behavior.Status b; ++ private Behavior.Status b; public final Behavior.Status getStatus() { return this.b; } // Tuinity - OBFHELPER + private long c; + private final int d; + private final int e; +diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java +index 63a761ebef80d4af09cdc2682e496d78492c4a3a..8d445e9c0875db6cf45e4d8bcfce7cd3d5094d94 100644 +--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java ++++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java +@@ -55,6 +55,227 @@ public class BehaviorFindPosition extends Behavior { + } + } + ++ // Tuinity - remove streams entirely for poi search ++ // the only intentional vanilla diff is that this function will NOT load in poi data, anything else is a bug! ++ protected static Set findNearestPoi(VillagePlace poiStorage, ++ Predicate villagePlaceType, ++ Predicate positionPredicate, ++ BlockPosition sourcePosition, ++ int range, // distance on x y z axis ++ VillagePlace.Occupancy occupancy, ++ int max) { ++ java.util.TreeSet ret = new java.util.TreeSet<>((blockpos1, blockpos2) -> { ++ // important to keep distanceSquared order: the param is the source ++ return Double.compare(blockpos1.distanceSquared(sourcePosition), blockpos2.distanceSquared(sourcePosition)); ++ }); ++ findNearestPoi(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, max, ret); ++ return new java.util.HashSet<>(ret); ++ } ++ protected static void findNearestPoi(VillagePlace poiStorage, ++ Predicate villagePlaceType, ++ Predicate positionPredicate, ++ BlockPosition sourcePosition, ++ int range, // distance on x y z axis ++ VillagePlace.Occupancy occupancy, ++ int max, ++ java.util.SortedSet ret) { ++ // the biggest issue with the original mojang implementation is that they chain so many streams together ++ // the amount of streams chained just rolls performance, even if nothing is iterated over ++ Predicate occupancyFilter = occupancy.getPredicate(); ++ double rangeSquared = range * range; ++ ++ // First up, we need to iterate the chunks ++ // all the values here are in chunk sections ++ int lowerX = MathHelper.floor(sourcePosition.getX() - range) >> 4; ++ int lowerY = Math.max(0, MathHelper.floor(sourcePosition.getY() - range) >> 4); ++ int lowerZ = MathHelper.floor(sourcePosition.getZ() - range) >> 4; ++ int upperX = MathHelper.floor(sourcePosition.getX() + range) >> 4; ++ int upperY = Math.min(15, MathHelper.floor(sourcePosition.getY() + range) >> 4); ++ int upperZ = MathHelper.floor(sourcePosition.getZ() + range) >> 4; ++ ++ // Vanilla iterates by x until max is reached then increases z ++ // vanilla also searches by increasing Y section value ++ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) { ++ for (int currX = lowerX; currX <= upperX; ++currX) { ++ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need ++ Optional poiSectionOptional = poiStorage.getIfLoaded(SectionPosition.asLong(currX, currY, currZ)); ++ VillagePlaceSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null); ++ if (poiSection == null) { ++ continue; ++ } ++ ++ java.util.Map> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (java.util.Iterator>> iterator = sectionData.entrySet().iterator(); ++ iterator.hasNext();) { ++ java.util.Map.Entry> entry = iterator.next(); ++ if (!villagePlaceType.test(entry.getKey())) { ++ // filter out by poi type ++ continue; ++ } ++ ++ // now we can look at the poi data ++ for (VillagePlaceRecord poiData : entry.getValue()) { ++ if (!occupancyFilter.test(poiData)) { ++ // filter by occupancy ++ continue; ++ } ++ ++ // vanilla code is pretty dumb about filtering by distance: first they filter out ++ // so that only values in the square radius of range are returned but then they ++ // filter out so that the distance is in range ++ // but there's a catch! distanceSquared, by default, will ADD 0.5 to ONLY ONE OF the ++ // block position parameters (itself, in this case the poi position)! So if we want to ++ // maintain exact vanilla behaviour, well shit we need to play dumb as well. ++ ++ BlockPosition poiPosition = poiData.getPosition(); ++ ++ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range ++ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { ++ // out of range for square radius ++ continue; ++ } ++ ++ if (poiPosition.distanceSquared(sourcePosition) > rangeSquared) { ++ // out of range for distance check ++ continue; ++ } ++ ++ if (!positionPredicate.test(poiPosition)) { ++ // filter by position ++ continue; ++ } ++ ++ // found one! ++ ret.add(poiPosition); ++ if (ret.size() > max) { ++ ret.remove(ret.last()); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected static BlockPosition findAnyFirstPoi(VillagePlace poiStorage, ++ Predicate villagePlaceType, ++ Predicate positionPredicate, ++ BlockPosition sourcePosition, ++ int range, // distance on x y z axis ++ VillagePlace.Occupancy occupancy) { ++ Set ret = new java.util.HashSet<>(); ++ findPoi(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, 1, ret); ++ return ret.isEmpty() ? null : ret.iterator().next(); ++ } ++ ++ protected static Set findPoi(VillagePlace poiStorage, ++ Predicate villagePlaceType, ++ Predicate positionPredicate, ++ BlockPosition sourcePosition, ++ int range, // distance on x y z axis ++ VillagePlace.Occupancy occupancy, ++ int max) { ++ Set ret = new java.util.HashSet<>(); ++ findPoi(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, max, ret); ++ return ret; ++ } ++ protected static void findPoi(VillagePlace poiStorage, ++ Predicate villagePlaceType, ++ Predicate positionPredicate, ++ BlockPosition sourcePosition, ++ int range, // distance on x y z axis ++ VillagePlace.Occupancy occupancy, ++ int max, ++ Set ret) { ++ // the biggest issue with the original mojang implementation is that they chain so many streams together ++ // the amount of streams chained just rolls performance, even if nothing is iterated over ++ Predicate occupancyFilter = occupancy.getPredicate(); ++ double rangeSquared = range * range; ++ ++ // First up, we need to iterate the chunks ++ // all the values here are in chunk sections ++ int lowerX = MathHelper.floor(sourcePosition.getX() - range) >> 4; ++ int lowerY = Math.max(0, MathHelper.floor(sourcePosition.getY() - range) >> 4); ++ int lowerZ = MathHelper.floor(sourcePosition.getZ() - range) >> 4; ++ int upperX = MathHelper.floor(sourcePosition.getX() + range) >> 4; ++ int upperY = Math.min(15, MathHelper.floor(sourcePosition.getY() + range) >> 4); ++ int upperZ = MathHelper.floor(sourcePosition.getZ() + range) >> 4; ++ ++ // Vanilla iterates by x until max is reached then increases z ++ // vanilla also searches by increasing Y section value ++ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) { ++ for (int currX = lowerX; currX <= upperX; ++currX) { ++ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need ++ Optional poiSectionOptional = poiStorage.getIfLoaded(SectionPosition.asLong(currX, currY, currZ)); ++ VillagePlaceSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null); ++ if (poiSection == null) { ++ continue; ++ } ++ ++ java.util.Map> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (java.util.Iterator>> iterator = sectionData.entrySet().iterator(); ++ iterator.hasNext();) { ++ java.util.Map.Entry> entry = iterator.next(); ++ if (!villagePlaceType.test(entry.getKey())) { ++ // filter out by poi type ++ continue; ++ } ++ ++ // now we can look at the poi data ++ for (VillagePlaceRecord poiData : entry.getValue()) { ++ if (!occupancyFilter.test(poiData)) { ++ // filter by occupancy ++ continue; ++ } ++ ++ // vanilla code is pretty dumb about filtering by distance: first they filter out ++ // so that only values in the square radius of range are returned but then they ++ // filter out so that the distance is in range ++ // but there's a catch! distanceSquared, by default, will ADD 0.5 to ONLY ONE OF the ++ // block position parameters (itself, in this case the poi position)! So if we want to ++ // maintain exact vanilla behaviour, well shit we need to play dumb as well. ++ ++ BlockPosition poiPosition = poiData.getPosition(); ++ ++ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range ++ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { ++ // out of range for square radius ++ continue; ++ } ++ ++ if (poiPosition.distanceSquared(sourcePosition) > rangeSquared) { ++ // out of range for distance check ++ continue; ++ } ++ ++ if (!positionPredicate.test(poiPosition)) { ++ // filter by position ++ continue; ++ } ++ ++ // found one! ++ ret.add(poiPosition); ++ if (ret.size() >= max) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ // Tuinity - remove streams entirely for poi search ++ + protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) { + this.f = i + 20L + (long) worldserver.getRandom().nextInt(20); + VillagePlace villageplace = worldserver.y(); +@@ -74,7 +295,7 @@ public class BehaviorFindPosition extends Behavior { + return true; + } + }; +- Set set = (Set) villageplace.b(this.b.c(), predicate, entitycreature.getChunkCoordinates(), 48, VillagePlace.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); ++ Set set = findNearestPoi(villageplace, this.b.c(), predicate, entitycreature.getChunkCoordinates(), 48, VillagePlace.Occupancy.HAS_SPACE, 5); // Tuinity - remove streams entirely for poi search + PathEntity pathentity = entitycreature.getNavigation().a(set, this.b.d()); + + if (pathentity != null && pathentity.j()) { +@@ -84,7 +305,7 @@ public class BehaviorFindPosition extends Behavior { + villageplace.a(this.b.c(), (blockposition1) -> { + return blockposition1.equals(blockposition); + }, blockposition, 1); +- entitycreature.getBehaviorController().setMemory(this.c, (Object) GlobalPos.create(worldserver.getDimensionKey(), blockposition)); ++ entitycreature.getBehaviorController().setMemory(this.c, GlobalPos.create(worldserver.getDimensionKey(), blockposition)); // Tuinity - decompile fix + this.e.ifPresent((obyte) -> { + worldserver.broadcastEntityEffect(entitycreature, obyte); + }); +diff --git a/src/main/java/net/minecraft/server/BehaviorGate.java b/src/main/java/net/minecraft/server/BehaviorGate.java +index 46e910581210421c8699637431804dc2f43eb4a6..fb967bc03f58fab8cec2732b1890108f2fc66af8 100644 +--- a/src/main/java/net/minecraft/server/BehaviorGate.java ++++ b/src/main/java/net/minecraft/server/BehaviorGate.java +@@ -12,7 +12,7 @@ public class BehaviorGate extends Behavior { + private final Set> b; + private final BehaviorGate.Order c; + private final BehaviorGate.Execution d; +- private final WeightedList> e = new WeightedList<>(false); // Paper - don't use a clone ++ private final WeightedList> e = new WeightedList<>(false); protected final WeightedList> getList() { return this.e; } // Paper - don't use a clone // Tuinity - OBFHELPER + + public BehaviorGate(Map, MemoryStatus> map, Set> set, BehaviorGate.Order behaviorgate_order, BehaviorGate.Execution behaviorgate_execution, List, Integer>> list) { + super(map); +@@ -26,11 +26,17 @@ public class BehaviorGate extends Behavior { + + @Override + protected boolean b(WorldServer worldserver, E e0, long i) { +- return this.e.c().filter((behavior) -> { +- return behavior.a() == Behavior.Status.RUNNING; +- }).anyMatch((behavior) -> { +- return behavior.b(worldserver, e0, i); +- }); ++ // Tuinity start - remove streams ++ List>> list = this.getList().getList(); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ Behavior behavior = list.get(index).getValue(); ++ if (behavior.getStatus() == Status.RUNNING && behavior.b(worldserver, e0, i)) { // copied from removed code, make sure to update ++ return true; ++ } ++ } ++ ++ return false; ++ // Tuinity end - remove streams + } + + @Override +@@ -46,20 +52,28 @@ public class BehaviorGate extends Behavior { + + @Override + protected void d(WorldServer worldserver, E e0, long i) { +- this.e.c().filter((behavior) -> { +- return behavior.a() == Behavior.Status.RUNNING; +- }).forEach((behavior) -> { +- behavior.f(worldserver, e0, i); +- }); ++ // Tuinity start - remove streams ++ List>> list = this.getList().getList(); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ Behavior behavior = list.get(index).getValue(); ++ if (behavior.getStatus() == Behavior.Status.RUNNING) { ++ behavior.f(worldserver, e0, i); // copied from removed code, make sure to update ++ } ++ } ++ // Tuinity end - remove streams + } + + @Override + protected void c(WorldServer worldserver, E e0, long i) { +- this.e.c().filter((behavior) -> { +- return behavior.a() == Behavior.Status.RUNNING; +- }).forEach((behavior) -> { +- behavior.g(worldserver, e0, i); +- }); ++ // Tuinity start - remove streams ++ List>> list = this.getList().getList(); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ Behavior behavior = list.get(index).getValue(); ++ if (behavior.getStatus() == Behavior.Status.RUNNING) { ++ behavior.g(worldserver, e0, i); // copied from removed code, make sure to update ++ } ++ } ++ // Tuinity end - remove streams + BehaviorController behaviorcontroller = e0.getBehaviorController(); + + this.b.forEach(behaviorcontroller::removeMemory); // Paper - decomp fix +@@ -79,21 +93,29 @@ public class BehaviorGate extends Behavior { + RUN_ONE { + @Override + public void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i) { +- weightedlist.c().filter((behavior) -> { +- return behavior.a() == Behavior.Status.STOPPED; +- }).filter((behavior) -> { +- return behavior.e(worldserver, e0, i); +- }).findFirst(); ++ // Tuinity start - remove streams ++ List>> list = weightedlist.getList(); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ Behavior behavior = list.get(index).getValue(); ++ if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.e(worldserver, e0, i)) { // copied from removed code, make sure to update ++ break; ++ } ++ } ++ // Tuinity end - remove streams + } + }, + TRY_ALL { + @Override + public void a(WeightedList> weightedlist, WorldServer worldserver, E e0, long i) { +- weightedlist.c().filter((behavior) -> { +- return behavior.a() == Behavior.Status.STOPPED; +- }).forEach((behavior) -> { +- behavior.e(worldserver, e0, i); +- }); ++ // Tuinity start - remove streams ++ List>> list = weightedlist.getList(); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ Behavior behavior = list.get(index).getValue(); ++ if (behavior.getStatus() == Behavior.Status.STOPPED) { ++ behavior.e(worldserver, e0, i); // copied from removed code, make sure to update ++ } ++ } ++ // Tuinity end - remove streams + } + }; + +diff --git a/src/main/java/net/minecraft/server/BehaviorLookInteract.java b/src/main/java/net/minecraft/server/BehaviorLookInteract.java +index a33303c31881b6391723e16a06d7841d48679958..ce57e6a4acac97d6da82202094306e7e91f1c87e 100644 +--- a/src/main/java/net/minecraft/server/BehaviorLookInteract.java ++++ b/src/main/java/net/minecraft/server/BehaviorLookInteract.java +@@ -7,7 +7,7 @@ import java.util.function.Predicate; + public class BehaviorLookInteract extends Behavior { + + private final EntityTypes b; +- private final int c; ++ private final int c; private final int getMaxRange() { return this.c; } // Tuinity - OBFHELPER + private final Predicate d; + private final Predicate e; + +@@ -29,7 +29,20 @@ public class BehaviorLookInteract extends Behavior { + + @Override + public boolean a(WorldServer worldserver, EntityLiving entityliving) { +- return this.e.test(entityliving) && this.b(entityliving).stream().anyMatch(this::a); ++ // Tuinity start - remove streams ++ if (!this.e.test(entityliving)) { ++ return false; ++ } ++ ++ List list = this.b(entityliving); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ if (this.a(list.get(index))) { ++ return true; ++ } ++ } ++ ++ return false; ++ // Tuinity end - remove streams + } + + @Override +@@ -37,16 +50,28 @@ public class BehaviorLookInteract extends Behavior { + super.a(worldserver, entityliving, i); + BehaviorController behaviorcontroller = entityliving.getBehaviorController(); + +- behaviorcontroller.getMemory(MemoryModuleType.VISIBLE_MOBS).ifPresent((list) -> { +- list.stream().filter((entityliving1) -> { +- return entityliving1.h((Entity) entityliving) <= (double) this.c; +- }).filter(this::a).findFirst().ifPresent((entityliving1) -> { +- behaviorcontroller.setMemory(MemoryModuleType.INTERACTION_TARGET, (Object) entityliving1); +- behaviorcontroller.setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BehaviorPositionEntity(entityliving1, true))); +- }); +- }); ++ // Tuinity start - remove streams ++ List inLOS = behaviorcontroller.getMemory(MemoryModuleType.VISIBLE_MOBS).orElse(null); ++ if (inLOS != null) { ++ double maxRangeSquared = this.getMaxRange(); ++ for (int index = 0, len = inLOS.size(); index < len; ++index) { ++ EntityLiving entity = inLOS.get(index); ++ if (!this.canTarget(entity)) { ++ continue; ++ } ++ double distance = entity.getDistanceSquared(entityliving.locX(), entityliving.locY(), entityliving.locZ()); ++ if (distance > maxRangeSquared) { ++ continue; ++ } ++ behaviorcontroller.setMemory(MemoryModuleType.INTERACTION_TARGET, entity); // Tuinity - decompile fix ++ behaviorcontroller.setMemory(MemoryModuleType.LOOK_TARGET, (new BehaviorPositionEntity(entity, true))); // Tuinity - decompile fix ++ break; ++ } ++ } ++ // Tuinity end - remove streams + } + ++ private final boolean canTarget(EntityLiving entityliving) { return this.a(entityliving); } // Tuinity - OBFHELPER + private boolean a(EntityLiving entityliving) { + return this.b.equals(entityliving.getEntityType()) && this.d.test(entityliving); + } +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 9287ab8e861a97fc4b132e46163b050a9ae52ced..3ad523b8ff4d5954cafb0dd71950262d83b04e8f 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -1589,6 +1589,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return d3 * d3 + d4 * d4 + d5 * d5; + } + ++ public final double getDistanceSquared(Entity other) { return this.h(other); } // Tuinity - OBFHELPER + public double h(Entity entity) { + return this.e(entity.getPositionVector()); + } +diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java +index 1558c5f8256f50be6850f1d7f70eee3e8ec76496..55fa3911703f96cf1f97c82b19d8e2d0d220016b 100644 +--- a/src/main/java/net/minecraft/server/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/server/NavigationAbstract.java +@@ -85,7 +85,7 @@ public abstract class NavigationAbstract { + + @Nullable + public PathEntity a(Stream stream, int i) { +- return this.a((Set) stream.collect(Collectors.toSet()), 8, false, i); ++ return this.a((Set) stream.collect(Collectors.toSet()), 8, false, i); // Tuinity - diff on change, inlined into SensorNearestBed + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java b/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java +index 475c0764b97b056f17720f37b1ca3eb1a2375334..9f48d476c05dbeabbfe3c650ce4ad33ec691a56a 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalMoveThroughVillage.java +@@ -50,7 +50,7 @@ public class PathfinderGoalMoveThroughVillage extends PathfinderGoal { + if (!worldserver.a_(blockposition1)) { + return Double.NEGATIVE_INFINITY; + } else { +- Optional optional = worldserver.y().c(VillagePlaceType.b, this::a, blockposition1, 10, VillagePlace.Occupancy.IS_OCCUPIED); ++ Optional optional = Optional.ofNullable(BehaviorFindPosition.findAnyFirstPoi(worldserver.y(), VillagePlaceType.b, this::a, blockposition1, 10, VillagePlace.Occupancy.IS_OCCUPIED)); // Tuinity - remove streams here + + return !optional.isPresent() ? Double.NEGATIVE_INFINITY : -((BlockPosition) optional.get()).j(blockposition); + } +@@ -59,7 +59,7 @@ public class PathfinderGoalMoveThroughVillage extends PathfinderGoal { + if (vec3d == null) { + return false; + } else { +- Optional optional = worldserver.y().c(VillagePlaceType.b, this::a, new BlockPosition(vec3d), 10, VillagePlace.Occupancy.IS_OCCUPIED); ++ Optional optional = Optional.ofNullable(BehaviorFindPosition.findAnyFirstPoi(worldserver.y(), VillagePlaceType.b, this::a, new BlockPosition(vec3d), 10, VillagePlace.Occupancy.IS_OCCUPIED)); // Tuinity - remove streams here + + if (!optional.isPresent()) { + return false; +diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java +index 04256a95108b8182e8f808e856e0d2b62165e242..d9362b74fda2ea937281f897fbc2cb501775a275 100644 +--- a/src/main/java/net/minecraft/server/RegionFileSection.java ++++ b/src/main/java/net/minecraft/server/RegionFileSection.java +@@ -50,8 +50,8 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + + } + +- @Nullable +- protected Optional c(long i) { ++ @Nullable protected Optional getIfLoaded(long value) { return this.c(value); } // Tuinity - OBFHELPER ++ @Nullable protected Optional c(long i) { // Tuinity - OBFHELPER + return (Optional) this.c.get(i); + } + +diff --git a/src/main/java/net/minecraft/server/SensorNearestBed.java b/src/main/java/net/minecraft/server/SensorNearestBed.java +index ad3609f2b884f64f1a1a449036cece49a46e933e..d3d28f97f9d2f969a182aec5e0947b6969d2939c 100644 +--- a/src/main/java/net/minecraft/server/SensorNearestBed.java ++++ b/src/main/java/net/minecraft/server/SensorNearestBed.java +@@ -40,15 +40,15 @@ public class SensorNearestBed extends Sensor { + return true; + } + }; +- Stream stream = villageplace.a(VillagePlaceType.r.c(), predicate, entityinsentient.getChunkCoordinates(), 48, VillagePlace.Occupancy.ANY); +- PathEntity pathentity = entityinsentient.getNavigation().a(stream, VillagePlaceType.r.d()); ++ Set set = BehaviorFindPosition.findPoi(villageplace, VillagePlaceType.r.c(), predicate, entityinsentient.getChunkCoordinates(), 48, VillagePlace.Occupancy.ANY, Integer.MAX_VALUE); // Tuinity - remove streams ++ PathEntity pathentity = entityinsentient.getNavigation().a(set, 8, false, VillagePlaceType.r.d()); // this.a((Set) stream.collect(Collectors.toSet()), 8, false, i) // Tuinity - remove streams + + if (pathentity != null && pathentity.j()) { + BlockPosition blockposition = pathentity.m(); + Optional optional = villageplace.c(blockposition); + + if (optional.isPresent()) { +- entityinsentient.getBehaviorController().setMemory(MemoryModuleType.NEAREST_BED, (Object) blockposition); ++ entityinsentient.getBehaviorController().setMemory(MemoryModuleType.NEAREST_BED, blockposition); // Tuinity - decompile fix + } + } else if (this.b < 5) { + this.a.long2LongEntrySet().removeIf((entry) -> { +diff --git a/src/main/java/net/minecraft/server/SensorNearestItems.java b/src/main/java/net/minecraft/server/SensorNearestItems.java +index 2e747158d48ab28ac1611990cc97aa4a9e30b30e..1de170b9fe6f2888da6dcf0151aaf1f865691c6a 100644 +--- a/src/main/java/net/minecraft/server/SensorNearestItems.java ++++ b/src/main/java/net/minecraft/server/SensorNearestItems.java +@@ -18,20 +18,23 @@ public class SensorNearestItems extends Sensor { + + protected void a(WorldServer worldserver, EntityInsentient entityinsentient) { + BehaviorController behaviorcontroller = entityinsentient.getBehaviorController(); +- List list = worldserver.a(EntityItem.class, entityinsentient.getBoundingBox().grow(8.0D, 4.0D, 8.0D), (entityitem) -> { +- return true; ++ // Tuinity start - remove streams ++ List list = worldserver.a(EntityItem.class, entityinsentient.getBoundingBox().grow(8.0D, 4.0D, 8.0D), (EntityItem item) -> { ++ return entityinsentient.i(item.getItemStack()) && item.a((Entity)entityinsentient, 9.0D); // copied from removed code, make sure to update - move here so we sort less + }); + +- entityinsentient.getClass(); +- list.sort(Comparator.comparingDouble(entityinsentient::h)); +- Stream stream = list.stream().filter((entityitem) -> { +- return entityinsentient.i(entityitem.getItemStack()); +- }).filter((entityitem) -> { +- return entityitem.a((Entity) entityinsentient, 9.0D); +- }); +- +- entityinsentient.getClass(); +- Optional optional = stream.filter(entityinsentient::hasLineOfSight).findFirst(); ++ list.sort(Comparator.comparingDouble(entityinsentient::h)); // better to take the sort perf hit than using line of sight more than we need to. ++ EntityItem nearest = null; ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ EntityItem item = list.get(index); ++ if (entityinsentient.hasLineOfSight(item)) { ++ nearest = item; ++ break; ++ } ++ } ++ ++ Optional optional = Optional.ofNullable(nearest); ++ // Tuinity end - remove streams + + behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); + } +diff --git a/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java b/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java +index f6568a54ab85bc3a682f6fbb19dda7a783625bbe..4005df5ef3dec956a54feff539db2e63c226059a 100644 +--- a/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java ++++ b/src/main/java/net/minecraft/server/SensorNearestLivingEntities.java +@@ -21,10 +21,17 @@ public class SensorNearestLivingEntities extends Sensor { + list.sort(Comparator.comparingDouble(entityliving::h)); + BehaviorController behaviorcontroller = entityliving.getBehaviorController(); + +- behaviorcontroller.setMemory(MemoryModuleType.MOBS, (Object) list); +- behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, list.stream().filter((entityliving1) -> { +- return a(entityliving, entityliving1); +- }).collect(Collectors.toList())); ++ behaviorcontroller.setMemory(MemoryModuleType.MOBS, list); // Tuinity - decompile fix ++ // Tuinity start - remove streams ++ List visible = new java.util.ArrayList<>(list.size()); ++ for (int index = 0, len = list.size(); index < len; ++index) { ++ EntityLiving nearby = list.get(index); ++ if (Sensor.a(entityliving, nearby)) { // copied from removed code, make sure to update ++ visible.add(nearby); ++ } ++ } ++ behaviorcontroller.setMemory(MemoryModuleType.VISIBLE_MOBS, visible); ++ // Tuinity end - remove streams + } + + @Override +diff --git a/src/main/java/net/minecraft/server/SensorNearestPlayers.java b/src/main/java/net/minecraft/server/SensorNearestPlayers.java +index 904a6d5ac61d2ac81f1057068383e9ab432852db..c8e43a9f2a23178fdef52375b7204b90b28ac20b 100644 +--- a/src/main/java/net/minecraft/server/SensorNearestPlayers.java ++++ b/src/main/java/net/minecraft/server/SensorNearestPlayers.java +@@ -19,22 +19,30 @@ public class SensorNearestPlayers extends Sensor { + + @Override + protected void a(WorldServer worldserver, EntityLiving entityliving) { +- Stream stream = worldserver.getPlayers().stream().filter(IEntitySelector.g).filter((entityplayer) -> { +- return entityliving.a((Entity) entityplayer, 16.0D); +- }); +- +- entityliving.getClass(); +- List list = (List) stream.sorted(Comparator.comparingDouble(entityliving::h)).collect(Collectors.toList()); ++ // Tuinity start - remove streams ++ List nearby = (List)worldserver.getNearbyPlayers(entityliving, 16.0, IEntitySelector.g); ++ nearby.sort((e1, e2) -> Double.compare(entityliving.getDistanceSquared(e1), entityliving.getDistanceSquared(e2))); + BehaviorController behaviorcontroller = entityliving.getBehaviorController(); + +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, (Object) list); +- List list1 = (List) list.stream().filter((entityhuman) -> { +- return a(entityliving, (EntityLiving) entityhuman); +- }).collect(Collectors.toList()); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, (Object) (list1.isEmpty() ? null : (EntityHuman) list1.get(0))); +- Optional optional = list1.stream().filter(IEntitySelector.f).findFirst(); +- +- behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, optional); ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_PLAYERS, nearby); ++ EntityHuman first = null; ++ EntityHuman firstNonSpectator = null; ++ for (int index = 0, len = nearby.size(); index < len; ++index) { ++ EntityHuman entity = nearby.get(index); ++ if (!Sensor.a(entityliving, (EntityLiving)entity)) { // copied from removed code, make sure to update ++ continue; ++ } ++ if (first == null) { ++ first = entity; ++ } ++ if (IEntitySelector.f.test(entity)) { // copied from removed code, make sure to update ++ firstNonSpectator = entity; ++ break; ++ } ++ } ++ ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, first); ++ behaviorcontroller.setMemory(MemoryModuleType.NEAREST_VISIBLE_TARGETABLE_PLAYER, Optional.ofNullable(firstNonSpectator)); ++ // Tuinity end - remove streams + } + } +diff --git a/src/main/java/net/minecraft/server/SensorVillagerBabies.java b/src/main/java/net/minecraft/server/SensorVillagerBabies.java +index a367bbfde4fbfeca6d01dec49c05f5e185aab43a..794b33a13b7f11b973caf085b0bded9b2135a4d7 100644 +--- a/src/main/java/net/minecraft/server/SensorVillagerBabies.java ++++ b/src/main/java/net/minecraft/server/SensorVillagerBabies.java +@@ -17,11 +17,23 @@ public class SensorVillagerBabies extends Sensor { + + @Override + protected void a(WorldServer worldserver, EntityLiving entityliving) { +- entityliving.getBehaviorController().setMemory(MemoryModuleType.VISIBLE_VILLAGER_BABIES, (Object) this.a(entityliving)); ++ entityliving.getBehaviorController().setMemory(MemoryModuleType.VISIBLE_VILLAGER_BABIES, this.a(entityliving)); // Tuinity - decompile fix + } + + private List a(EntityLiving entityliving) { +- return (List) this.c(entityliving).stream().filter(this::b).collect(Collectors.toList()); ++ // Tuinity start - remove streams ++ List nearby = this.c(entityliving); // copied from removed code, make sure to update ++ List ret = new java.util.ArrayList<>(); ++ ++ for (int index = 0, len = nearby.size(); index < len; ++index) { ++ EntityLiving entity = nearby.get(index); ++ if (this.b(entity)) { // copied from removed code, make sure to update ++ ret.add(entity); ++ } ++ } ++ ++ return ret; ++ // Tuinity end - remove streams + } + + private boolean b(EntityLiving entityliving) { +diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java +index 46c4e66566b7206d311653341987b9312dea3e68..0094babbd59cc81554b9480088464d632824ae8e 100644 +--- a/src/main/java/net/minecraft/server/VillagePlace.java ++++ b/src/main/java/net/minecraft/server/VillagePlace.java +@@ -311,6 +311,7 @@ public class VillagePlace extends RegionFileSection { + this.d = predicate; + } + ++ public final Predicate getPredicate() { return this.a(); } // Tuinity - OBFHELPER + public Predicate a() { + return this.d; + } +diff --git a/src/main/java/net/minecraft/server/VillagePlaceRecord.java b/src/main/java/net/minecraft/server/VillagePlaceRecord.java +index 0b40c2f4dada7d8432e3f91e9cf206c2bda3b24b..6eaf9fc9cc93f79a497b07a3549d459ba66be849 100644 +--- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java ++++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java +@@ -6,7 +6,7 @@ import java.util.Objects; + + public class VillagePlaceRecord { + +- private final BlockPosition a; ++ private final BlockPosition a; public final BlockPosition getPosition() { return this.a; } // Tuinity - OBFHELPER + private final VillagePlaceType b; + private int c; + private final Runnable d; +diff --git a/src/main/java/net/minecraft/server/VillagePlaceSection.java b/src/main/java/net/minecraft/server/VillagePlaceSection.java +index b86963aa34b5ae479f924c5a52afc5b5b66dba76..943a437ff27162eae09211c28bdc0d141fa6a404 100644 +--- a/src/main/java/net/minecraft/server/VillagePlaceSection.java ++++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java +@@ -23,12 +23,12 @@ public class VillagePlaceSection { + + private static final Logger LOGGER = LogManager.getLogger(); + private final Short2ObjectMap b; +- private final Map> c; ++ private final Map> c; public final Map> getData() { return this.c; } // Tuinity - OBFHELPER + private final Runnable d; + private boolean e; + + public static Codec a(Runnable runnable) { +- Codec codec = RecordCodecBuilder.create((instance) -> { ++ Codec codec = RecordCodecBuilder.create((instance) -> { // Tuinity - decompile fix + return instance.group(RecordCodecBuilder.point(runnable), Codec.BOOL.optionalFieldOf("Valid", false).forGetter((villageplacesection) -> { + return villageplacesection.e; + }), VillagePlaceRecord.a(runnable).listOf().fieldOf("Records").forGetter((villageplacesection) -> { +diff --git a/src/main/java/net/minecraft/server/WeightedList.java b/src/main/java/net/minecraft/server/WeightedList.java +index 5d9d58411f2fad9d5da703f964d269b4a7c2b205..f0fdfd6891e59891e7370a2d682b65c647b28e9e 100644 +--- a/src/main/java/net/minecraft/server/WeightedList.java ++++ b/src/main/java/net/minecraft/server/WeightedList.java +@@ -14,7 +14,7 @@ import java.util.stream.Stream; + + public class WeightedList { + +- protected final List> list; // Paper - decompile conflict ++ protected final List> list; public final List> getList() { return this.list; } // Paper - decompile conflict // Tuinity - OBFHELPER + private final Random b; + private final boolean isUnsafe; // Paper + +@@ -74,7 +74,7 @@ public class WeightedList { + + public static class a { + +- private final T a; ++ private final T a; public final T getValue() { return this.a; } // Tuinity - OBFHELPER + private final int b; + private double c; + diff --git a/patches/Tuinity/patches/server/0049-Don-t-lookup-fluid-state-when-raytracing.patch b/patches/Tuinity/patches/server/0049-Don-t-lookup-fluid-state-when-raytracing.patch new file mode 100644 index 00000000..dd37eb89 --- /dev/null +++ b/patches/Tuinity/patches/server/0049-Don-t-lookup-fluid-state-when-raytracing.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 28 Aug 2020 12:33:47 -0700 +Subject: [PATCH] Don't lookup fluid state when raytracing + +Just use the iblockdata already retrieved, removes a getType call. + +diff --git a/src/main/java/net/minecraft/server/IBlockAccess.java b/src/main/java/net/minecraft/server/IBlockAccess.java +index c4a83448ed4513f6e4ab179d1d43e5bb0cb13641..5c3eb4fc7e5aec2ad8d0050673fc8f4d2bff6a71 100644 +--- a/src/main/java/net/minecraft/server/IBlockAccess.java ++++ b/src/main/java/net/minecraft/server/IBlockAccess.java +@@ -55,7 +55,7 @@ public interface IBlockAccess { + return MovingObjectPositionBlock.a(raytrace1.a(), EnumDirection.a(vec3d.x, vec3d.y, vec3d.z), new BlockPosition(raytrace1.a())); + } + // Paper end +- Fluid fluid = this.getFluid(blockposition); ++ Fluid fluid = iblockdata.getFluid(); // Tuinity - don't need to go to world state again + Vec3D vec3d = raytrace1.b(); + Vec3D vec3d1 = raytrace1.a(); + VoxelShape voxelshape = raytrace1.a(iblockdata, this, blockposition); diff --git a/patches/Tuinity/patches/server/0050-Reduce-pathfinder-branches.patch b/patches/Tuinity/patches/server/0050-Reduce-pathfinder-branches.patch new file mode 100644 index 00000000..21faa4e8 --- /dev/null +++ b/patches/Tuinity/patches/server/0050-Reduce-pathfinder-branches.patch @@ -0,0 +1,125 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 28 Aug 2020 13:09:15 -0700 +Subject: [PATCH] Reduce pathfinder branches + +Reduce static path type detection to simple lazy-init fields + +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index 85f60b56b5689b77ba3d9e99e29b4f734df5d91e..2760b377d1f68ac5f66e7274317379e2dda8288a 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -309,7 +309,9 @@ public abstract class BlockBase { + private final BlockBase.e o; + private final BlockBase.e p; + @Nullable +- protected BlockBase.BlockData.Cache a; ++ protected BlockBase.BlockData.Cache a; protected final BlockBase.BlockData.Cache getShapeCache() { return this.a; } // Tuinity - OBFHELPER ++ public PathType staticPathType; // Tuinity - cache static path types ++ public PathType neighbourOverridePathType; // Tuinity - cache static path types + + protected BlockData(Block block, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { + super(block, immutablemap, mapcodec); +@@ -357,6 +359,8 @@ public abstract class BlockBase { + this.a = new BlockBase.BlockData.Cache(this.p()); + } + this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here ++ this.staticPathType = null; // Tuinity - cache static path type ++ this.neighbourOverridePathType = null; // Tuinity - cache static path types + + } + +diff --git a/src/main/java/net/minecraft/server/PathType.java b/src/main/java/net/minecraft/server/PathType.java +index fb37f5b500c52f915b4536e5ec35552b75056046..52a2d3db7da3596bfdd6fd51147cc93bbe6c7ed0 100644 +--- a/src/main/java/net/minecraft/server/PathType.java ++++ b/src/main/java/net/minecraft/server/PathType.java +@@ -4,6 +4,8 @@ public enum PathType { + + BLOCKED(-1.0F), OPEN(0.0F), WALKABLE(0.0F), WALKABLE_DOOR(0.0F), TRAPDOOR(0.0F), FENCE(-1.0F), LAVA(-1.0F), WATER(8.0F), WATER_BORDER(8.0F), RAIL(0.0F), UNPASSABLE_RAIL(-1.0F), DANGER_FIRE(8.0F), DAMAGE_FIRE(16.0F), DANGER_CACTUS(8.0F), DAMAGE_CACTUS(-1.0F), DANGER_OTHER(8.0F), DAMAGE_OTHER(-1.0F), DOOR_OPEN(0.0F), DOOR_WOOD_CLOSED(-1.0F), DOOR_IRON_CLOSED(-1.0F), BREACH(4.0F), LEAVES(-1.0F), STICKY_HONEY(8.0F), COCOA(0.0F); + ++ PathType belowOverride; // Tuinity ++ + private final float y; + + private PathType(float f) { +diff --git a/src/main/java/net/minecraft/server/PathfinderNormal.java b/src/main/java/net/minecraft/server/PathfinderNormal.java +index 74e81e1e4aea6f74b14a84231ddeb7f2fb845ae7..33804e68931e8b4145b896eedeab79bde78779f2 100644 +--- a/src/main/java/net/minecraft/server/PathfinderNormal.java ++++ b/src/main/java/net/minecraft/server/PathfinderNormal.java +@@ -421,6 +421,12 @@ public class PathfinderNormal extends PathfinderAbstract { + if (pathtype == PathType.OPEN && j >= 1) { + PathType pathtype1 = b(iblockaccess, blockposition_mutableblockposition.d(i, j - 1, k)); + ++ // Tuinity start - reduce pathfinder branches ++ if (pathtype1.belowOverride != null) { ++ pathtype = pathtype1.belowOverride; ++ } else { ++ PathType original1 = pathtype1; ++ // Tuinity end - reduce pathfinder branches + pathtype = pathtype1 != PathType.WALKABLE && pathtype1 != PathType.OPEN && pathtype1 != PathType.WATER && pathtype1 != PathType.LAVA ? PathType.WALKABLE : PathType.OPEN; + if (pathtype1 == PathType.DAMAGE_FIRE) { + pathtype = PathType.DAMAGE_FIRE; +@@ -437,6 +443,7 @@ public class PathfinderNormal extends PathfinderAbstract { + if (pathtype1 == PathType.STICKY_HONEY) { + pathtype = PathType.STICKY_HONEY; + } ++ original1.belowOverride = pathtype; } // Tuinity - reduce pathfinder branches + } + + if (pathtype == PathType.WALKABLE) { +@@ -462,22 +469,29 @@ public class PathfinderNormal extends PathfinderAbstract { + pathtype = PathType.BLOCKED; + } else { + // Paper end +- ++ // Tuinity start - reduce pathfinder branching ++ if (iblockdata.neighbourOverridePathType == PathType.OPEN) { ++ continue; ++ } else if (iblockdata.neighbourOverridePathType != null) { ++ return iblockdata.neighbourOverridePathType; ++ } ++ // Tuinity end - reduce pathfinder branching + if (iblockdata.a(Blocks.CACTUS)) { +- return PathType.DANGER_CACTUS; ++ return iblockdata.neighbourOverridePathType = PathType.DANGER_CACTUS; // Tuinity - reduce pathfinder branching + } + + if (iblockdata.a(Blocks.SWEET_BERRY_BUSH)) { +- return PathType.DANGER_OTHER; ++ return iblockdata.neighbourOverridePathType = PathType.DANGER_OTHER; // Tuinity - reduce pathfinder branching + } + + if (a(iblockdata)) { +- return PathType.DANGER_FIRE; ++ return iblockdata.neighbourOverridePathType = PathType.DANGER_FIRE; // Tuinity - reduce pathfinder branching + } + + if (iblockdata.getFluid().a((Tag) TagsFluid.WATER)) { // Paper - remove another getType call +- return PathType.WATER_BORDER; ++ return iblockdata.neighbourOverridePathType = PathType.WATER_BORDER; // Tuinity - reduce pathfinder branching + } ++ iblockdata.neighbourOverridePathType = PathType.OPEN; // Tuinity - reduce pathfinder branching + } // Paper + } + } +@@ -490,6 +504,20 @@ public class PathfinderNormal extends PathfinderAbstract { + protected static PathType b(IBlockAccess iblockaccess, BlockPosition blockposition) { + IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper + if (iblockdata == null) return PathType.BLOCKED; // Paper ++ // Tuinity start - reduce pathfinder branches ++ if (iblockdata.staticPathType != null) { ++ return iblockdata.staticPathType; ++ } ++ if (iblockdata.getShapeCache() == null) { ++ // while it might be called static, it might vary on shape! However, only a few blocks have variable shape. ++ // So we rarely enter here. ++ return getStaticTypeSlow(iblockaccess, blockposition, iblockdata); ++ } else { ++ return iblockdata.staticPathType = getStaticTypeSlow(iblockaccess, blockposition, iblockdata); ++ } ++ } ++ protected static PathType getStaticTypeSlow(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata) { ++ // Tuinity end - reduce pathfinder branches + Block block = iblockdata.getBlock(); + Material material = iblockdata.getMaterial(); + diff --git a/patches/Tuinity/patches/server/0051-Add-Velocity-natives-for-encryption-and-compression.patch b/patches/Tuinity/patches/server/0051-Add-Velocity-natives-for-encryption-and-compression.patch new file mode 100644 index 00000000..1860e1dd --- /dev/null +++ b/patches/Tuinity/patches/server/0051-Add-Velocity-natives-for-encryption-and-compression.patch @@ -0,0 +1,320 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Wed, 23 Sep 2020 01:46:46 -0400 +Subject: [PATCH] Add Velocity natives for encryption and compression + + +diff --git a/pom.xml b/pom.xml +index 650c6b91440d4ce1c5f82517a00056673750e74e..ddb06df414408461e2ad270e4849514cc7d3ec78 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -149,6 +149,13 @@ + 4.8.47 + test + ++ ++ ++ com.velocitypowered ++ velocity-native ++ 1.1.0-SNAPSHOT ++ compile ++ + + + +diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java +index c61cd50df0c81f7ab12bd0c955fd6f07f2b02e64..d987483255195c0bde713a92676baced1eaff2b3 100644 +--- a/src/main/java/net/minecraft/server/LoginListener.java ++++ b/src/main/java/net/minecraft/server/LoginListener.java +@@ -234,7 +234,7 @@ public class LoginListener implements PacketLoginInListener { + + s = (new BigInteger(MinecraftEncryption.a("", this.server.getKeyPair().getPublic(), this.loginKey))).toString(16); + this.g = LoginListener.EnumProtocolState.AUTHENTICATING; +- this.networkManager.a(cipher, cipher1); ++ this.networkManager.a(this.loginKey); // Tuinity + } catch (CryptographyException cryptographyexception) { + throw new IllegalStateException("Protocol error", cryptographyexception); + } +diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java +index c918eba753180670a22e25afe58307cd2d289952..3945233e2182f38a7b4419792ca2a931b90ed787 100644 +--- a/src/main/java/net/minecraft/server/NetworkManager.java ++++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -542,10 +542,16 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + return this.channel instanceof LocalChannel || this.channel instanceof LocalServerChannel; + } + +- public void a(Cipher cipher, Cipher cipher1) { ++ public void a(javax.crypto.SecretKey secretkey) { // Tuinity + this.n = true; +- this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(cipher)); +- this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(cipher1)); ++ // Tuinity start ++ try { ++ this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(/*MinecraftEncryption.a(2, secretkey)*/ secretkey)); ++ this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(/*MinecraftEncryption.a(1, secretkey)*/ secretkey)); ++ } catch (java.security.GeneralSecurityException e) { ++ throw new RuntimeException("Couldn't enable encryption", e); ++ } ++ // Tuinity end + } + + public boolean isConnected() { +diff --git a/src/main/java/net/minecraft/server/PacketCompressor.java b/src/main/java/net/minecraft/server/PacketCompressor.java +index 3cdd07cad85ef2d2c4b6c27a55a878695b4a7b12..50b2a8dfbdd0fe60e295d7c7214d7c99bcbeb19a 100644 +--- a/src/main/java/net/minecraft/server/PacketCompressor.java ++++ b/src/main/java/net/minecraft/server/PacketCompressor.java +@@ -7,14 +7,18 @@ import java.util.zip.Deflater; + + public class PacketCompressor extends MessageToByteEncoder { + +- private final byte[] a = new byte[8192]; +- private final Deflater b; ++ // Tuinity start - use Velocity natives ++// private final byte[] a = new byte[8192]; ++// private final Deflater b; + private int c; ++ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; + + public PacketCompressor(int i) { + this.c = i; +- this.b = new Deflater(); ++// this.b = new Deflater(); ++ this.compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); + } ++ // Tuinity end + + protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception { + int i = bytebuf.readableBytes(); +@@ -24,24 +28,46 @@ public class PacketCompressor extends MessageToByteEncoder { + packetdataserializer.d(0); + packetdataserializer.writeBytes(bytebuf); + } else { +- byte[] abyte = new byte[i]; +- +- bytebuf.readBytes(abyte); +- packetdataserializer.d(abyte.length); +- this.b.setInput(abyte, 0, i); +- this.b.finish(); +- +- while (!this.b.finished()) { +- int j = this.b.deflate(this.a); +- +- packetdataserializer.writeBytes(this.a, 0, j); ++ // Tuinity start - delegate to Velocity natives ++// byte[] abyte = new byte[i]; ++// ++// bytebuf.readBytes(abyte); ++// packetdataserializer.d(abyte.length); ++// this.b.setInput(abyte, 0, i); ++// this.b.finish(); ++// ++// while (!this.b.finished()) { ++// int j = this.b.deflate(this.a); ++// ++// packetdataserializer.writeBytes(this.a, 0, j); ++// } ++// ++// this.b.reset(); ++ packetdataserializer.d(i); ++ ByteBuf source = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelhandlercontext.alloc(), ++ this.compressor, bytebuf); ++ try { ++ this.compressor.deflate(source, bytebuf1); ++ } finally { ++ source.release(); + } +- +- this.b.reset(); ++ // Tuinity end + } + + } + ++ // Tuinity start ++ @Override ++ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { ++ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, msg.readableBytes() + 1); ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ this.compressor.close(); ++ } ++ // Tuinity end ++ + public void a(int i) { + this.c = i; + } +diff --git a/src/main/java/net/minecraft/server/PacketDecompressor.java b/src/main/java/net/minecraft/server/PacketDecompressor.java +index 23c850be0155c1ece807d244117a196488f0a13b..4bab19a52b400071e69b06b940ab6432dfe59a1b 100644 +--- a/src/main/java/net/minecraft/server/PacketDecompressor.java ++++ b/src/main/java/net/minecraft/server/PacketDecompressor.java +@@ -10,13 +10,17 @@ import java.util.zip.Inflater; + + public class PacketDecompressor extends ByteToMessageDecoder { + +- private final Inflater a; ++ // Tuinity start - use Velocity natives ++ //private final Inflater a; ++ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; + private int b; + + public PacketDecompressor(int i) { + this.b = i; +- this.a = new Inflater(); ++ //this.a = new Inflater(); ++ this.compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); + } ++ // Tuinity end + + protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { + if (bytebuf.readableBytes() != 0) { +@@ -34,20 +38,41 @@ public class PacketDecompressor extends ByteToMessageDecoder { + throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of " + 2097152); + } + +- byte[] abyte = new byte[packetdataserializer.readableBytes()]; +- +- packetdataserializer.readBytes(abyte); +- this.a.setInput(abyte); +- byte[] abyte1 = new byte[i]; +- +- this.a.inflate(abyte1); +- list.add(Unpooled.wrappedBuffer(abyte1)); +- this.a.reset(); ++ // Tuinity start ++ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelhandlercontext.alloc(), compressor, bytebuf); ++ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelhandlercontext.alloc(), compressor, i); ++ try { ++ compressor.inflate(compatibleIn, uncompressed, i); ++ list.add(uncompressed); ++ bytebuf.clear(); ++ } catch (Exception e) { ++ uncompressed.release(); ++ throw e; ++ } finally { ++ compatibleIn.release(); ++ } ++// byte[] abyte = new byte[packetdataserializer.readableBytes()]; ++// ++// packetdataserializer.readBytes(abyte); ++// this.a.setInput(abyte); ++// byte[] abyte1 = new byte[i]; ++// ++// this.a.inflate(abyte1); ++// list.add(Unpooled.wrappedBuffer(abyte1)); ++// this.a.reset(); ++ // Tuinity end + } + + } + } + ++ // Tuinity start ++ @Override ++ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { ++ this.compressor.close(); ++ } ++ // Tuinity end ++ + public void a(int i) { + this.b = i; + } +diff --git a/src/main/java/net/minecraft/server/PacketDecrypter.java b/src/main/java/net/minecraft/server/PacketDecrypter.java +index c85f291c5b22a8e85c7556b220cba698701748f2..771cc0f4fa98be294abba65c83442205b6b0ef0b 100644 +--- a/src/main/java/net/minecraft/server/PacketDecrypter.java ++++ b/src/main/java/net/minecraft/server/PacketDecrypter.java +@@ -8,13 +8,24 @@ import javax.crypto.Cipher; + + public class PacketDecrypter extends MessageToMessageDecoder { + +- private final PacketEncryptionHandler a; ++ // Tuinity start ++ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; ++ //private final PacketEncryptionHandler a; + +- public PacketDecrypter(Cipher cipher) { +- this.a = new PacketEncryptionHandler(cipher); ++ public PacketDecrypter(javax.crypto.SecretKey key /* Cipher cipher */) throws java.security.GeneralSecurityException { ++ //this.a = new PacketEncryptionHandler(cipher); ++ this.cipher = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); + } + + protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { +- list.add(this.a.a(channelhandlercontext, bytebuf)); ++ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelhandlercontext.alloc(), cipher, bytebuf).slice(); ++ try { ++ cipher.process(compatible); ++ list.add(compatible); ++ } catch (Exception e) { ++ compatible.release(); // compatible will never be used if we throw an exception ++ throw e; ++ } + } ++ // Tuinity end + } +diff --git a/src/main/java/net/minecraft/server/PacketEncrypter.java b/src/main/java/net/minecraft/server/PacketEncrypter.java +index e35369476839e9622520af1027d7478aa6d1b037..aba14794cc4cb114fea17bb92816ac29a64b44f8 100644 +--- a/src/main/java/net/minecraft/server/PacketEncrypter.java ++++ b/src/main/java/net/minecraft/server/PacketEncrypter.java +@@ -5,15 +5,38 @@ import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToByteEncoder; + import javax.crypto.Cipher; + +-public class PacketEncrypter extends MessageToByteEncoder { ++// Tuinity start ++// We rewrite this class as the Velocity natives support in-place encryption ++import io.netty.handler.codec.MessageToMessageEncoder; // An unfortunate import, but this is required to fix a compiler error ++public class PacketEncrypter extends MessageToMessageEncoder { + +- private final PacketEncryptionHandler a; ++ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; ++ //private final PacketEncryptionHandler a; + +- public PacketEncrypter(Cipher cipher) { +- this.a = new PacketEncryptionHandler(cipher); ++ public PacketEncrypter(javax.crypto.SecretKey key /* Cipher cipher */) throws java.security.GeneralSecurityException { ++ // this.a = new PacketEncryptionHandler(cipher); ++ this.cipher = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); + } + +- protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception { +- this.a.a(bytebuf, bytebuf1); ++// protected void encode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, ByteBuf bytebuf1) throws Exception { ++// this.a.a(bytebuf, bytebuf1); ++// } ++ ++ @Override ++ protected void encode(ChannelHandlerContext ctx, ByteBuf msg, java.util.List out) throws Exception { ++ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(ctx.alloc(), this.cipher, msg); ++ try { ++ this.cipher.process(compatible); ++ out.add(compatible); ++ } catch (Exception e) { ++ compatible.release(); // compatible will never be used if we throw an exception ++ throw e; ++ } ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ cipher.close(); + } + } ++// Tuinity end +diff --git a/src/main/java/net/minecraft/server/ServerConnection.java b/src/main/java/net/minecraft/server/ServerConnection.java +index 5f4dacf9c93c2495a07df2647fe0411f796da6af..0668d383db1f3a81d1053954d72678c7ac5aecec 100644 +--- a/src/main/java/net/minecraft/server/ServerConnection.java ++++ b/src/main/java/net/minecraft/server/ServerConnection.java +@@ -74,6 +74,11 @@ public class ServerConnection { + ServerConnection.LOGGER.info("Using default channel type"); + } + ++ // Tuinity start - indicate Velocity natives in use ++ ServerConnection.LOGGER.info("Tuinity: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity."); ++ ServerConnection.LOGGER.info("Tuinity: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); ++ // Tuinity end ++ + this.listeningChannels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { + protected void initChannel(Channel channel) throws Exception { + try { diff --git a/patches/Tuinity/patches/server/0052-Optimise-non-flush-packet-sending.patch b/patches/Tuinity/patches/server/0052-Optimise-non-flush-packet-sending.patch new file mode 100644 index 00000000..cc090c11 --- /dev/null +++ b/patches/Tuinity/patches/server/0052-Optimise-non-flush-packet-sending.patch @@ -0,0 +1,177 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 22 Sep 2020 01:49:19 -0700 +Subject: [PATCH] Optimise non-flush packet sending + +Places like entity tracking make heavy use of packet sending, +and internally netty will use some very expensive thread wakeup +calls when scheduling. + +Thanks to various hacks in ProtocolLib as well as other +plugins, we cannot simply use a queue of packets to group +send on execute. We have to call execute for each packet. + +Tux's suggestion here is exactly what was needed - tag +the Runnable indicating it should not make a wakeup call. + +Big thanks to Tux for making this possible as I had given +up on this optimisation before he came along. + +Locally this patch drops the entity tracker tick by a full 1.5x. + +diff --git a/pom.xml b/pom.xml +index ddb06df414408461e2ad270e4849514cc7d3ec78..e83e4241a56fe131a75fe21cc1518992c089da2c 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -38,6 +38,13 @@ + ${project.version} + compile + ++ ++ ++ io.netty ++ netty-all ++ 4.1.50.Final ++ ++ + + io.papermc + minecraft-server +@@ -105,11 +112,7 @@ + cleaner + 1.0-SNAPSHOT + +- +- io.netty +- netty-all +- 4.1.50.Final +- ++ + + + com.googlecode.json-simple +diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java +index 3945233e2182f38a7b4419792ca2a931b90ed787..6a0ec0105399066dede622b45c9471b32c162cf6 100644 +--- a/src/main/java/net/minecraft/server/NetworkManager.java ++++ b/src/main/java/net/minecraft/server/NetworkManager.java +@@ -27,6 +27,8 @@ import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.Marker; + import org.apache.logging.log4j.MarkerManager; + ++import io.netty.util.concurrent.AbstractEventExecutor; // Tuinity ++ + public class NetworkManager extends SimpleChannelInboundHandler> { + + private static final Logger LOGGER = LogManager.getLogger(); +@@ -386,39 +388,83 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + } + // Paper end + } else { +- this.channel.eventLoop().execute(() -> { +- if (enumprotocol != enumprotocol1) { +- this.setProtocol(enumprotocol); +- } ++ // Tuinity start - optimise packets that are not flushed ++ Runnable choice1 = null; ++ AbstractEventExecutor.LazyRunnable choice2 = null; ++ // note: since the type is not dynamic here, we need to actually copy the old executor code ++ // into two branches. On conflict, just re-copy - no changes were made inside the executor code. ++ if (flush) { ++ choice1 = () -> { ++ if (enumprotocol != enumprotocol1) { ++ this.setProtocol(enumprotocol); ++ } + +- // Paper start +- if (!isConnected()) { +- packet.onPacketDispatchFinish(player, null); +- return; +- } +- try { ++ // Paper start ++ if (!isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end ++ ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter ++ ++ ++ if (genericfuturelistener != null) { ++ channelfuture1.addListener(genericfuturelistener); ++ } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } ++ // Paper end ++ ++ channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ // Paper start ++ } catch (Exception e) { ++ LOGGER.error("NetworkException: " + player, e); ++ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; ++ packet.onPacketDispatchFinish(player, null); ++ } + // Paper end +- ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter ++ }; ++ } else { ++ // explicitly declare a variable to make the lambda use the type ++ choice2 = () -> { ++ if (enumprotocol != enumprotocol1) { ++ this.setProtocol(enumprotocol); ++ } + ++ // Paper start ++ if (!isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end ++ ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter + +- if (genericfuturelistener != null) { +- channelfuture1.addListener(genericfuturelistener); +- } +- // Paper start +- if (packet.hasFinishListener()) { +- channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); +- } +- // Paper end + +- channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); +- // Paper start +- } catch (Exception e) { +- LOGGER.error("NetworkException: " + player, e); +- close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; +- packet.onPacketDispatchFinish(player, null); +- } +- // Paper end +- }); ++ if (genericfuturelistener != null) { ++ channelfuture1.addListener(genericfuturelistener); ++ } ++ // Paper start ++ if (packet.hasFinishListener()) { ++ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } ++ // Paper end ++ ++ channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ // Paper start ++ } catch (Exception e) { ++ LOGGER.error("NetworkException: " + player, e); ++ close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; ++ packet.onPacketDispatchFinish(player, null); ++ } ++ // Paper end ++ }; ++ } ++ this.channel.eventLoop().execute(choice1 != null ? choice1 : choice2); ++ // Tuinity end - optimise packets that are not flushed + } + + } diff --git a/patches/Tuinity/patches/server/0053-Do-not-retain-playerchunkmap-instance-in-light-threa.patch b/patches/Tuinity/patches/server/0053-Do-not-retain-playerchunkmap-instance-in-light-threa.patch new file mode 100644 index 00000000..285a755a --- /dev/null +++ b/patches/Tuinity/patches/server/0053-Do-not-retain-playerchunkmap-instance-in-light-threa.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 12 Nov 2020 08:06:55 -0800 +Subject: [PATCH] Do not retain playerchunkmap instance in light thread factory + +The executor returned is finalizable and of course +that causes issues. + +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index f4d5ff1d0f1ad34032aaab96e1077f4be43d4bf3..caaffea1b670ddfd20bf39cbd55da1c5cf561288 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -337,9 +337,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + this.worldLoadListener = worldloadlistener; + // Paper start - use light thread ++ String threadName = ((WorldDataServer)this.world.getWorldData()).getName() + " - Light"; // Tuinity - make sure playerchunkmap instance is not retained by the thread factory + ThreadedMailbox lightthreaded; ThreadedMailbox threadedmailbox1 = lightthreaded = ThreadedMailbox.a(lightThread = java.util.concurrent.Executors.newSingleThreadExecutor(r -> { + Thread thread = new Thread(r); +- thread.setName(((WorldDataServer)world.getWorldData()).getName() + " - Light"); ++ thread.setName(threadName); // Tuinity - make sure playerchunkmap instance is not retained by the thread factory + thread.setDaemon(true); + thread.setPriority(Thread.NORM_PRIORITY+1); + return thread; diff --git a/patches/Tuinity/patches/server/0054-Do-not-load-chunks-during-a-crash-report.patch b/patches/Tuinity/patches/server/0054-Do-not-load-chunks-during-a-crash-report.patch new file mode 100644 index 00000000..af9946f5 --- /dev/null +++ b/patches/Tuinity/patches/server/0054-Do-not-load-chunks-during-a-crash-report.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 10 Dec 2020 14:38:58 -0800 +Subject: [PATCH] Do not load chunks during a crash report + +This causes deadlocks in some cases when generating +crash reports. + +Fixes https://github.com/Spottedleaf/Tuinity/issues/215 + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java +index 7511e38130f38703164395a670f12d1af648ff04..e602efcb3fad390bb6bff1055e782bba909d7694 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java +@@ -37,7 +37,7 @@ public class CraftCrashReport implements CrashReportCallable { + value.append("\n Force Loaded Chunks: {"); + for (World world : Bukkit.getWorlds()) { + value.append(' ').append(world.getName()).append(": {"); +- for (Map.Entry> entry : world.getPluginChunkTickets().entrySet()) { ++ for (Map.Entry> entry : ((CraftWorld)world).getPluginChunkTicketsCoordinates().entrySet()) { // Tuinity - do not load chunks in crash reports + value.append(' ').append(entry.getKey().getDescription().getFullName()).append(": ").append(Integer.toString(entry.getValue().size())).append(','); + } + value.append("},"); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 545a8bcf16215bde467d39881b19486bee7db873..f34e1570052eac83fb3e03b3e361d8d4f451abac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -719,6 +719,30 @@ public class CraftWorld implements World { + return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build())); + } + ++ // Tuinity start - don't load chunks for crash reports ++ public Map> getPluginChunkTicketsCoordinates() { ++ // Copied from above ++ Map> ret = new HashMap<>(); ++ ChunkMapDistance chunkDistanceManager = this.world.getChunkProvider().playerChunkMap.chunkDistanceManager; ++ ++ for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) { ++ long chunkKey = chunkTickets.getLongKey(); ++ ArraySetSorted> tickets = chunkTickets.getValue(); ++ ++ ChunkCoordIntPair chunk = new ChunkCoordIntPair(chunkKey); ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() != TicketType.PLUGIN_TICKET) { ++ continue; ++ } ++ ++ ret.computeIfAbsent((Plugin) ticket.identifier, (key) -> ImmutableList.builder()).add(chunk); ++ } ++ } ++ ++ return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build())); ++ } ++ // Tuinity end - don't load chunks for crash reports ++ + @Override + public boolean isChunkForceLoaded(int x, int z) { + return getHandle().getForceLoadedChunks().contains(ChunkCoordIntPair.pair(x, z)); diff --git a/patches/Tuinity/patches/server/0055-Improve-abnormal-server-shutdown-process.patch b/patches/Tuinity/patches/server/0055-Improve-abnormal-server-shutdown-process.patch new file mode 100644 index 00000000..25f8c197 --- /dev/null +++ b/patches/Tuinity/patches/server/0055-Improve-abnormal-server-shutdown-process.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 10 Dec 2020 15:18:05 -0800 +Subject: [PATCH] Improve abnormal server shutdown process + +- When we're trying to kill the main thread from watchdog, +step up the stop() spamming after 15s to really kill the main thread. + +- Do not wait for window disposing when disposing of the server +gui. It looks like during sigint shutdown there can be some +deadlock between the server thread and awt shutdown thread here. + +diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java +index fcba187bbdc1b468cfea2bc922187d9b8959a9d5..ecff0657e5666ddc2e6a5c3111bfb2b8dd2b78d3 100644 +--- a/src/main/java/net/minecraft/server/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/DedicatedServer.java +@@ -358,7 +358,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + } + + if (this.q != null) { +- this.q.b(); ++ //this.q.b(); // Tuinity - do not wait for AWT, causes deadlock with sigint handler (AWT shutdown will properly clear our resources anyways) + } + + if (this.remoteControlListener != null) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 19b26543a1503b8710ef5c013d0cf26e55749bfd..45e310e249a83714d0001d85b2ead8d4f8a2d742 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -750,10 +750,11 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +Date: Thu, 10 Dec 2020 15:35:43 -0800 +Subject: [PATCH] Copy passenger list in enderTeleportTo + +Fixes https://github.com/Spottedleaf/Tuinity/issues/208 + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 3ad523b8ff4d5954cafb0dd71950262d83b04e8f..013c44f80f74376e8bbb37afb5de07aa5d8fb1bc 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -3084,7 +3084,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + this.recursiveStream().forEach((entity) -> { + worldserver.chunkCheck(entity); + entity.az = true; +- Iterator iterator = entity.passengers.iterator(); ++ Iterator iterator = new java.util.ArrayList<>(entity.passengers).iterator(); // Tuinity - copy list to guard against CME + + while (iterator.hasNext()) { + Entity entity1 = (Entity) iterator.next(); diff --git a/patches/Tuinity/patches/server/0057-Revert-MC-4-fix.patch b/patches/Tuinity/patches/server/0057-Revert-MC-4-fix.patch new file mode 100644 index 00000000..b68bb690 --- /dev/null +++ b/patches/Tuinity/patches/server/0057-Revert-MC-4-fix.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 22 Dec 2020 12:06:15 -0800 +Subject: [PATCH] Revert MC-4 fix + +When messing around with collisions, I ran into problems where +entity position was off by ULP and that caused clipping problems. +Now, the collision epsilon is 1.0e-7 to account for those errors. + +But this patch is going to cause problems on the order of 1.0e-4. + +I do not want to deal with clipping problems. The very fact it works +shows it's causing the clipping to occur serverside. + +diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java +index f41aaa7623c052b9f4044898d1bdee898c03057a..d99cecc4075338d7b8f154ab94d8ac04190ba371 100644 +--- a/src/main/java/net/minecraft/server/EntityItem.java ++++ b/src/main/java/net/minecraft/server/EntityItem.java +@@ -526,7 +526,7 @@ public class EntityItem extends Entity { + + // Paper start - fix MC-4 + public void setPositionRaw(double x, double y, double z) { +- if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { ++ if (false && com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { // Tuinity - revert + // encode/decode from PacketPlayOutEntity + x = MathHelper.floorLong(x * 4096.0D) * (1 / 4096.0D); + y = MathHelper.floorLong(y * 4096.0D) * (1 / 4096.0D); diff --git a/patches/Tuinity/patches/server/0058-Prevent-light-queue-overfill-when-no-players-are-onl.patch b/patches/Tuinity/patches/server/0058-Prevent-light-queue-overfill-when-no-players-are-onl.patch new file mode 100644 index 00000000..f65652e1 --- /dev/null +++ b/patches/Tuinity/patches/server/0058-Prevent-light-queue-overfill-when-no-players-are-onl.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 22 Dec 2020 21:12:05 -0800 +Subject: [PATCH] Prevent light queue overfill when no players are online + +block changes don't queue light updates (and they shouldn't) + +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index a44fbfaf42170e57729ecbc23e034d8745d45785..38ca1c042afd41a1f660f88e398fedde00f34e39 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -1211,7 +1211,7 @@ public class ChunkProviderServer extends IChunkProvider { + if (ChunkProviderServer.this.tickDistanceManager()) { + return true; + } else { +- //ChunkProviderServer.this.lightEngine.queueUpdate(); // Paper - not needed ++ ChunkProviderServer.this.lightEngine.queueUpdate(); // Paper - not needed // Tuinity - prevent queue overflow when no players are in this world + return super.executeNext() || execChunkTask; // Paper + } + } finally { diff --git a/patches/Tuinity/patches/server/0059-Don-t-allow-StructureLocateEvent-to-change-worlds.patch b/patches/Tuinity/patches/server/0059-Don-t-allow-StructureLocateEvent-to-change-worlds.patch new file mode 100644 index 00000000..26815c29 --- /dev/null +++ b/patches/Tuinity/patches/server/0059-Don-t-allow-StructureLocateEvent-to-change-worlds.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 18 Jan 2021 10:17:39 -0800 +Subject: [PATCH] Don't allow StructureLocateEvent to change worlds + +Callers and even the function itself aren't expecting +this to happen + +diff --git a/src/main/java/net/minecraft/server/ChunkGenerator.java b/src/main/java/net/minecraft/server/ChunkGenerator.java +index 097cb9896c525a605c50e83548f828e0c71ab3d5..17cf00dfe8b24adf2fb66eca4710ab7888a894e3 100644 +--- a/src/main/java/net/minecraft/server/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/server/ChunkGenerator.java +@@ -135,7 +135,7 @@ public abstract class ChunkGenerator { + // Get origin location (re)defined by event call. + blockposition = new BlockPosition(event.getOrigin().getBlockX(), event.getOrigin().getBlockY(), event.getOrigin().getBlockZ()); + // Get world (re)defined by event call. +- worldserver = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle(); ++ //worldserver = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle(); // Tuinity - callers and this function don't expect this to change + // Get radius and whether to find unexplored structures (re)defined by event call. + i = event.getRadius(); + flag = event.shouldFindUnexplored(); diff --git a/patches/Tuinity/patches/server/0060-Properly-handle-cancellation-of-projectile-hit-event.patch b/patches/Tuinity/patches/server/0060-Properly-handle-cancellation-of-projectile-hit-event.patch new file mode 100644 index 00000000..c58897e1 --- /dev/null +++ b/patches/Tuinity/patches/server/0060-Properly-handle-cancellation-of-projectile-hit-event.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 18 Jan 2021 14:02:22 -0800 +Subject: [PATCH] Properly handle cancellation of projectile hit event + +Subclasses override and run logic. Some of this logic destroys +the projectile as well, which wouldn't be fitting for cancellation. + +diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java +index 1e7f5957d879d1ba8cf2b29cf9397b8e204e4381..f983516b89cdf7ce7fdea8f5a5b1a29dd01ae597 100644 +--- a/src/main/java/net/minecraft/server/EntityArrow.java ++++ b/src/main/java/net/minecraft/server/EntityArrow.java +@@ -173,8 +173,10 @@ public abstract class EntityArrow extends IProjectile { + // Paper end + + if (object != null && !flag) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, (MovingObjectPosition) object)) { // CraftBukkit - Call event // Paper - make cancellable // Tuinity - implement cancellation properly + this.a((MovingObjectPosition) object); + this.impulse = true; ++ } // Tuinity - implement cancellation properly + } + + if (movingobjectpositionentity == null || this.getPierceLevel() <= 0) { +diff --git a/src/main/java/net/minecraft/server/EntityFireball.java b/src/main/java/net/minecraft/server/EntityFireball.java +index 0840fdf3585407ec317f0326359619220c64db78..6b9b64539d2272070b523ed6b927de02d2b00af5 100644 +--- a/src/main/java/net/minecraft/server/EntityFireball.java ++++ b/src/main/java/net/minecraft/server/EntityFireball.java +@@ -67,7 +67,9 @@ public abstract class EntityFireball extends IProjectile { + // Paper end + + if (movingobjectposition != null && movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS) { // Paper - add null check in case cancelled ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) { // CraftBukkit - Call event // Paper - make cancellable // Tuinity - implement cancellation properly + this.a(movingobjectposition); ++ } // Tuinity - implement cancellation properly + + // CraftBukkit start - Fire ProjectileHitEvent + if (this.dead) { +diff --git a/src/main/java/net/minecraft/server/EntityFireworks.java b/src/main/java/net/minecraft/server/EntityFireworks.java +index a646dc9f030ad1f76ba2b7bb1bc7897cd34b648c..dd18eabd7104995f0e6a8ecb279a3872b46773de 100644 +--- a/src/main/java/net/minecraft/server/EntityFireworks.java ++++ b/src/main/java/net/minecraft/server/EntityFireworks.java +@@ -124,8 +124,10 @@ public class EntityFireworks extends IProjectile { + MovingObjectPosition movingobjectposition = ProjectileHelper.a((Entity) this, this::a); + + if (!this.noclip) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) { // CraftBukkit - Call event // Paper - make cancellable // Tuinity - implement cancellation properly + this.a(movingobjectposition); + this.impulse = true; ++ } // Tuinity - implement cancellation properly + } + + this.x(); +diff --git a/src/main/java/net/minecraft/server/EntityFishingHook.java b/src/main/java/net/minecraft/server/EntityFishingHook.java +index e97c7794e86c0518bcec0a0370bffbeab20e2623..0816ab54bc99bcf29356b56516e83759a3f2988f 100644 +--- a/src/main/java/net/minecraft/server/EntityFishingHook.java ++++ b/src/main/java/net/minecraft/server/EntityFishingHook.java +@@ -226,7 +226,9 @@ public class EntityFishingHook extends IProjectile { + private void m() { + MovingObjectPosition movingobjectposition = ProjectileHelper.a((Entity) this, this::a); + ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) { // CraftBukkit - Call event // Paper - make cancellable // Tuinity - implement cancellation properly + this.a(movingobjectposition); ++ } // Tuinity - implement cancellation properly + } + + @Override +diff --git a/src/main/java/net/minecraft/server/EntityLlamaSpit.java b/src/main/java/net/minecraft/server/EntityLlamaSpit.java +index 7636a51a7ef0aa05b5b2aaa9d17e7b551dedac96..480a02a8f6ec7110f9af8f2037fdc09a7a54ef01 100644 +--- a/src/main/java/net/minecraft/server/EntityLlamaSpit.java ++++ b/src/main/java/net/minecraft/server/EntityLlamaSpit.java +@@ -19,7 +19,9 @@ public class EntityLlamaSpit extends IProjectile { + MovingObjectPosition movingobjectposition = ProjectileHelper.a((Entity) this, this::a); + + if (movingobjectposition != null) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) { // CraftBukkit - Call event // Paper - make cancellable // Tuinity - implement cancellation properly + this.a(movingobjectposition); ++ } // Tuinity - implement cancellation properly + } + + double d0 = this.locX() + vec3d.x; +diff --git a/src/main/java/net/minecraft/server/EntityProjectile.java b/src/main/java/net/minecraft/server/EntityProjectile.java +index 53a8ea7d1eff84abe6c49464d556aa2788a6abcb..d85a19905efab7189e461a61becb6ca2b8c50803 100644 +--- a/src/main/java/net/minecraft/server/EntityProjectile.java ++++ b/src/main/java/net/minecraft/server/EntityProjectile.java +@@ -48,7 +48,7 @@ public abstract class EntityProjectile extends IProjectile { + movingobjectposition = null; + } + } +- if (movingobjectposition != null) { ++ if (movingobjectposition != null && org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) { // Tuinity - implement cancellation properly + // Paper end + this.a(movingobjectposition); + } // Paper +diff --git a/src/main/java/net/minecraft/server/EntityShulkerBullet.java b/src/main/java/net/minecraft/server/EntityShulkerBullet.java +index 23017b5486530bcf76b3934cfa8621e8b4772b27..a4d94385ede0303417d676155c2c0b226681cc59 100644 +--- a/src/main/java/net/minecraft/server/EntityShulkerBullet.java ++++ b/src/main/java/net/minecraft/server/EntityShulkerBullet.java +@@ -206,7 +206,7 @@ public class EntityShulkerBullet extends IProjectile { + + MovingObjectPosition movingobjectposition = ProjectileHelper.a((Entity) this, this::a); + +- if (movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS) { ++ if (movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.MISS && org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) { // Tuinity - implement cancellation properly + this.a(movingobjectposition); + } + } +diff --git a/src/main/java/net/minecraft/server/IProjectile.java b/src/main/java/net/minecraft/server/IProjectile.java +index bbc089b41fcbe0141f13591db2cb44b9e688cac4..dc9f2a1a132b3a7925bd62aa1da0512afd90b8b1 100644 +--- a/src/main/java/net/minecraft/server/IProjectile.java ++++ b/src/main/java/net/minecraft/server/IProjectile.java +@@ -118,7 +118,7 @@ public abstract class IProjectile extends Entity { + } + + protected void a(MovingObjectPosition movingobjectposition) { +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition)) return; // CraftBukkit - Call event // Paper - make cancellable ++ // Tuinity - proper cancellation requires moving this into the caller (see method overrides) - TODO this unfortunately means we need to manually inspect each call on update + MovingObjectPosition.EnumMovingObjectType movingobjectposition_enummovingobjecttype = movingobjectposition.getType(); + + if (movingobjectposition_enummovingobjecttype == MovingObjectPosition.EnumMovingObjectType.ENTITY) { diff --git a/patches/Tuinity/patches/server/0061-Rewrite-the-light-engine.patch b/patches/Tuinity/patches/server/0061-Rewrite-the-light-engine.patch new file mode 100644 index 00000000..b47a5ea4 --- /dev/null +++ b/patches/Tuinity/patches/server/0061-Rewrite-the-light-engine.patch @@ -0,0 +1,5483 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 28 Oct 2020 16:51:55 -0700 +Subject: [PATCH] Rewrite the light engine + +The standard vanilla light engine is plagued by +awful performance. Paper's changes to the light engine +help a bit, however they appear to cause some lighting +errors - most easily noticed in coral generation. + +The vanilla light engine's is too abstract to be modified - +so an entirely new implementation is required to fix the +performance and lighting errors. + +The new implementation is designed primarily to optimise +light level propagations (increase and decrease). Unlike +the vanilla light engine, this implementation tracks more +information per queued value when performing a +breadth first search. Vanilla just tracks coordinate, which +means every time they handle a queued value, they must +also determine the coordinate's target light level +from its neighbours - very wasteful, especially considering +these checks read neighbour block data. +The new light engine tracks both position and target level, +as well as whether the target block needs to be read at all +(for checking sided propagation). So, the work done per coordinate +is significantly reduced because no work is done for calculating +the target level. +In my testing, the block get calls were reduced by approximately +an order of magnitude. However, the light read checks were only +reduced by approximately 2x - but this is fine, light read checks +are extremely cheap compared to block gets. + +Generation testing showed that the new light engine improved +total generation (not lighting itself, but the whole generation process) +by 2x. According to cpu time, the light engine itself spent 10x less time +lighting chunks for generation. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 0abfe19e204d20af0f8dedbeedb0ef98dfe9d3c8..1a876336384198bad2a25c018be5f2418027dd47 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -186,6 +186,44 @@ public class PaperCommand extends Command { + } + } + ++ private void starlightFixLight(EntityPlayer sender, WorldServer world, LightEngineThreaded lightengine, int radius) { ++ long start = System.nanoTime(); ++ LinkedHashSet chunks = new LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.getChunkCoordinates(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos ++ ++ int[] pending = new int[1]; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ final ChunkCoordIntPair chunkPos = iterator.next(); ++ ++ final IChunkAccess chunk = world.getChunkProvider().getChunkAtImmediately(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLit() || !chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ ++pending[0]; ++ } ++ ++ int[] relitChunks = new int[1]; ++ lightengine.relight(chunks, ++ (ChunkCoordIntPair chunkPos) -> { ++ ++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])) + "%" ++ ); ++ }, ++ (int totalRelit) -> { ++ final long end = System.nanoTime(); ++ final long diff = Math.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" ++ ); ++ }); ++ sender.getBukkitEntity().sendMessage(ChatColor.BLUE + "Relighting " + ChatColor.DARK_AQUA + pending[0] + ChatColor.BLUE + " chunks"); ++ } ++ + private void doFixLight(CommandSender sender, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Only players can use this command"); +@@ -194,7 +232,7 @@ public class PaperCommand extends Command { + int radius = 2; + if (args.length > 1) { + try { +- radius = Math.min(5, Integer.parseInt(args[1])); ++ radius = Math.min(15, Integer.parseInt(args[1])); // Tuinity - MOOOOOORE + } catch (Exception e) { + sender.sendMessage("Not a number"); + return; +@@ -207,6 +245,13 @@ public class PaperCommand extends Command { + net.minecraft.server.WorldServer world = (WorldServer) handle.world; + LightEngineThreaded lightengine = world.getChunkProvider().getLightEngine(); + ++ // Tuinity start - rewrite light engine ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) { ++ this.starlightFixLight(handle, world, lightengine, radius); ++ return; ++ } ++ // Tuinity end - rewrite light engine ++ + BlockPosition center = MCUtil.toBlockPosition(player.getLocation()); + Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); + updateLight(sender, world, lightengine, queue); +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..43de95779a5472aaf04da11c9f4c5feb7253c6c3 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/BlockStarLightEngine.java +@@ -0,0 +1,277 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.Chunk; ++import net.minecraft.server.ChunkSection; ++import net.minecraft.server.ChunkStatus; ++import net.minecraft.server.DataPaletteBlock; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.ProtoChunkExtension; ++import net.minecraft.server.VoxelShape; ++import net.minecraft.server.VoxelShapes; ++import net.minecraft.server.World; ++import net.minecraft.server.WorldServer; ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Set; ++ ++public final class BlockStarLightEngine extends StarLightEngine { ++ ++ public BlockStarLightEngine(final World world) { ++ super(false, world); ++ } ++ ++ @Override ++ protected boolean[] getEmptinessMap(final IChunkAccess chunk) { ++ return chunk.getBlockEmptinessMap(); ++ } ++ ++ @Override ++ protected void setEmptinessMap(final IChunkAccess chunk, final boolean[] to) { ++ chunk.setBlockEmptinessMap(to); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk) { ++ return chunk.getBlockNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setBlockNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final IChunkAccess chunk) { ++ return chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLit()); ++ } ++ ++ @Override ++ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { ++ return; ++ } ++ ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble == null) { ++ if (!initRemovedNibbles) { ++ throw new IllegalStateException(); ++ } else { ++ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); ++ } ++ } else { ++ nibble.setNonNull(); ++ } ++ } ++ ++ @Override ++ protected final void checkBlock(final ILightAccess lightAccess, final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change emitted light ++ // blocks can change direction of propagation ++ ++ final int encodeOffset = this.coordinateOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ final VariableBlockLightHandler customBlockHandler = ((WorldServer)lightAccess.getWorld()).customBlockLightHandlers; ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ final IBlockData blockState = this.getBlockState(worldX, worldY, worldZ); ++ final int emittedLevel = (customBlockHandler != null ? this.getCustomLightLevel(customBlockHandler, worldX, worldY, worldZ, blockState.getEmittedLight()) : blockState.getEmittedLight()) & emittedMask; ++ ++ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); ++ // this accounts for change in emitted light that would cause an increase ++ if (emittedLevel != 0) { ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (emittedLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) ++ ); ++ } ++ // this also accounts for a change in emitted light that would cause a decrease ++ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) ++ // as it checks all neighbours (even if current level is 0) ++ this.appendToDecreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ // always keep sided transparent false here, new block might be conditionally transparent which would ++ // prevent us from decreasing sources in the directions where the new block is opaque ++ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always ++ // catch that and fix it. ++ ); ++ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block ++ } ++ ++ protected final BlockPosition.MutableBlockPosition recalcCenterPos = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition recalcNeighbourPos = new BlockPosition.MutableBlockPosition(); ++ ++ @Override ++ protected int calculateLightValue(final ILightAccess lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect, final VariableBlockLightHandler customBlockLight) { ++ final IBlockData centerState = this.getBlockState(worldX, worldY, worldZ); ++ int level = centerState.getEmittedLight() & 0xFF; ++ if (customBlockLight != null) { ++ level = this.getCustomLightLevel(customBlockLight, worldX, worldY, worldZ, level); ++ } ++ ++ if (level >= (15 - 1) || level > expect) { ++ return level; ++ } ++ ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final IBlockData conditionallyOpaqueState; ++ int opacity = centerState.getOpacityIfCached(); ++ ++ if (opacity == -1) { ++ this.recalcCenterPos.setValues(worldX, worldY, worldZ); ++ opacity = centerState.getOpacity(lightAccess.getWorld(), this.recalcCenterPos); ++ if (centerState.isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ } else if (opacity >= 15) { ++ return level; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ opacity = Math.max(1, opacity); ++ ++ for (final AxisDirection direction : AXIS_DIRECTIONS) { ++ final int offX = worldX + direction.x; ++ final int offY = worldY + direction.y; ++ final int offZ = worldZ + direction.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ ++ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); ++ ++ if ((neighbourLevel - 1) <= level) { ++ // don't need to test transparency, we know it wont affect the result. ++ continue; ++ } ++ ++ final IBlockData neighbourState = this.getBlockState(offX, offY ,offZ); ++ ++ if (neighbourState.isConditionallyFullOpaque()) { ++ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that ++ // we don't read the blockstate because most of the time this is false, so using the faster ++ // known transparency lookup results in a net win ++ this.recalcNeighbourPos.setValues(offX, offY, offZ); ++ final VoxelShape neighbourFace = neighbourState.getCullingFace(lightAccess.getWorld(), this.recalcNeighbourPos, direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? VoxelShapes.empty() : conditionallyOpaqueState.getCullingFace(lightAccess.getWorld(), this.recalcCenterPos, direction.nms); ++ if (VoxelShapes.combinationOccludes(thisFace, neighbourFace)) { ++ // not allowed to propagate ++ continue; ++ } ++ } ++ ++ // passed transparency, ++ ++ final int calculated = neighbourLevel - opacity; ++ level = Math.max(calculated, level); ++ if (level > expect) { ++ return level; ++ } ++ } ++ ++ return level; ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk, final Set positions) { ++ for (final BlockPosition pos : positions) { ++ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected Iterator getSources(final ILightAccess lightAccess, final IChunkAccess chunk) { ++ if (chunk instanceof ProtoChunkExtension || chunk instanceof Chunk) { ++ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is ++ // skipping empty sections, and the far more optimised reading of types. ++ List sources = new ArrayList<>(); ++ ++ int offX = chunk.getPos().x << 4; ++ int offZ = chunk.getPos().z << 4; ++ ++ final ChunkSection[] sections = chunk.getSections(); ++ for (int sectionY = 0; sectionY <= 15; ++sectionY) { ++ if (sections[sectionY] == null || sections[sectionY].isFullOfAir()) { ++ // no sources in empty sections ++ continue; ++ } ++ final DataPaletteBlock section = sections[sectionY].blockIds; ++ final int offY = sectionY << 4; ++ ++ for (int index = 0; index < (16 * 16 * 16); ++index) { ++ final IBlockData state = section.rawGet(index); ++ if (state.getEmittedLight() <= 0) { ++ continue; ++ } ++ ++ // index = x | (z << 4) | (y << 8) ++ sources.add(new BlockPosition(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); ++ } ++ } ++ ++ final VariableBlockLightHandler customBlockHandler = ((WorldServer)lightAccess.getWorld()).customBlockLightHandlers; ++ if (customBlockHandler == null) { ++ return sources.iterator(); ++ } ++ ++ final Set ret = new HashSet<>(sources); ++ ret.addAll(customBlockHandler.getCustomLightPositions(chunk.getPos().x, chunk.getPos().z)); ++ ++ return ret.iterator(); ++ } else { ++ return chunk.getLightSources().iterator(); ++ } ++ } ++ ++ @Override ++ public void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks) { ++ // setup sources ++ final int emittedMask = this.emittedLightMask; ++ final VariableBlockLightHandler customBlockHandler = ((WorldServer)lightAccess.getWorld()).customBlockLightHandlers; ++ for (final Iterator positions = this.getSources(lightAccess, chunk); positions.hasNext();) { ++ final BlockPosition pos = positions.next(); ++ final IBlockData blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); ++ final int emittedLight = (customBlockHandler != null ? this.getCustomLightLevel(customBlockHandler, pos.getX(), pos.getY(), pos.getZ(), blockState.getEmittedLight()) : blockState.getEmittedLight()) & emittedMask; ++ ++ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { ++ // some other source is brighter ++ continue; ++ } ++ ++ this.appendToIncreaseQueue( ++ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (emittedLight & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) ++ ); ++ ++ ++ // propagation wont set this for us ++ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); ++ } ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ // verify neighbour edges ++ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ } else { ++ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java b/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..051e2db5349b6f20887841efad7fbc183b190f68 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/SWMRNibbleArray.java +@@ -0,0 +1,325 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import net.minecraft.server.NibbleArray; ++import java.util.ArrayDeque; ++import java.util.Arrays; ++ ++// SWMR -> Single Writer Multi Reader Nibble Array ++public final class SWMRNibbleArray { ++ ++ /* ++ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null ++ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised ++ * nibbles can be written to. ++ * ++ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. ++ * ++ * Initialised nibble - Has light data. ++ */ ++ ++ protected static final int INIT_STATE_NULL = 0; // null ++ protected static final int INIT_STATE_UNINIT = 1; // uninitialised ++ protected static final int INIT_STATE_INIT = 2; // initialised ++ ++ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block ++ protected static final byte[] FULL_LIT = new byte[ARRAY_SIZE]; ++ static { ++ Arrays.fill(FULL_LIT, (byte)-1); ++ } ++ // this allows us to maintain only 1 byte array when we're not updating ++ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); ++ ++ private static byte[] allocateBytes() { ++ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); ++ if (inPool != null) { ++ return inPool; ++ } ++ ++ return new byte[ARRAY_SIZE]; ++ } ++ ++ private static void freeBytes(final byte[] bytes) { ++ WORKING_BYTES_POOL.get().addFirst(bytes); ++ } ++ ++ protected int stateUpdating; ++ protected volatile int stateVisible; ++ ++ protected byte[] storageUpdating; ++ protected boolean updatingDirty; // only returns whether storageUpdating is dirty ++ protected byte[] storageVisible; ++ ++ public SWMRNibbleArray() { ++ this(null, false); // lazy init ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes) { ++ this(bytes, false); ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { ++ if (bytes != null && bytes.length != ARRAY_SIZE) { ++ throw new IllegalArgumentException(); ++ } ++ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; ++ this.storageUpdating = this.storageVisible = bytes; ++ } ++ ++ // operation type: visible ++ public boolean isAllZero() { ++ final int state = this.stateVisible; ++ ++ if (state == INIT_STATE_NULL) { ++ return false; ++ } else if (state == INIT_STATE_UNINIT) { ++ return true; ++ } ++ ++ synchronized (this) { ++ final byte[] bytes = this.storageVisible; ++ ++ if (bytes == null) { ++ return this.stateVisible == INIT_STATE_UNINIT; ++ } ++ ++ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { ++ byte whole = bytes[i << 4]; ++ ++ for (int k = 1; k < (1 << 4); ++k) { ++ whole |= bytes[(i << 4) | k]; ++ } ++ ++ if (whole != 0) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ // operation type: updating on src, updating on other ++ public void extrudeLower(final SWMRNibbleArray other) { ++ if (other.stateUpdating == INIT_STATE_NULL) { ++ throw new IllegalArgumentException(); ++ } ++ ++ if (other.storageUpdating == null) { ++ this.setUninitialised(); ++ return; ++ } ++ ++ final byte[] src = other.storageUpdating; ++ final byte[] into; ++ ++ if (this.storageUpdating != null) { ++ into = this.storageUpdating; ++ } else { ++ this.storageUpdating = into = allocateBytes(); ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ this.updatingDirty = true; ++ ++ final int start = 0; ++ final int end = (15 | (15 << 4)) >>> 1; ++ ++ /* x | (z << 4) | (y << 8) */ ++ for (int y = 0; y <= 15; ++y) { ++ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); ++ } ++ } ++ ++ // operation type: updating ++ public void setFull() { ++ this.stateUpdating = INIT_STATE_INIT; ++ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public void setZero() { ++ this.stateUpdating = INIT_STATE_INIT; ++ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public void setNonNull() { ++ if (this.stateUpdating != INIT_STATE_NULL) { ++ return; ++ } ++ this.stateUpdating = INIT_STATE_UNINIT; ++ } ++ ++ // operation type: updating ++ public void setNull() { ++ this.stateUpdating = INIT_STATE_NULL; ++ if (this.updatingDirty && this.storageUpdating != null) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = null; ++ this.updatingDirty = false; ++ } ++ ++ // operation type: updating ++ public void setUninitialised() { ++ this.stateUpdating = INIT_STATE_UNINIT; ++ if (this.storageUpdating != null && this.updatingDirty) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = null; ++ this.updatingDirty = false; ++ } ++ ++ // operation type: updating ++ public boolean isDirty() { ++ return this.stateUpdating != this.stateVisible || this.updatingDirty; ++ } ++ ++ // operation type: updating ++ public boolean isNullNibbleUpdating() { ++ return this.stateUpdating == INIT_STATE_NULL; ++ } ++ ++ // operation type: visible ++ public boolean isNullNibbleVisible() { ++ return this.stateVisible == INIT_STATE_NULL; ++ } ++ ++ // opeartion type: updating ++ public boolean isUninitialisedUpdating() { ++ return this.stateUpdating == INIT_STATE_UNINIT; ++ } ++ ++ // operation type: visible ++ public boolean isUninitialisedVisible() { ++ return this.stateVisible == INIT_STATE_UNINIT; ++ } ++ ++ // operation type: updating ++ public boolean isInitialisedUpdating() { ++ return this.stateUpdating == INIT_STATE_INIT; ++ } ++ ++ // operation type: visible ++ public boolean isInitialisedVisible() { ++ return this.stateVisible == INIT_STATE_INIT; ++ } ++ ++ // operation type: updating ++ protected void swapUpdatingAndMarkDirty() { ++ if (this.updatingDirty) { ++ return; ++ } ++ ++ if (this.storageUpdating == null) { ++ this.storageUpdating = allocateBytes(); ++ Arrays.fill(this.storageUpdating, (byte)0); ++ } else { ++ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); ++ } ++ ++ this.stateUpdating = INIT_STATE_INIT; ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public boolean updateVisible() { ++ if (!this.isDirty()) { ++ return false; ++ } ++ ++ synchronized (this) { ++ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { ++ this.storageVisible = null; ++ } else { ++ if (this.storageVisible == null) { ++ this.storageVisible = this.storageUpdating.clone(); ++ } else { ++ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); ++ } ++ ++ freeBytes(this.storageUpdating); ++ this.storageUpdating = this.storageVisible; ++ } ++ this.updatingDirty = false; ++ this.stateVisible = this.stateUpdating; ++ } ++ ++ return true; ++ } ++ ++ // operation type: visible ++ public NibbleArray toVanillaNibble() { ++ synchronized (this) { ++ switch (this.stateVisible) { ++ case INIT_STATE_NULL: ++ return null; ++ case INIT_STATE_UNINIT: ++ return new NibbleArray(); ++ case INIT_STATE_INIT: ++ return new NibbleArray(this.storageVisible.clone()); ++ default: ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ /* x | (z << 4) | (y << 8) */ ++ ++ // operation type: updating ++ public int getUpdating(final int x, final int y, final int z) { ++ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ // operation type: updating ++ public int getUpdating(final int index) { ++ // indices range from 0 -> 4096 ++ final byte[] bytes = this.storageUpdating; ++ if (bytes == null) { ++ return 0; ++ } ++ final byte value = bytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ // operation type: visible ++ public int getVisible(final int x, final int y, final int z) { ++ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ // operation type: visible ++ public int getVisible(final int index) { ++ synchronized (this) { ++ // indices range from 0 -> 4096 ++ final byte[] visibleBytes = this.storageVisible; ++ if (visibleBytes == null) { ++ return 0; ++ } ++ final byte value = visibleBytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ } ++ ++ // operation type: updating ++ public void set(final int x, final int y, final int z, final int value) { ++ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); ++ } ++ ++ // operation type: updating ++ public void set(final int index, final int value) { ++ if (!this.updatingDirty) { ++ this.swapUpdatingAndMarkDirty(); ++ } ++ final int shift = (index & 1) << 2; ++ final int i = index >>> 1; ++ ++ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..26534e05ff5aac5a963906ba678411c843b132e3 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/SkyStarLightEngine.java +@@ -0,0 +1,706 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import com.tuinity.tuinity.util.WorldUtil; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortIterator; ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.ChunkSection; ++import net.minecraft.server.ChunkStatus; ++import net.minecraft.server.IBlockAccess; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.VoxelShape; ++import net.minecraft.server.VoxelShapes; ++import net.minecraft.server.World; ++import java.util.Arrays; ++import java.util.Set; ++ ++public final class SkyStarLightEngine extends StarLightEngine { ++ ++ /* ++ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: ++ ++ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. ++ ++ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. ++ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees ++ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise ++ our own) - we need a radius of 2 to de-initialise neighbour nibbles. ++ How do we solve this? ++ ++ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. ++ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the ++ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last ++ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data ++ to see if any of its nibbles need to be de-initialised. ++ ++ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, ++ and if it doesn't have data then we know it will correctly de-initialise once it fills up. ++ ++ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking ++ around those. ++ */ ++ ++ protected final int[] heightMapBlockChange = new int[16 * 16]; ++ { ++ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap ++ } ++ ++ protected final boolean[] nullPropagationCheckCache; ++ ++ public SkyStarLightEngine(final World world) { ++ super(true, world); ++ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; ++ } ++ ++ @Override ++ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { ++ return; ++ } ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble == null) { ++ if (!initRemovedNibbles) { ++ throw new IllegalStateException(); ++ } else { ++ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); ++ } ++ } ++ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); ++ } ++ ++ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { ++ if (!currNibble.isNullNibbleUpdating()) { ++ // already initialised ++ return; ++ } ++ ++ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); ++ ++ // are we above this chunk's lowest empty section? ++ int lowestY = this.minLightSection - 1; ++ for (int currY = this.maxSection; currY >= this.minSection; --currY) { ++ if (emptinessMap == null) { ++ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. ++ final ChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); ++ if (current == null || current == EMPTY_CHUNK_SECTION) { ++ continue; ++ } ++ } else { ++ if (emptinessMap[currY - this.minSection]) { ++ continue; ++ } ++ } ++ ++ // should always be full lit here ++ lowestY = currY; ++ break; ++ } ++ ++ if (chunkY > lowestY) { ++ // we need to set this one to full ++ this.getNibbleFromCache(chunkX, chunkY, chunkZ).setFull(); ++ return; ++ } ++ ++ if (extrude) { ++ // this nibble is going to depend solely on the skylight data above it ++ // find first non-null data above (there does exist one, as we just found it above) ++ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); ++ if (nibble != null && !nibble.isNullNibbleUpdating()) { ++ currNibble.extrudeLower(nibble); ++ break; ++ } ++ } ++ } else { ++ currNibble.setNonNull(); ++ } ++ } ++ ++ protected final void rewriteNibbleCacheForSkylight(final IChunkAccess chunk) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (nibble != null && nibble.isNullNibbleUpdating()) { ++ // stop propagation in these areas ++ this.nibbleCache[index] = null; ++ nibble.updateVisible(); ++ } ++ } ++ } ++ ++ // rets whether neighbours were init'd ++ ++ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, ++ final boolean extrudeInitialised) { ++ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are ++ // non-null. Propagation to these neighbours is necessary. ++ // What makes this easy is we know none of these neighbours are non-empty (otherwise ++ // this nibble would be initialised). So, we don't have to initialise ++ // the neighbours in the full 1 radius, because there's no worry that any "paths" ++ // to the neighbours on this horizontal plane are blocked. ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { ++ return false; ++ } ++ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; ++ ++ // check horizontal neighbours ++ boolean needInitNeighbours = false; ++ neighbour_search: ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); ++ if (nibble != null && !nibble.isNullNibbleUpdating()) { ++ needInitNeighbours = true; ++ break neighbour_search; ++ } ++ } ++ } ++ ++ if (needInitNeighbours) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); ++ } ++ } ++ } ++ ++ return needInitNeighbours; ++ } ++ ++ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { ++ final int chunkX = worldX >> 4; ++ int chunkY = worldY >> 4; ++ final int chunkZ = worldZ >> 4; ++ ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ return nibble.getUpdating(worldX, worldY, worldZ); ++ } ++ ++ for (;;) { ++ if (++chunkY > this.maxLightSection) { ++ return 15; ++ } ++ ++ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ ++ if (nibble != null) { ++ return nibble.getUpdating(worldX, 0, worldZ); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean[] getEmptinessMap(final IChunkAccess chunk) { ++ return chunk.getSkyEmptinessMap(); ++ } ++ ++ @Override ++ protected void setEmptinessMap(final IChunkAccess chunk, final boolean[] to) { ++ chunk.setSkyEmptinessMap(to); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk) { ++ return chunk.getSkyNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setSkyNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final IChunkAccess chunk) { ++ // can only use chunks for sky stuff if their sections have been init'd ++ return chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT) && (this.isClientSide ? true : chunk.isLit()); ++ } ++ ++ @Override ++ protected void checkChunkEdges(final ILightAccess lightAccess, final IChunkAccess chunk, final int fromSection, ++ final int toSection) { ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ this.rewriteNibbleCacheForSkylight(chunk); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ for (int y = toSection; y >= fromSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, true); ++ } ++ ++ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); ++ } ++ ++ @Override ++ protected void checkChunkEdges(final ILightAccess lightAccess, final IChunkAccess chunk, final ShortCollection sections) { ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ this.rewriteNibbleCacheForSkylight(chunk); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { ++ final int y = (int)iterator.nextShort(); ++ this.checkNullSection(chunkX, y, chunkZ, true); ++ } ++ ++ super.checkChunkEdges(lightAccess, chunk, sections); ++ } ++ ++ ++ protected final BlockPosition.MutableBlockPosition recalcCenterPos = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition recalcNeighbourPos = new BlockPosition.MutableBlockPosition(); ++ ++ @Override ++ protected int calculateLightValue(final ILightAccess lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect, final VariableBlockLightHandler customBlockLight) { ++ if (expect == 15) { ++ return expect; ++ } ++ ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final IBlockData centerState = this.getBlockState(worldX, worldY, worldZ); ++ int opacity = centerState.getOpacityIfCached(); ++ ++ ++ final IBlockData conditionallyOpaqueState; ++ if (opacity < 0) { ++ this.recalcCenterPos.setValues(worldX, worldY, worldZ); ++ opacity = Math.max(1, centerState.getOpacity(lightAccess.getWorld(), this.recalcCenterPos)); ++ if (centerState.isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ } else { ++ conditionallyOpaqueState = null; ++ opacity = Math.max(1, opacity); ++ } ++ ++ int level = 0; ++ ++ for (final AxisDirection direction : AXIS_DIRECTIONS) { ++ final int offX = worldX + direction.x; ++ final int offY = worldY + direction.y; ++ final int offZ = worldZ + direction.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ ++ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); ++ ++ if ((neighbourLevel - 1) <= level) { ++ // don't need to test transparency, we know it wont affect the result. ++ continue; ++ } ++ ++ final IBlockData neighbourState = this.getBlockState(offX, offY ,offZ); ++ ++ if (neighbourState.isConditionallyFullOpaque()) { ++ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that ++ // we don't read the blockstate because most of the time this is false, so using the faster ++ // known transparency lookup results in a net win ++ this.recalcNeighbourPos.setValues(offX, offY, offZ); ++ final VoxelShape neighbourFace = neighbourState.getCullingFace(lightAccess.getWorld(), this.recalcNeighbourPos, direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? VoxelShapes.empty() : conditionallyOpaqueState.getCullingFace(lightAccess.getWorld(), this.recalcCenterPos, direction.nms); ++ if (VoxelShapes.combinationOccludes(thisFace, neighbourFace)) { ++ // not allowed to propagate ++ continue; ++ } ++ } ++ ++ // passed transparency, ++ ++ final int calculated = neighbourLevel - opacity; ++ level = Math.max(calculated, level); ++ if (level > expect) { ++ return level; ++ } ++ } ++ ++ return level; ++ } ++ ++ @Override ++ protected void checkBlock(final ILightAccess lightAccess, final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change direction of propagation ++ ++ // same logic applies from BlockStarLightEngine#checkBlock ++ ++ final int encodeOffset = this.coordinateOffset; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ ++ if (currentLevel == 15) { ++ // must re-propagate clobbered source ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent ++ ); ++ } else { ++ this.setLightLevel(worldX, worldY, worldZ, 0); ++ } ++ ++ this.appendToDecreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ ); ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk, final Set positions) { ++ this.rewriteNibbleCacheForSkylight(atChunk); ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ ++ final IBlockAccess world = lightAccess.getWorld(); ++ final int chunkX = atChunk.getPos().x; ++ final int chunkZ = atChunk.getPos().z; ++ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); ++ ++ // setup heightmap for changes ++ for (final BlockPosition pos : positions) { ++ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; ++ final int curr = this.heightMapBlockChange[index]; ++ if (pos.getY() > curr) { ++ this.heightMapBlockChange[index] = pos.getY(); ++ } ++ } ++ ++ // note: light sets are delayed while processing skylight source changes due to how ++ // nibbles are initialised, as we want to avoid clobbering nibble values so what when ++ // below nibbles are initialised they aren't reading from partially modified nibbles ++ ++ // now we can recalculate the sources for the changed columns ++ for (int index = 0; index < (16 * 16); ++index) { ++ final int maxY = this.heightMapBlockChange[index]; ++ if (maxY == Integer.MIN_VALUE) { ++ // not changed ++ continue; ++ } ++ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller ++ ++ final int columnX = (index & 15) | (chunkX << 4); ++ final int columnZ = (index >>> 4) | (chunkZ << 4); ++ ++ // try and propagate from the above y ++ // delay light set until after processing all sources to setup ++ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); ++ ++ // maxPropagationY is now the highest block that could not be propagated to ++ ++ // remove all sources below that are 15 ++ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; ++ final int encodeOffset = this.coordinateOffset; ++ ++ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { ++ // ensure section is checked ++ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); ++ ++ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { ++ if ((currY & 15) == 15) { ++ // ensure section is checked ++ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); ++ } ++ ++ // ensure section below is always checked ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); ++ if (nibble == null) { ++ // advance currY to the the top of the section below ++ currY = (currY) & (~15); ++ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually ++ // end up there ++ continue; ++ } ++ ++ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { ++ break; ++ } ++ ++ // delay light set until after processing all sources to setup ++ this.appendToDecreaseQueue( ++ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ // do not set transparent blocks for the same reason we don't in the checkBlock method ++ ); ++ } ++ } ++ } ++ ++ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads ++ // immediate light value ++ this.processDelayedIncreases(); ++ this.processDelayedDecreases(); ++ ++ for (final BlockPosition pos : positions) { ++ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ @Override ++ protected void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks) { ++ this.rewriteNibbleCacheForSkylight(chunk); ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ ++ final IBlockAccess world = lightAccess.getWorld(); ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ final ChunkSection[] sections = chunk.getSections(); ++ ++ int highestNonEmptySection = this.maxSection; ++ while (highestNonEmptySection == (this.minSection - 1) || ++ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].isFullOfAir()) { ++ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); ++ // try propagate FULL to neighbours ++ ++ // check neighbours to see if we need to propagate into them ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourX = chunkX + direction.x; ++ final int neighbourZ = chunkZ + direction.z; ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); ++ if (neighbourNibble == null) { ++ // unloaded neighbour ++ // most of the time we fall here ++ continue; ++ } ++ ++ // it looks like we need to propagate into the neighbour ++ ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (direction.x != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (direction.z < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final int encodeOffset = this.coordinateOffset; ++ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction ++ ++ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ this.appendToIncreaseQueue( ++ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) ++ ); ++ } ++ } ++ } ++ ++ if (highestNonEmptySection-- == (this.minSection - 1)) { ++ break; ++ } ++ } ++ ++ if (highestNonEmptySection >= this.minSection) { ++ // fill out our other sources ++ final int minX = chunkPos.x << 4; ++ final int maxX = chunkPos.x << 4 | 15; ++ final int minZ = chunkPos.z << 4; ++ final int maxZ = chunkPos.z << 4 | 15; ++ final int startY = highestNonEmptySection << 4 | 15; ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); ++ } ++ } ++ } // else: apparently the chunk is empty ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, false); ++ } ++ // no need to rewrite the nibble cache again ++ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); ++ } else { ++ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, false); ++ } ++ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++ ++ protected final void processDelayedIncreases() { ++ // copied from performLightIncrease ++ final long[] queue = this.increaseQueue; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ ++ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { ++ final long queueValue = queue[i]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); ++ ++ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); ++ } ++ } ++ ++ protected final void processDelayedDecreases() { ++ // copied from performLightDecrease ++ final long[] queue = this.decreaseQueue; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ ++ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { ++ final long queueValue = queue[i]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ ++ this.setLightLevel(posX, posY, posZ, 0); ++ } ++ } ++ ++ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays ++ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so ++ // clobbering the light values will result in broken propagation) ++ protected final int tryPropagateSkylight(final IBlockAccess world, final int worldX, int startY, final int worldZ, ++ final boolean extrudeInitialised, final boolean delayLightSet) { ++ final BlockPosition.MutableBlockPosition mutablePos = this.mutablePos3; ++ final int encodeOffset = this.coordinateOffset; ++ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. ++ ++ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { ++ return startY; ++ } ++ ++ // ensure this section is always checked ++ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); ++ ++ IBlockData above = this.getBlockState(worldX, startY + 1, worldZ); ++ if (above == null) { ++ above = AIR_BLOCK_STATE; ++ } ++ ++ for (;startY >= (this.minLightSection << 4); --startY) { ++ if ((startY & 15) == 15) { ++ // ensure this section is always checked ++ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); ++ } ++ IBlockData current = this.getBlockState(worldX, startY, worldZ); ++ if (current == null) { ++ current = AIR_BLOCK_STATE; ++ } ++ ++ final VoxelShape fromShape; ++ if (above.isConditionallyFullOpaque()) { ++ this.mutablePos2.setValues(worldX, startY + 1, worldZ); ++ fromShape = above.getCullingFace(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); ++ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) { ++ // above wont let us propagate ++ break; ++ } ++ } else { ++ fromShape = VoxelShapes.getEmptyShape(); ++ } ++ ++ final int opacityIfCached = current.getOpacityIfCached(); ++ // does light propagate from the top down? ++ if (opacityIfCached != -1) { ++ if (opacityIfCached != 0) { ++ // we cannot propagate 15 through this ++ break; ++ } ++ // most of the time it falls here. ++ // add to propagate ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ ); ++ } else { ++ mutablePos.setValues(worldX, startY, worldZ); ++ long flags = 0L; ++ if (current.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = current.getCullingFace(world, mutablePos, AxisDirection.POSITIVE_Y.nms); ++ ++ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) { ++ // can't propagate here, we're done on this column. ++ break; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = current.getOpacity(world, mutablePos); ++ if (opacity > 0) { ++ // let the queued value (if any) handle it from here. ++ break; ++ } ++ ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | flags ++ ); ++ } ++ ++ above = current; ++ ++ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { ++ // we skip empty sections here, as this is just an easy way of making sure the above block ++ // can propagate through air. ++ ++ // nothing can propagate in null sections, remove the queue entry for it ++ --this.increaseQueueInitialLength; ++ ++ // advance currY to the the top of the section below ++ startY = (startY) & (~15); ++ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually ++ // end up there ++ ++ // make sure this is marked as AIR ++ above = AIR_BLOCK_STATE; ++ } else if (!delayLightSet) { ++ this.setLightLevel(worldX, startY, worldZ, 15); ++ } ++ } ++ ++ return startY; ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d1aff9117e74d8cd7c3ca9334edfaf174a240956 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightEngine.java +@@ -0,0 +1,1610 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import com.tuinity.tuinity.util.CoordinateUtils; ++import com.tuinity.tuinity.util.IntegerUtil; ++import com.tuinity.tuinity.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortIterator; ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.Blocks; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.ChunkSection; ++import net.minecraft.server.EnumDirection; ++import net.minecraft.server.EnumSkyBlock; ++import net.minecraft.server.IBlockAccess; ++import net.minecraft.server.IBlockData; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.SectionPosition; ++import net.minecraft.server.VoxelShape; ++import net.minecraft.server.VoxelShapes; ++import net.minecraft.server.World; ++import net.minecraft.server.WorldServer; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.Set; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; ++ ++public abstract class StarLightEngine { ++ ++ protected static final IBlockData AIR_BLOCK_STATE = Blocks.AIR.getBlockData(); ++ ++ protected static final ChunkSection EMPTY_CHUNK_SECTION = new ChunkSection(0); ++ ++ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); ++ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; ++ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { ++ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, ++ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z ++ }; ++ ++ protected static enum AxisDirection { ++ ++ // Declaration order is important and relied upon. Do not change without modifying propagation code. ++ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), ++ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), ++ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); ++ ++ static { ++ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; ++ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; ++ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; ++ } ++ ++ protected AxisDirection opposite; ++ ++ public final int x; ++ public final int y; ++ public final int z; ++ public final EnumDirection nms; ++ public final long everythingButThisDirection; ++ public final long everythingButTheOppositeDirection; ++ ++ AxisDirection(final int x, final int y, final int z) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ this.nms = EnumDirection.from(x, y, z); ++ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); ++ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. ++ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); ++ } ++ ++ public AxisDirection getOpposite() { ++ return this.opposite; ++ } ++ } ++ ++ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 ++ // for explaining how light propagates via breadth-first search ++ ++ // While the above is a good start to understanding the general idea of what the general principles are, it's not ++ // exactly how the vanilla light engine should behave for minecraft. ++ ++ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ // null index indicates the chunk section doesn't exist (empty or out of bounds) ++ protected final ChunkSection[] sectionCache; ++ ++ // the exact same as above, except for storing fast access to SWMRNibbleArray ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ protected final SWMRNibbleArray[] nibbleCache; ++ ++ // the exact same as above, except for storing fast access to nibbles to call change callbacks for ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ protected final boolean[] notifyUpdateCache; ++ ++ // always initialsed during start of lighting. no index is null. ++ // index = x + (z * 5) ++ protected final IChunkAccess[] chunkCache = new IChunkAccess[5 * 5]; ++ ++ // index = x + (z * 5) ++ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; ++ ++ protected final BlockPosition.MutableBlockPosition mutablePos1 = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition mutablePos2 = new BlockPosition.MutableBlockPosition(); ++ protected final BlockPosition.MutableBlockPosition mutablePos3 = new BlockPosition.MutableBlockPosition(); ++ ++ protected int encodeOffsetX; ++ protected int encodeOffsetY; ++ protected int encodeOffsetZ; ++ ++ protected int coordinateOffset; ++ ++ protected int chunkOffsetX; ++ protected int chunkOffsetY; ++ protected int chunkOffsetZ; ++ ++ protected int chunkIndexOffset; ++ protected int chunkSectionIndexOffset; ++ ++ protected final boolean skylightPropagator; ++ protected final int emittedLightMask; ++ protected static final boolean isClientSide = false; ++ ++ protected final World world; ++ protected final int minLightSection; ++ protected final int maxLightSection; ++ protected final int minSection; ++ protected final int maxSection; ++ ++ protected StarLightEngine(final boolean skylightPropagator, final World world) { ++ this.skylightPropagator = skylightPropagator; ++ this.emittedLightMask = skylightPropagator ? 0 : 0xF; ++ //this.isClientSide = isClientSide; ++ this.world = world; ++ this.minLightSection = WorldUtil.getMinLightSection(world); ++ this.maxLightSection = WorldUtil.getMaxLightSection(world); ++ this.minSection = WorldUtil.getMinSection(world); ++ this.maxSection = WorldUtil.getMaxSection(world); ++ ++ this.sectionCache = new ChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ } ++ ++ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { ++ // 31 = center + encodeOffset ++ this.encodeOffsetX = 31 - centerX; ++ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value ++ this.encodeOffsetZ = 31 - centerZ; ++ ++ // coordinateIndex = x | (z << 6) | (y << 12) ++ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); ++ ++ // 2 = (centerX >> 4) + chunkOffset ++ this.chunkOffsetX = 2 - (centerX >> 4); ++ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 ++ this.chunkOffsetZ = 2 - (centerZ >> 4); ++ ++ // chunk index = x + (5 * z) ++ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); ++ ++ // chunk section index = x + (5 * z) + ((5*5) * y) ++ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); ++ } ++ ++ protected final void setupCaches(final ILightAccess chunkProvider, final int centerX, final int centerY, final int centerZ, ++ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { ++ final int centerChunkX = centerX >> 4; ++ final int centerChunkY = centerY >> 4; ++ final int centerChunkZ = centerZ >> 4; ++ ++ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); ++ ++ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; ++ ++ for (int dz = -radius; dz <= radius; ++dz) { ++ for (int dx = -radius; dx <= radius; ++dx) { ++ final int cx = centerChunkX + dx; ++ final int cz = centerChunkZ + dz; ++ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; ++ final IChunkAccess chunk = (IChunkAccess)chunkProvider.getFeaturesReadyChunk(cx, cz); // mappings are awful here, this is the "get chunk at if at least features" ++ ++ if (chunk == null) { ++ if (relaxed | isTwoRadius) { ++ continue; ++ } ++ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); ++ } ++ ++ if (!this.canUseChunk(chunk)) { ++ continue; ++ } ++ ++ this.setChunkInCache(cx, cz, chunk); ++ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); ++ if (!isTwoRadius) { ++ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); ++ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); ++ } ++ } ++ } ++ } ++ ++ protected final IChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { ++ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setChunkInCache(final int chunkX, final int chunkZ, final IChunkAccess chunk) { ++ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; ++ } ++ ++ protected final ChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final ChunkSection section) { ++ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; ++ } ++ ++ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final ChunkSection[] sections) { ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ this.setChunkSectionInCache(chunkX, cy, chunkZ, ++ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? (sections[cy - this.minSection] == null || sections[cy - this.minSection].isFullOfAir() ? EMPTY_CHUNK_SECTION : sections[cy - this.minSection]) : EMPTY_CHUNK_SECTION)); ++ } ++ } ++ ++ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { ++ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; ++ ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; ++ } ++ ++ return ret; ++ } ++ ++ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { ++ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; ++ } ++ ++ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); ++ } ++ } ++ ++ protected final void updateVisible(final ILightAccess lightAccess) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { ++ continue; ++ } ++ ++ final int chunkX = (index % 5) - this.chunkOffsetX; ++ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; ++ final int chunkY = ((index / (5*5)) % (16 + 2 + 2)) - this.chunkOffsetY; ++ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { ++ lightAccess.markLightSectionDirty(this.skylightPropagator ? EnumSkyBlock.SKY : EnumSkyBlock.BLOCK, new SectionPosition(chunkX, chunkY, chunkZ)); ++ } ++ } ++ } ++ ++ protected final void destroyCaches() { ++ Arrays.fill(this.sectionCache, null); ++ Arrays.fill(this.nibbleCache, null); ++ Arrays.fill(this.chunkCache, null); ++ Arrays.fill(this.emptinessMapCache, null); ++ if (this.isClientSide) { ++ Arrays.fill(this.notifyUpdateCache, false); ++ } ++ } ++ ++ protected final IBlockData getBlockState(final int worldX, final int worldY, final int worldZ) { ++ final ChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ if (section != null) { ++ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_STATE : section.getType(worldX & 15, worldY & 15, worldZ & 15); ++ } ++ ++ return null; ++ } ++ ++ protected final IBlockData getBlockState(final int sectionIndex, final int localIndex) { ++ final ChunkSection section = this.sectionCache[sectionIndex]; ++ ++ if (section != null) { ++ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_STATE : section.blockIds.rawGet(localIndex); ++ } ++ ++ return null; ++ } ++ ++ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { ++ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); ++ } ++ ++ protected final int getLightLevel(final int sectionIndex, final int localIndex) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ return nibble == null ? 0 : nibble.getUpdating(localIndex); ++ } ++ ++ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { ++ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set(localIndex, level); ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { ++ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { ++ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; ++ } ++ ++ protected final int getCustomLightLevel(final VariableBlockLightHandler customBlockHandler, final int worldX, final int worldY, ++ final int worldZ, final int dfl) { ++ final int ret = customBlockHandler.getLightLevel(worldX, worldY, worldZ); ++ return ret == -1 ? dfl : ret; ++ } ++ ++ // :( ++ ++ protected final long getKnownTransparency(final int worldX, final int worldY, final int worldZ) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ // warn: localIndex = y | (x << 4) | (z << 8) ++ protected final long getKnownTransparency(final int sectionIndex, final int localIndex) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ /** ++ * @deprecated To be removed in 1.17 due to variable section count ++ */ ++ @Deprecated ++ public static SWMRNibbleArray[] getFilledEmptyLight() { ++ return getFilledEmptyLight(16 - (-1) + 1); ++ } ++ ++ public static SWMRNibbleArray[] getFilledEmptyLight(final World world) { ++ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); ++ } ++ ++ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { ++ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; ++ ++ for (int i = 0, len = ret.length; i < len; ++i) { ++ ret[i] = new SWMRNibbleArray(null, true); ++ } ++ ++ return ret; ++ } ++ ++ protected abstract boolean[] getEmptinessMap(final IChunkAccess chunk); ++ ++ protected abstract void setEmptinessMap(final IChunkAccess chunk, final boolean[] to); ++ ++ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final IChunkAccess chunk); ++ ++ protected abstract void setNibbles(final IChunkAccess chunk, final SWMRNibbleArray[] to); ++ ++ protected abstract boolean canUseChunk(final IChunkAccess chunk); ++ ++ public final void blocksChangedInChunk(final ILightAccess lightAccess, final int chunkX, final int chunkZ, ++ final Set positions, final Boolean[] changedSections) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ final IChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (this.isClientSide && chunk == null) { ++ return; ++ } ++ if (changedSections != null) { ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ } ++ if (!positions.isEmpty()) { ++ this.propagateBlockChanges(lightAccess, chunk, positions); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ protected abstract void propagateBlockChanges(final ILightAccess lightAccess, final IChunkAccess atChunk, final Set positions); ++ ++ protected abstract void checkBlock(final ILightAccess lightAccess, final int worldX, final int worldY, final int worldZ); ++ ++ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) ++ // if ret == expect, then expect is the correct light value for pos ++ // if ret < expect, then ret is the real light value ++ protected abstract int calculateLightValue(final ILightAccess lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect, final VariableBlockLightHandler customBlockLight); ++ ++ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; ++ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; ++ ++ protected void checkChunkEdge(final ILightAccess lightAccess, final IChunkAccess chunk, ++ final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (currNibble == null) { ++ return; ++ } ++ ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ chunkY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null) { ++ continue; ++ } ++ ++ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { ++ // both are zero, nothing to check. ++ continue; ++ } ++ ++ // this chunk ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final VariableBlockLightHandler customLightHandler = ((WorldServer)lightAccess.getWorld()).customBlockLightHandlers; ++ int centerDelayedChecks = 0; ++ int neighbourDelayedChecks = 0; ++ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int neighbourX = currX + neighbourOffX; ++ final int neighbourZ = currZ + neighbourOffZ; ++ ++ final int currentIndex = (currX & 15) | ++ ((currZ & 15)) << 4 | ++ ((currY & 15) << 8); ++ final int currentLevel = currNibble.getUpdating(currentIndex); ++ ++ final int neighbourIndex = ++ (neighbourX & 15) | ++ ((neighbourZ & 15)) << 4 | ++ ((currY & 15) << 8); ++ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); ++ ++ // the checks are delayed because the checkBlock method clobbers light values - which then ++ // affect later calculate light value operations. While they don't affect it in a behaviourly significant ++ // way, they do have a negative performance impact due to simply queueing more values ++ ++ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel, customLightHandler) != currentLevel) { ++ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; ++ } ++ ++ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel, customLightHandler) != neighbourLevel) { ++ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; ++ } ++ } ++ } ++ ++ final int currentChunkOffX = chunkX << 4; ++ final int currentChunkOffZ = chunkZ << 4; ++ final int neighbourChunkOffX = (chunkX + direction.x) << 4; ++ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; ++ final int chunkOffY = chunkY << 4; ++ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { ++ // try to queue neighbouring data together ++ // index = x | (z << 4) | (y << 8) ++ if (i < centerDelayedChecks) { ++ final int value = this.chunkCheckDelayedUpdatesCenter[i]; ++ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), ++ chunkOffY | (value >>> 8), ++ currentChunkOffZ | ((value >>> 4) & 0xF)); ++ } ++ if (i < neighbourDelayedChecks) { ++ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; ++ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), ++ chunkOffY | (value >>> 8), ++ neighbourChunkOffZ | ((value >>> 4) & 0xF)); ++ } ++ } ++ } ++ } ++ ++ protected void checkChunkEdges(final ILightAccess lightAccess, final IChunkAccess chunk, final ShortCollection sections) { ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { ++ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours ++ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). ++ // This does not resolve skylight source problems. ++ protected void checkChunkEdges(final ILightAccess lightAccess, final IChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. ++ protected final void propagateNeighbourLevels(final ILightAccess lightAccess, final IChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkCoordIntPair chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); ++ if (currNibble == null) { ++ continue; ++ } ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ currSectionY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { ++ // can't pull from 0 ++ continue; ++ } ++ ++ // neighbour chunk ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = (chunkX << 4) - 1; ++ } else { ++ startX = (chunkX << 4) + 16; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = (chunkZ << 4) - 1; ++ } else { ++ startZ = (chunkZ << 4) + 16; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk ++ final int encodeOffset = this.coordinateOffset; ++ ++ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int level = neighbourNibble.getUpdating( ++ (currX & 15) ++ | ((currZ & 15) << 4) ++ | ((currY & 15) << 8) ++ ); ++ ++ if (level <= 1) { ++ // nothing to propagate ++ continue; ++ } ++ ++ this.appendToIncreaseQueue( ++ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((level & 0xFL) << (6 + 6 + 16)) ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. ++ ); ++ } ++ } ++ } ++ } ++ } ++ ++ public static Boolean[] getEmptySectionsForChunk(final IChunkAccess chunk) { ++ final ChunkSection[] sections = chunk.getSections(); ++ final Boolean[] ret = new Boolean[sections.length]; ++ ++ for (int i = 0; i < sections.length; ++i) { ++ if (sections[i] == null || sections[i].isFullOfAir()) { ++ ret[i] = Boolean.TRUE; ++ } else { ++ ret[i] = Boolean.FALSE; ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void forceHandleEmptySectionChanges(final ILightAccess lightAccess, final IChunkAccess chunk, ++ final Boolean[] emptinessChanges) { ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ // force current chunk into cache ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void handleEmptySectionChanges(final ILightAccess lightAccess, final int chunkX, final int chunkZ, ++ final Boolean[] emptinessChanges) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ if (this.isClientSide) { ++ // force current chunk into cache ++ final IChunkAccess chunk = (IChunkAccess)lightAccess.getFeaturesReadyChunk(chunkX, chunkZ); ++ if (chunk == null) { ++ // unloaded this frame (or last), and we were still queued ++ return; ++ } ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ } ++ final IChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // subclasses are guaranteed that this is always called before a changed block set ++ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks ++ // rets non-null when the emptiness map changed and needs to be updated ++ protected final boolean[] handleEmptySectionChanges(final ILightAccess lightAccess, final IChunkAccess chunk, ++ final Boolean[] emptinessChanges, final boolean unlit) { ++ final World world = (World)lightAccess.getWorld(); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ ++ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); ++ boolean[] ret = null; ++ final boolean needsInit = unlit || chunkEmptinessMap == null; ++ if (needsInit) { ++ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); ++ } ++ ++ // update emptiness map ++ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { ++ final Boolean valueBoxed = emptinessChanges[sectionIndex]; ++ if (valueBoxed == null) { ++ if (needsInit) { ++ throw new IllegalStateException("Current chunk has not initialised emptiness map yet supplied emptiness map isn't filled?"); ++ } ++ continue; ++ } ++ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); ++ } ++ ++ // now init neighbour nibbles ++ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { ++ final Boolean valueBoxed = emptinessChanges[sectionIndex]; ++ final int sectionY = sectionIndex + this.minSection; ++ if (valueBoxed == null) { ++ continue; ++ } ++ ++ final boolean empty = valueBoxed.booleanValue(); ++ ++ if (empty) { ++ continue; ++ } ++ ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ // if we're not empty, we also need to initialise nibbles ++ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up ++ final boolean extrude = (dx | dz) != 0 || !unlit; ++ for (int dy = 1; dy >= -1; --dy) { ++ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); ++ } ++ } ++ } ++ } ++ ++ // check for de-init and lazy-init ++ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running ++ // init checks. ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ // does this neighbour have 1 radius loaded? ++ boolean neighboursLoaded = true; ++ neighbour_loaded_search: ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { ++ neighboursLoaded = false; ++ break neighbour_loaded_search; ++ } ++ } ++ } ++ ++ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, sectionY, dz + chunkZ); ++ ++ // check neighbours to see if we need to de-init this one ++ boolean allEmpty = true; ++ neighbour_search: ++ for (int dy2 = -1; dy2 <= 1; ++dy2) { ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ final int y = sectionY + dy2; ++ if (y < this.minSection || y > this.maxSection) { ++ // empty ++ continue; ++ } ++ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); ++ if (emptinessMap != null) { ++ if (!emptinessMap[y - this.minSection]) { ++ allEmpty = false; ++ break neighbour_search; ++ } ++ } else { ++ final ChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); ++ if (section != null && section != EMPTY_CHUNK_SECTION) { ++ allEmpty = false; ++ break neighbour_search; ++ } ++ } ++ } ++ } ++ } ++ ++ if (allEmpty & neighboursLoaded) { ++ // can only de-init when neighbours are loaded ++ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting ++ // to be correct ++ ++ // all were empty, so de-init ++ if (nibble != null) { ++ nibble.setNull(); ++ } ++ } else if (!allEmpty) { ++ // must init ++ final boolean extrude = (dx | dz) != 0 || !unlit; ++ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void checkChunkEdges(final ILightAccess lightAccess, final int chunkX, final int chunkZ) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); ++ try { ++ final IChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void checkChunkEdges(final ILightAccess lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); ++ try { ++ final IChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ this.checkChunkEdges(lightAccess, chunk, sections); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current ++ // chunks light values with respect to neighbours ++ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function ++ // does not need to detect empty chunks itself (and it should do no handling for them either!) ++ protected abstract void lightChunk(final ILightAccess lightAccess, final IChunkAccess chunk, final boolean needsEdgeChecks); ++ ++ public final void light(final ILightAccess lightAccess, final IChunkAccess chunk, final Boolean[] emptySections) { ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ ++ try { ++ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); ++ // force current chunk into cache ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.lightChunk(lightAccess, chunk, true); // TODO ++ this.setNibbles(chunk, nibbles); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void relightChunks(final ILightAccess lightAccess, final Set chunks, ++ final Consumer chunkLightCallback, final IntConsumer onComplete) { ++ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of ++ // the region of chunks to relight ++ // it's required that tickets are added for each chunk to keep them loaded ++ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); ++ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); ++ ++ final int[] neighbourLightOrder = new int[] { ++ // d = 0 ++ 0, 0, ++ // d = 1 ++ -1, 0, ++ 0, -1, ++ 1, 0, ++ 0, 1, ++ // d = 2 ++ -1, 1, ++ 1, 1, ++ -1, -1, ++ 1, -1, ++ }; ++ ++ int lightCalls = 0; ++ ++ for (final ChunkCoordIntPair chunkPos : chunks) { ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ final IChunkAccess chunk = (IChunkAccess) lightAccess.getFeaturesReadyChunk(chunkX, chunkZ); ++ if (chunk == null || !this.canUseChunk(chunk)) { ++ throw new IllegalStateException(); ++ } ++ ++ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { ++ final int dx = neighbourLightOrder[i]; ++ final int dz = neighbourLightOrder[i + 1]; ++ final int neighbourX = dx + chunkX; ++ final int neighbourZ = dz + chunkZ; ++ ++ final IChunkAccess neighbour = (IChunkAccess) lightAccess.getFeaturesReadyChunk(neighbourX, neighbourZ); ++ if (neighbour == null || !this.canUseChunk(neighbour)) { ++ continue; ++ } ++ ++ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { ++ // lit already called for neighbour, no need to light it now ++ continue; ++ } ++ ++ // light neighbour chunk ++ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); ++ try { ++ // insert all neighbouring chunks for this neighbour that we have data for ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ final int neighbourX2 = neighbourX + dx2; ++ final int neighbourZ2 = neighbourZ + dz2; ++ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); ++ final IChunkAccess neighbour2 = (IChunkAccess)lightAccess.getFeaturesReadyChunk(neighbourX2, neighbourZ2); ++ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { ++ continue; ++ } ++ ++ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); ++ if (nibbles == null) { ++ // we haven't lit this chunk ++ continue; ++ } ++ ++ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); ++ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); ++ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); ++ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); ++ } ++ } ++ ++ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); ++ ++ // now insert the neighbour chunk and light it ++ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); ++ nibblesByChunk.put(key, nibbles); ++ ++ this.setChunkInCache(neighbourX, neighbourZ, neighbour); ++ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); ++ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); ++ ++ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); ++ emptinessMapByChunk.put(key, neighbourEmptiness); ++ if (chunks.contains(new ChunkCoordIntPair(neighbourX, neighbourZ))) { ++ this.setEmptinessMap(neighbour, neighbourEmptiness); ++ } ++ ++ this.lightChunk(lightAccess, neighbour, false); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // done lighting all neighbours, so the chunk is now fully lit ++ ++ // make sure nibbles are fully updated before calling back ++ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ for (final SWMRNibbleArray nibble : nibbles) { ++ nibble.updateVisible(); ++ } ++ ++ this.setNibbles(chunk, nibbles); ++ ++ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { ++ lightAccess.markLightSectionDirty(this.skylightPropagator ? EnumSkyBlock.SKY : EnumSkyBlock.BLOCK, new SectionPosition(chunkX, y, chunkX)); ++ } ++ ++ // now do callback ++ if (chunkLightCallback != null) { ++ chunkLightCallback.accept(chunkPos); ++ } ++ ++lightCalls; ++ } ++ ++ if (onComplete != null) { ++ onComplete.accept(lightCalls); ++ } ++ } ++ ++ // old algorithm for propagating ++ // this is also the basic algorithm, the optimised algorithm is always going to be tested against this one ++ // and this one is always tested against vanilla ++ // contains: ++ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) ++ // next 4 bits: propagated light level (0, 15] ++ // next 6 bits: propagation direction bitset ++ // next 24 bits: unused ++ // last 4 bits: state flags ++ // state flags: ++ // whether the propagation must set the current position's light value (0 if decrease, propagated light level if increase) ++ // whether the propagation needs to check if its current level is equal to the expected level ++ // used only in increase propagation ++ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; ++ // whether the propagation needs to consider if its block is conditionally transparent ++ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; ++ ++ protected long[] increaseQueue = new long[16 * 16 * 16]; ++ protected int increaseQueueInitialLength; ++ protected long[] decreaseQueue = new long[16 * 16 * 16]; ++ protected int decreaseQueueInitialLength; ++ ++ protected final long[] resizeIncreaseQueue() { ++ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); ++ } ++ ++ protected final long[] resizeDecreaseQueue() { ++ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); ++ } ++ ++ protected final void appendToIncreaseQueue(final long value) { ++ final int idx = this.increaseQueueInitialLength++; ++ long[] queue = this.increaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ queue[idx] = value; ++ } else { ++ queue[idx] = value; ++ } ++ } ++ ++ protected final void appendToDecreaseQueue(final long value) { ++ final int idx = this.decreaseQueueInitialLength++; ++ long[] queue = this.decreaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ queue[idx] = value; ++ } else { ++ queue[idx] = value; ++ } ++ } ++ ++ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; ++ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; ++ static { ++ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { ++ final List directions = new ArrayList<>(); ++ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { ++ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); ++ } ++ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); ++ } ++ } ++ ++ protected final void performLightIncrease(final ILightAccess lightAccess) { ++ final IBlockAccess world = lightAccess.getWorld(); ++ long[] queue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.increaseQueueInitialLength; ++ this.increaseQueueInitialLength = 0; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); ++ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; ++ ++ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { ++ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { ++ // not at the level we expect, so something changed. ++ continue; ++ } ++ } ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { ++ // we don't need to worry about our state here. ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int currentLevel; ++ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want or unloaded ++ } ++ ++ final IBlockData blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getOpacity(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final IBlockData fromBlock = this.getBlockState(posX, posY, posZ); ++ this.mutablePos2.setValues(posX, posY, posZ); ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.getEmptyShape(); ++ ++ if (fromShape != VoxelShapes.getEmptyShape() && VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) { ++ continue; ++ } ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int currentLevel; ++ ++ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want ++ } ++ ++ final IBlockData blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getOpacity(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void performLightDecrease(final ILightAccess lightAccess) { ++ final IBlockAccess world = lightAccess.getWorld(); ++ long[] queue = this.decreaseQueue; ++ long[] increaseQueue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.decreaseQueueInitialLength; ++ this.decreaseQueueInitialLength = 0; ++ int increaseQueueLength = this.increaseQueueInitialLength; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final int emittedMask = this.emittedLightMask; ++ final VariableBlockLightHandler customLightHandler = this.skylightPropagator ? null : ((WorldServer)world).customBlockLightHandlers; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); ++ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { ++ // we don't need to worry about our state here. ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int lightLevel; ++ ++ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { ++ // already at lowest (or unloaded), nothing we can do ++ continue; ++ } ++ ++ final IBlockData blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = (customLightHandler != null ? this.getCustomLightLevel(customLightHandler, offX, offY, offZ, blockState.getEmittedLight()) : blockState.getEmittedLight()) & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0L); ++ } ++ ++ currentNibble.set(localIndex, emittedLight); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getOpacity(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = (customLightHandler != null ? this.getCustomLightLevel(customLightHandler, offX, offY, offZ, blockState.getEmittedLight()) : blockState.getEmittedLight()) & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ ++ currentNibble.set(localIndex, emittedLight); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final IBlockData fromBlock = this.getBlockState(posX, posY, posZ); ++ this.mutablePos2.setValues(posX, posY, posZ); ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.getEmptyShape(); ++ ++ if (fromShape != VoxelShapes.getEmptyShape() && VoxelShapes.combinationOccludes(VoxelShapes.getEmptyShape(), fromShape)) { ++ continue; ++ } ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int lightLevel; ++ ++ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { ++ // already at lowest (or unloaded), nothing we can do ++ continue; ++ } ++ ++ final IBlockData blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = (customLightHandler != null ? this.getCustomLightLevel(customLightHandler, offX, offY, offZ, blockState.getEmittedLight()) : blockState.getEmittedLight()) & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0L); ++ } ++ ++ currentNibble.set(localIndex, emittedLight); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.setValues(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (VoxelShapes.combinationOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getOpacity(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = (customLightHandler != null ? this.getCustomLightLevel(customLightHandler, offX, offY, offZ, blockState.getEmittedLight()) : blockState.getEmittedLight()) & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ ++ currentNibble.set(localIndex, emittedLight); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered ++ this.increaseQueueInitialLength = increaseQueueLength; ++ this.performLightIncrease(lightAccess); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/StarLightInterface.java b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightInterface.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0fddb331bfcee762da38efea3a36dc6394718519 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/StarLightInterface.java +@@ -0,0 +1,490 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import com.tuinity.tuinity.util.CoordinateUtils; ++import com.tuinity.tuinity.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.ChunkStatus; ++import net.minecraft.server.IChunkAccess; ++import net.minecraft.server.ILightAccess; ++import net.minecraft.server.LightEngineLayerEventListener; ++import net.minecraft.server.NibbleArray; ++import net.minecraft.server.SectionPosition; ++import net.minecraft.server.WorldServer; ++import java.util.ArrayDeque; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.Set; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; ++ ++public final class StarLightInterface { ++ ++ ++ /** ++ * Can be {@code null}, indicating the light is all empty. ++ */ ++ protected final WorldServer world; ++ protected final ILightAccess lightAccess; ++ ++ protected final ArrayDeque cachedSkyPropagators; ++ protected final ArrayDeque cachedBlockPropagators; ++ ++ protected final Long2ObjectOpenHashMap changedBlocks = new Long2ObjectOpenHashMap<>(); ++ ++ protected final LightEngineLayerEventListener skyReader; ++ protected final LightEngineLayerEventListener blockReader; ++ protected static final boolean isClientSide = false; ++ ++ protected final int minSection; ++ protected final int maxSection; ++ protected final int minLightSection; ++ protected final int maxLightSection; ++ ++ public StarLightInterface(final ILightAccess lightAccess, final boolean hasSkyLight, final boolean hasBlockLight) { ++ this.lightAccess = lightAccess; ++ this.world = lightAccess == null ? null : (WorldServer)lightAccess.getWorld(); ++ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; ++ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; ++ if (this.world == null) { ++ this.minSection = 0; ++ this.maxSection = 15; ++ this.minLightSection = -1; ++ this.maxLightSection = 16; ++ } else { ++ this.minSection = WorldUtil.getMinSection(this.world); ++ this.maxSection = WorldUtil.getMaxSection(this.world); ++ this.minLightSection = WorldUtil.getMinLightSection(this.world); ++ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); ++ } ++ this.skyReader = !hasSkyLight ? LightEngineLayerEventListener.Void.INSTANCE : new LightEngineLayerEventListener() { ++ @Override ++ public NibbleArray a(final SectionPosition pos) { ++ final IChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); ++ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLit()) || !chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) { ++ return null; ++ } ++ ++ final int sectionY = pos.getY(); ++ ++ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { ++ return null; ++ } ++ ++ if (chunk.getSkyEmptinessMap() == null) { ++ return null; ++ } ++ ++ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); ++ } ++ ++ @Override ++ public int b(final BlockPosition blockPos) { ++ final int x = blockPos.getX(); ++ int y = blockPos.getY(); ++ final int z = blockPos.getZ(); ++ ++ final IChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(x >> 4, z >> 4); ++ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLit()) || !chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) { ++ return 15; ++ } ++ ++ int sectionY = y >> 4; ++ ++ if (sectionY > StarLightInterface.this.maxLightSection) { ++ return 15; ++ } ++ ++ if (sectionY < StarLightInterface.this.minLightSection) { ++ sectionY = StarLightInterface.this.minLightSection; ++ y = sectionY << 4; ++ } ++ ++ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); ++ final SWMRNibbleArray immediate = nibbles[sectionY - StarLightInterface.this.minLightSection]; ++ ++ if (StarLightInterface.this.isClientSide) { ++ if (!immediate.isNullNibbleUpdating()) { ++ return immediate.getUpdating(x, y, z); ++ } ++ } else { ++ if (!immediate.isNullNibbleVisible()) { ++ return immediate.getVisible(x, y, z); ++ } ++ } ++ ++ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); ++ ++ if (emptinessMap == null) { ++ return 15; ++ } ++ ++ // are we above this chunk's lowest empty section? ++ int lowestY = StarLightInterface.this.minLightSection - 1; ++ for (int currY = StarLightInterface.this.maxSection; currY >= StarLightInterface.this.minSection; --currY) { ++ if (emptinessMap[currY - StarLightInterface.this.minSection]) { ++ continue; ++ } ++ ++ // should always be full lit here ++ lowestY = currY; ++ break; ++ } ++ ++ if (sectionY > lowestY) { ++ return 15; ++ } ++ ++ // this nibble is going to depend solely on the skylight data above it ++ // find first non-null data above (there does exist one, as we just found it above) ++ for (int currY = sectionY + 1; currY <= StarLightInterface.this.maxLightSection; ++currY) { ++ final SWMRNibbleArray nibble = nibbles[currY - StarLightInterface.this.minLightSection]; ++ if (StarLightInterface.this.isClientSide) { ++ if (!nibble.isNullNibbleUpdating()) { ++ return nibble.getUpdating(x, 0, z); ++ } ++ } else { ++ if (!nibble.isNullNibbleVisible()) { ++ return nibble.getVisible(x, 0, z); ++ } ++ } ++ } ++ ++ // should never reach here ++ return 15; ++ } ++ ++ @Override ++ public void a(final SectionPosition pos, final boolean notReady) { ++ StarLightInterface.this.sectionChange(pos, notReady); ++ } ++ }; ++ this.blockReader = !hasBlockLight ? LightEngineLayerEventListener.Void.INSTANCE : new LightEngineLayerEventListener() { ++ @Override ++ public NibbleArray a(final SectionPosition pos) { ++ final IChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); ++ ++ if (pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { ++ return null; ++ } ++ ++ return chunk != null ? chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble() : null; ++ } ++ ++ @Override ++ public int b(final BlockPosition blockPos) { ++ final int cx = blockPos.getX() >> 4; ++ final int cy = blockPos.getY() >> 4; ++ final int cz = blockPos.getZ() >> 4; ++ ++ if (cy < StarLightInterface.this.minLightSection || cy > StarLightInterface.this.maxLightSection) { ++ return 0; ++ } ++ ++ final IChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(cx, cz); ++ ++ if (chunk == null) { ++ return 0; ++ } ++ ++ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - StarLightInterface.this.minLightSection]; ++ if (StarLightInterface.this.isClientSide) { ++ return nibble.getUpdating(blockPos.getX(), blockPos.getY(), blockPos.getZ()); ++ } else { ++ return nibble.getVisible(blockPos.getX(), blockPos.getY(), blockPos.getZ()); ++ } ++ } ++ ++ @Override ++ public void a(final SectionPosition pos, final boolean notReady) { ++ return; // block engine doesn't care ++ } ++ }; ++ } ++ ++ public LightEngineLayerEventListener getSkyReader() { ++ return this.skyReader; ++ } ++ ++ public LightEngineLayerEventListener getBlockReader() { ++ return this.blockReader; ++ } ++ ++ public boolean isClientSide() { ++ return this.isClientSide; ++ } ++ ++ public IChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { ++ if (this.world == null) { ++ // empty world ++ return null; ++ } ++ return this.world.getChunkProvider().getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ public boolean hasUpdates() { ++ synchronized (this) { ++ return !this.changedBlocks.isEmpty(); ++ } ++ } ++ ++ public WorldServer getWorld() { ++ return this.world; ++ } ++ ++ public ILightAccess getLightAccess() { ++ return this.lightAccess; ++ } ++ ++ protected final SkyStarLightEngine getSkyLightEngine() { ++ if (this.cachedSkyPropagators == null) { ++ return null; ++ } ++ final SkyStarLightEngine ret; ++ synchronized (this.cachedSkyPropagators) { ++ ret = this.cachedSkyPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new SkyStarLightEngine(this.world); ++ } ++ return ret; ++ } ++ ++ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { ++ if (this.cachedSkyPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedSkyPropagators) { ++ this.cachedSkyPropagators.addFirst(engine); ++ } ++ } ++ ++ protected final BlockStarLightEngine getBlockLightEngine() { ++ if (this.cachedBlockPropagators == null) { ++ return null; ++ } ++ final BlockStarLightEngine ret; ++ synchronized (this.cachedBlockPropagators) { ++ ret = this.cachedBlockPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new BlockStarLightEngine(this.world); ++ } ++ return ret; ++ } ++ ++ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { ++ if (this.cachedBlockPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedBlockPropagators) { ++ this.cachedBlockPropagators.addFirst(engine); ++ } ++ } ++ ++ public void blockChange(BlockPosition pos) { ++ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world ++ return; ++ } ++ ++ pos = pos.immutableCopy(); ++ synchronized (this.changedBlocks) { ++ this.changedBlocks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkChanges(); ++ }).changedPositions.add(pos); ++ } ++ } ++ ++ public void sectionChange(final SectionPosition pos, final boolean newEmptyValue) { ++ if (this.world == null) { // empty world ++ return; ++ } ++ ++ synchronized (this.changedBlocks) { ++ final ChunkChanges changes = this.changedBlocks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkChanges(); ++ }); ++ if (changes.changedSectionSet == null) { ++ changes.changedSectionSet = new Boolean[this.maxSection - this.minSection + 1]; ++ } ++ changes.changedSectionSet[pos.getY() - this.minSection] = Boolean.valueOf(newEmptyValue); ++ } ++ } ++ ++ public void forceLoadInChunk(final IChunkAccess chunk, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void lightChunk(final IChunkAccess chunk, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.light(this.lightAccess, chunk, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.light(this.lightAccess, chunk, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, ++ final IntConsumer onComplete) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, ++ blockEngine == null ? onComplete : null); ++ } ++ if (blockEngine != null) { ++ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkChunkEdges(final int chunkX, final int chunkZ) { ++ this.checkSkyEdges(chunkX, chunkZ); ++ this.checkBlockEdges(chunkX, chunkZ); ++ } ++ ++ public void checkSkyEdges(final int chunkX, final int chunkZ) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ } ++ } ++ ++ public void checkBlockEdges(final int chunkX, final int chunkZ) { ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ try { ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ } ++ } ++ ++ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ try { ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); ++ } ++ } finally { ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void propagateChanges() { ++ synchronized (this.changedBlocks) { ++ if (this.changedBlocks.isEmpty()) { ++ return; ++ } ++ } ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ // TODO be smarter about this in the future ++ final Long2ObjectOpenHashMap changedBlocks; ++ synchronized (this.changedBlocks) { ++ changedBlocks = this.changedBlocks.clone(); ++ this.changedBlocks.clear(); ++ } ++ ++ for (final Iterator> iterator = changedBlocks.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Long2ObjectMap.Entry entry = iterator.next(); ++ final long coordinate = entry.getLongKey(); ++ final ChunkChanges changes = entry.getValue(); ++ final Set positions = changes.changedPositions; ++ final Boolean[] sectionChanges = changes.changedSectionSet; ++ ++ final int chunkX = CoordinateUtils.getChunkX(coordinate); ++ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); ++ ++ if (skyEngine != null) { ++ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); ++ } ++ if (blockEngine != null) { ++ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); ++ } ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ protected static final class ChunkChanges { ++ ++ // note: on the main thread, empty section changes are queued before block changes. This means we don't need ++ // to worry about cases where a block change is called inside an empty chunk section, according to the "emptiness" map per chunk, ++ // for example. ++ public final Set changedPositions = new HashSet<>(); ++ ++ public Boolean[] changedSectionSet; ++ ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/VariableBlockLightHandler.java b/src/main/java/com/tuinity/tuinity/chunk/light/VariableBlockLightHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b8a6c59ee3c919e47e4be76fc4e1737d81a5810b +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/VariableBlockLightHandler.java +@@ -0,0 +1,30 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import net.minecraft.server.BlockPosition; ++import java.util.Collection; ++ ++/** ++ * Recommended implementation is {@link VariableBlockLightHandlerImpl}, but you can implement this interface yourself ++ * if you want. ++ */ ++public interface VariableBlockLightHandler { ++ ++ /** ++ * Returns the custom light level for the specified position. Must return {@code -1} if there is custom level. ++ * @param x Block x world coordinate ++ * @param y Block y world coordinate ++ * @param z Block z world coordinate ++ * @return Custom light level for the specified position ++ */ ++ public int getLightLevel(final int x, final int y, final int z); ++ ++ /** ++ * Returns a collection of all the custom light positions inside the specified chunk. This must be fast, ++ * as it is used during chunk lighting. ++ * @param chunkX Chunk's x coordinate. ++ * @param chunkZ Chunk's z coordinate. ++ * @return Collection of all the custom light positions in the specified chunk. ++ */ ++ public Collection getCustomLightPositions(final int chunkX, final int chunkZ); ++ ++} +diff --git a/src/main/java/com/tuinity/tuinity/chunk/light/VariableBlockLightHandlerImpl.java b/src/main/java/com/tuinity/tuinity/chunk/light/VariableBlockLightHandlerImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..125f59826a9b0174039139ed0715a4ed3df3724b +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/chunk/light/VariableBlockLightHandlerImpl.java +@@ -0,0 +1,112 @@ ++package com.tuinity.tuinity.chunk.light; ++ ++import com.tuinity.tuinity.util.CoordinateUtils; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import net.minecraft.server.BlockPosition; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.concurrent.locks.StampedLock; ++ ++public class VariableBlockLightHandlerImpl implements VariableBlockLightHandler { ++ ++ protected final Long2ObjectOpenHashMap> positionsByChunk = new Long2ObjectOpenHashMap<>(); ++ protected final Long2IntOpenHashMap lightValuesByPosition = new Long2IntOpenHashMap(); ++ protected final StampedLock seqlock = new StampedLock(); ++ { ++ this.lightValuesByPosition.defaultReturnValue(-1); ++ this.positionsByChunk.defaultReturnValue(Collections.emptySet()); ++ } ++ ++ @Override ++ public int getLightLevel(final int x, final int y, final int z) { ++ final long key = CoordinateUtils.getBlockKey(x, y, z); ++ try { ++ final long attempt = this.seqlock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ final int ret = this.lightValuesByPosition.get(key); ++ ++ if (this.seqlock.validate(attempt)) { ++ return ret; ++ } ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore ++ } ++ ++ this.seqlock.readLock(); ++ try { ++ return this.lightValuesByPosition.get(key); ++ } finally { ++ this.seqlock.tryUnlockRead(); ++ } ++ } ++ ++ @Override ++ public Collection getCustomLightPositions(final int chunkX, final int chunkZ) { ++ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ try { ++ final long attempt = this.seqlock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ final Set ret = new HashSet<>(this.positionsByChunk.get(key)); ++ ++ if (this.seqlock.validate(attempt)) { ++ return ret; ++ } ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore ++ } ++ ++ this.seqlock.readLock(); ++ try { ++ return new HashSet<>(this.positionsByChunk.get(key)); ++ } finally { ++ this.seqlock.tryUnlockRead(); ++ } ++ } ++ ++ public void setSource(final int x, final int y, final int z, final int to) { ++ if (to < 0 || to > 15) { ++ throw new IllegalArgumentException(); ++ } ++ this.seqlock.writeLock(); ++ try { ++ if (this.lightValuesByPosition.put(CoordinateUtils.getBlockKey(x, y, z), to) == -1) { ++ this.positionsByChunk.computeIfAbsent(CoordinateUtils.getChunkKey(x >> 4, z >> 4), (final long keyInMap) -> { ++ return new HashSet<>(); ++ }).add(new BlockPosition(x, y, z)); ++ } ++ } finally { ++ this.seqlock.tryUnlockWrite(); ++ } ++ } ++ ++ public int removeSource(final int x, final int y, final int z) { ++ this.seqlock.writeLock(); ++ try { ++ final int ret = this.lightValuesByPosition.remove(CoordinateUtils.getBlockKey(x, y, z)); ++ ++ if (ret != -1) { ++ final long chunkKey = CoordinateUtils.getChunkKey(x >> 4, z >> 4); ++ ++ final Set positions = this.positionsByChunk.get(chunkKey); ++ positions.remove(new BlockPosition(x, y, z)); ++ ++ if (positions.isEmpty()) { ++ this.positionsByChunk.remove(chunkKey); ++ } ++ } ++ ++ return ret; ++ } finally { ++ this.seqlock.tryUnlockWrite(); ++ } ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +index 03a59aabc2a35daf7eee899967b569a8ac3b632e..ceeb515f4528551659598d9999917f8596e3eded 100644 +--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java ++++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java +@@ -223,6 +223,12 @@ public final class TuinityConfig { + } + } + ++ public static boolean useNewLightEngine; ++ ++ private static void useNewLightEngine() { ++ useNewLightEngine = TuinityConfig.getBoolean("use-new-light-engine", true); ++ } ++ + public static final class WorldConfig { + + public final String worldName; +diff --git a/src/main/java/com/tuinity/tuinity/util/CoordinateUtils.java b/src/main/java/com/tuinity/tuinity/util/CoordinateUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..81fe01c122529f1716a264263957500015476f5f +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/CoordinateUtils.java +@@ -0,0 +1,128 @@ ++package com.tuinity.tuinity.util; ++ ++import net.minecraft.server.BlockPosition; ++import net.minecraft.server.ChunkCoordIntPair; ++import net.minecraft.server.Entity; ++import net.minecraft.server.MathHelper; ++import net.minecraft.server.SectionPosition; ++ ++public final class CoordinateUtils { ++ ++ // dx, dz are relative to the target chunk ++ // dx, dz in [-radius, radius] ++ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { ++ return (dx + radius) + (2 * radius + 1)*(dz + radius); ++ } ++ ++ // the chunk keys are compatible with vanilla ++ ++ public static long getChunkKey(final BlockPosition pos) { ++ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final Entity entity) { ++ return ((long)(MathHelper.floor(entity.locZ()) >> 4) << 32) | ((MathHelper.floor(entity.locX()) >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final ChunkCoordIntPair pos) { ++ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final SectionPosition pos) { ++ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getChunkX(final long chunkKey) { ++ return (int)chunkKey; ++ } ++ ++ public static int getChunkZ(final long chunkKey) { ++ return (int)(chunkKey >>> 32); ++ } ++ ++ public static int getChunkCoordinate(final double blockCoordinate) { ++ return MathHelper.floor(blockCoordinate) >> 4; ++ } ++ ++ // the section keys are compatible with vanilla's ++ ++ static final int SECTION_X_BITS = 22; ++ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; ++ static final int SECTION_Y_BITS = 20; ++ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; ++ static final int SECTION_Z_BITS = 22; ++ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; ++ // format is y,z,x (in order of LSB to MSB) ++ static final int SECTION_Y_SHIFT = 0; ++ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; ++ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; ++ static final int SECTION_TO_BLOCK_SHIFT = 4; ++ ++ public static long getChunkSectionKey(final int x, final int y, final int z) { ++ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final SectionPosition pos) { ++ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final ChunkCoordIntPair pos, final int y) { ++ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final BlockPosition pos) { ++ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static long getChunkSectionKey(final Entity entity) { ++ return ((MathHelper.floorLong(entity.locX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((MathHelper.floorLong(entity.locY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ ((MathHelper.floorLong(entity.locZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static int getChunkSectionX(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); ++ } ++ ++ public static int getChunkSectionY(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); ++ } ++ ++ public static int getChunkSectionZ(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); ++ } ++ ++ // the block coordinates are not necessarily compatible with vanilla's ++ ++ public static int getBlockCoordinate(final double blockCoordinate) { ++ return MathHelper.floor(blockCoordinate); ++ } ++ ++ public static long getBlockKey(final int x, final int y, final int z) { ++ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); ++ } ++ ++ public static long getBlockKey(final BlockPosition pos) { ++ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); ++ } ++ ++ public static long getBlockKey(final Entity entity) { ++ return ((long)entity.locX() & 0x7FFFFFF) | (((long)entity.locZ() & 0x7FFFFFF) << 27) | ((long)entity.locY() << 54); ++ } ++ ++ private CoordinateUtils() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/IntegerUtil.java b/src/main/java/com/tuinity/tuinity/util/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..695444a510e616180734f5fd284f1a00a2d73ea6 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/IntegerUtil.java +@@ -0,0 +1,226 @@ ++package com.tuinity.tuinity.util; ++ ++public final class IntegerUtil { ++ ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static int trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ // copied from hacker's delight (signed division magic value) ++ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt ++ public static long getDivisorNumbers(final int d) { ++ final int ad = IntegerUtil.branchlessAbs(d); ++ ++ if (ad < 2) { ++ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); ++ } ++ ++ final int two31 = 0x80000000; ++ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour ++ ++ int p = 31; ++ ++ // all these variables are UNSIGNED! ++ int t = two31 + (d >>> 31); ++ int anc = t - 1 - t%ad; ++ int q1 = (int)((two31 & mask)/(anc & mask)); ++ int r1 = two31 - q1*anc; ++ int q2 = (int)((two31 & mask)/(ad & mask)); ++ int r2 = two31 - q2*ad; ++ int delta; ++ ++ do { ++ p = p + 1; ++ q1 = 2*q1; // Update q1 = 2**p/|nc|. ++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). ++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) ++ q1 = q1 + 1; ++ r1 = r1 - anc; ++ } ++ q2 = 2*q2; // Update q2 = 2**p/|d|. ++ r2 = 2*r2; // Update r2 = rem(2**p, |d|). ++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) ++ q2 = q2 + 1; ++ r2 = r2 - ad; ++ } ++ delta = ad - r2; ++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); ++ ++ int magicNum = q2 + 1; ++ if (d < 0) { ++ magicNum = -magicNum; ++ } ++ int shift = p - 32; ++ return ((long)magicNum << 32) | shift; ++ } ++ ++ public static int branchlessAbs(final int val) { ++ // -n = -1 ^ n + 1 ++ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ public static long branchlessAbs(final long val) { ++ // -n = -1 ^ n + 1 ++ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ //https://github.com/skeeto/hash-prospector for hash functions ++ ++ //score = ~590.47984224483832 ++ public static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ //score = ~310.01596637036749 ++ public static int hash1(int x) { ++ x ^= x >>> 15; ++ x *= 0x356aaaad; ++ x ^= x >>> 17; ++ return x; ++ } ++ ++ public static int hash2(int x) { ++ x ^= x >>> 16; ++ x *= 0x7feb352d; ++ x ^= x >>> 15; ++ x *= 0x846ca68b; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public static int hash3(int x) { ++ x ^= x >>> 17; ++ x *= 0xed5ad4bb; ++ x ^= x >>> 11; ++ x *= 0xac4c1b51; ++ x ^= x >>> 15; ++ x *= 0x31848bab; ++ x ^= x >>> 14; ++ return x; ++ } ++ ++ //score = ~365.79959673201887 ++ public static long hash1(long x) { ++ x ^= x >>> 27; ++ x *= 0xb24924b71d2d354bL; ++ x ^= x >>> 28; ++ return x; ++ } ++ ++ //h2 hash ++ public static long hash2(long x) { ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ return x; ++ } ++ ++ public static long hash3(long x) { ++ x ^= x >>> 45; ++ x *= 0xc161abe5704b6c79L; ++ x ^= x >>> 41; ++ x *= 0xe3e5389aedbc90f7L; ++ x ^= x >>> 56; ++ x *= 0x1f9aba75a52db073L; ++ x ^= x >>> 53; ++ return x; ++ } ++ ++ private IntegerUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/com/tuinity/tuinity/util/WorldUtil.java b/src/main/java/com/tuinity/tuinity/util/WorldUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..141748fe4915eb46671f1d532951f14d7080818d +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/WorldUtil.java +@@ -0,0 +1,48 @@ ++package com.tuinity.tuinity.util; ++ ++import net.minecraft.server.World; ++ ++public final class WorldUtil { ++ ++ // min, max are inclusive ++ // TODO update these for 1.17 ++ ++ public static int getMaxSection(final World world) { ++ return 15; ++ } ++ ++ public static int getMinSection(final World world) { ++ return 0; ++ } ++ ++ public static int getMaxLightSection(final World world) { ++ return getMaxSection(world) + 1; ++ } ++ ++ public static int getMinLightSection(final World world) { ++ return getMinSection(world) - 1; ++ } ++ ++ ++ ++ public static int getTotalSections(final World world) { ++ return getMaxSection(world) - getMinSection(world) + 1; ++ } ++ ++ public static int getTotalLightSections(final World world) { ++ return getMaxLightSection(world) - getMinLightSection(world) + 1; ++ } ++ ++ public static int getMinBlockY(final World world) { ++ return getMinSection(world) << 4; ++ } ++ ++ public static int getMaxBlockY(final World world) { ++ return (getMaxSection(world) << 4) | 15; ++ } ++ ++ private WorldUtil() { ++ throw new RuntimeException(); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java +index 2760b377d1f68ac5f66e7274317379e2dda8288a..829d4a7508e1656dbdc912096b7eafcf30cbb5b2 100644 +--- a/src/main/java/net/minecraft/server/BlockBase.java ++++ b/src/main/java/net/minecraft/server/BlockBase.java +@@ -330,6 +330,7 @@ public abstract class BlockBase { + this.n = blockbase_info.s; + this.o = blockbase_info.t; + this.p = blockbase_info.u; ++ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Tuinity + } + // Paper start - impl cached craft block data, lazy load to fix issue with loading at the wrong time + private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; +@@ -352,6 +353,19 @@ public abstract class BlockBase { + } + // Tuinity end + ++ // Tuinity start ++ protected int opacityIfCached = -1; ++ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] ++ public final int getOpacityIfCached() { ++ return this.opacityIfCached; ++ } ++ ++ protected final boolean conditionallyFullOpaque; ++ public final boolean isConditionallyFullOpaque() { ++ return this.conditionallyFullOpaque; ++ } ++ // Tuinity end ++ + public void a() { + this.fluid = this.getBlock().d(this.p()); // Paper - moved from getFluid() + this.isTicking = this.getBlock().isTicking(this.p()); // Paper - moved from isTicking() +@@ -361,6 +375,33 @@ public abstract class BlockBase { + this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here + this.staticPathType = null; // Tuinity - cache static path type + this.neighbourOverridePathType = null; // Tuinity - cache static path types ++ this.opacityIfCached = this.a == null || this.isConditionallyFullOpaque() ? -1 : this.a.getOpacity(); // Tuinity - cache opacity for light ++ // Tuinity start - optimise culling shape cache for light ++ if (this.a != null && this.a.getCullingShapeCache() != null) { ++ for (int i = 0, len = this.a.getCullingShapeCache().length; i < len; ++i) { ++ VoxelShape face = this.a.getCullingShapeCache()[i].simplify(); ++ if (face.isEmpty()) { ++ this.a.getCullingShapeCache()[i] = VoxelShapes.getEmptyShape(); ++ continue; ++ } ++ List boxes = face.getBoundingBoxesRepresentation(); ++ if (boxes.size() == 1) { ++ AxisAlignedBB boundingBox = boxes.get(0); ++ if (boundingBox.equals(VoxelShapes.optimisedFullCube.aabb)) { ++ this.a.getCullingShapeCache()[i] = VoxelShapes.fullCube(); ++ } else { ++ this.a.getCullingShapeCache()[i] = VoxelShapes.of(boundingBox); ++ if (!(this.a.getCullingShapeCache()[i] instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) && ++ this.a.getCullingShapeCache()[i].getBoundingBoxesRepresentation().size() == 1) { ++ this.a.getCullingShapeCache()[i] = new com.tuinity.tuinity.voxel.AABBVoxelShape(boundingBox); ++ } ++ } ++ continue; ++ } ++ this.a.getCullingShapeCache()[i] = face; ++ } ++ } ++ // Tuinity end - optimise culling shape cache for light + + } + +@@ -689,9 +730,9 @@ public abstract class BlockBase { + private static final int f = EnumBlockSupport.values().length; + protected final boolean a; + private final boolean g; +- private final int h; ++ private final int h; private final int getOpacity() { return this.h; } // Tuinity - OBFHELPER + @Nullable +- private final VoxelShape[] i; ++ private final VoxelShape[] i; private final VoxelShape[] getCullingShapeCache () { return this.i; } // Tuinity - OBFHELPER + protected final VoxelShape b; + protected final boolean c; + private final boolean[] j; +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index 781d74cf7e3669d71727cce781a8f8ce088c5547..ae07ea2a34f5cd82ce2eae523359cb7540065335 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -140,6 +140,52 @@ public class Chunk implements IChunkAccess { + } + } + // Tuinity end - optimise hard collision handling ++ // Tuinity start - rewrite light engine ++ protected volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(); ++ protected volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(); ++ protected volatile boolean[] skyEmptinessMap; ++ protected volatile boolean[] blockEmptinessMap; ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.blockNibbles; ++ } ++ ++ @Override ++ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.blockNibbles = nibbles; ++ } ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.skyNibbles; ++ } ++ ++ @Override ++ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.skyNibbles = nibbles; ++ } ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return this.skyEmptinessMap; ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(boolean[] emptinessMap) { ++ this.skyEmptinessMap = emptinessMap; ++ } ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return this.blockEmptinessMap; ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(boolean[] emptinessMap) { ++ this.blockEmptinessMap = emptinessMap; ++ } ++ // Tuinity end - rewrite light engine + + // Tuinity start - entity slices by class + private final com.tuinity.tuinity.chunk.ChunkEntitiesByClass entitiesByClass = new com.tuinity.tuinity.chunk.ChunkEntitiesByClass(this); +@@ -443,6 +489,12 @@ public class Chunk implements IChunkAccess { + + public Chunk(World world, ProtoChunk protochunk) { + this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.getInhabitedTime(), protochunk.getSections(), (Consumer) null); ++ // Tuinity start - copy over protochunk light ++ this.setBlockNibbles(protochunk.getBlockNibbles()); ++ this.setSkyNibbles(protochunk.getSkyNibbles()); ++ this.setSkyEmptinessMap(protochunk.getSkyEmptinessMap()); ++ this.setBlockEmptinessMap(protochunk.getBlockEmptinessMap()); ++ // Tuinity end - copy over protochunk light + Iterator iterator = protochunk.y().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index 17a5b5b5bab4412242a18224b366674c25cdf286..076d6c1e1cc049dd312ecb30518e7b25fc2d7371 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -64,6 +64,13 @@ public class ChunkRegionLoader { + private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); + // Paper end + ++ // Tuinity start - rewrite light engine ++ private static final int STARLIGHT_LIGHT_VERSION = 4; ++ ++ private static final String UNINITIALISED_SKYLIGHT_TAG = "starlight.skylight_uninit"; ++ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; ++ // Tuinity end - rewrite light engine ++ + public static InProgressChunkHolder loadChunk(WorldServer worldserver, DefinedStructureManager definedstructuremanager, VillagePlace villageplace, ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound, boolean distinguish) { + ArrayDeque tasksToExecuteOnMain = new ArrayDeque<>(); + // Paper end +@@ -93,13 +100,17 @@ public class ChunkRegionLoader { + ProtoChunkTickList protochunkticklist1 = new ProtoChunkTickList<>((fluidtype) -> { + return fluidtype == null || fluidtype == FluidTypes.EMPTY; + }, chunkcoordintpair, nbttagcompound1.getList("LiquidsToBeTicked", 9)); +- boolean flag = nbttagcompound1.getBoolean("isLightOn"); ++ boolean flag = (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nbttagcompound1.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION : nbttagcompound1.getBoolean("isLightOn")); boolean canUseSkyLight = flag && getStatus(nbttagcompound).isAtLeastStatus(ChunkStatus.LIGHT); boolean canUseBlockLight = canUseSkyLight; // Tuinity + NBTTagList nbttaglist = nbttagcompound1.getList("Sections", 10); + boolean flag1 = true; + ChunkSection[] achunksection = new ChunkSection[16]; + boolean flag2 = worldserver.getDimensionManager().hasSkyLight(); + ChunkProviderServer chunkproviderserver = worldserver.getChunkProvider(); + LightEngine lightengine = chunkproviderserver.getLightEngine(); ++ com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(worldserver); // Tuinity - replace light impl ++ com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(worldserver); // Tuinity - replace light impl ++ final int minSection = com.tuinity.tuinity.util.WorldUtil.getMinLightSection(worldserver); ++ final int maxSection = com.tuinity.tuinity.util.WorldUtil.getMaxLightSection(worldserver); + + if (flag) { + tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main +@@ -127,6 +138,7 @@ public class ChunkRegionLoader { + + if (flag) { + if (nbttagcompound2.hasKeyOfType("BlockLight", 7)) { ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && canUseBlockLight) blockNibbles[b0 - minSection] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(nbttagcompound2.getByteArray("BlockLight").clone()); // Tuinity - replace light impl + // Paper start - delay this task since we're executing off-main + NibbleArray blockLight = new NibbleArray(nbttagcompound2.getByteArray("BlockLight")); + tasksToExecuteOnMain.add(() -> { +@@ -136,13 +148,14 @@ public class ChunkRegionLoader { + } + + if (flag2 && nbttagcompound2.hasKeyOfType("SkyLight", 7)) { ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && canUseSkyLight) skyNibbles[b0 - minSection] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(nbttagcompound2.getByteArray("SkyLight").clone()); // Tuinity - replace light impl + // Paper start - delay this task since we're executing off-main + NibbleArray skyLight = new NibbleArray(nbttagcompound2.getByteArray("SkyLight")); + tasksToExecuteOnMain.add(() -> { + lightengine.a(EnumSkyBlock.SKY, SectionPosition.a(chunkcoordintpair, b0), skyLight, true); + }); + // Paper end - delay this task since we're executing off-main +- } ++ } else if (flag2 && nbttagcompound2.getBoolean(UNINITIALISED_SKYLIGHT_TAG)) skyNibbles[b0 - minSection] = new com.tuinity.tuinity.chunk.light.SWMRNibbleArray(); // Tuinity - replace light impl + } + } + +@@ -181,8 +194,12 @@ public class ChunkRegionLoader { + object = new Chunk(worldserver.getMinecraftWorld(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys. + createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here + );// Paper end ++ ((Chunk)object).setBlockNibbles(blockNibbles); // Tuinity - replace light impl ++ ((Chunk)object).setSkyNibbles(skyNibbles); // Tuinity - replace light impl + } else { + ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter ++ protochunk.setBlockNibbles(blockNibbles); // Tuinity - replace light impl ++ protochunk.setSkyNibbles(skyNibbles); // Tuinity - replace light impl + + protochunk.a(biomestorage); + object = protochunk; +@@ -361,15 +378,20 @@ public class ChunkRegionLoader { + NibbleArray[] blockLight = new NibbleArray[17 - (-1)]; + NibbleArray[] skyLight = new NibbleArray[17 - (-1)]; + ++ // Tuinity start - rewrite light impl ++ final int minSection = com.tuinity.tuinity.util.WorldUtil.getMinLightSection(world); ++ final int maxSection = com.tuinity.tuinity.util.WorldUtil.getMaxLightSection(world); ++ // Tuinity end - rewrite light impl ++ + for (int i = -1; i < 17; ++i) { +- NibbleArray blockArray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i)); +- NibbleArray skyArray = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i)); ++ NibbleArray blockArray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasBlockLight ? null : (chunk.getBlockNibbles()[i - minSection].isAllZero() ? new NibbleArray() : chunk.getBlockNibbles()[i - minSection].toVanillaNibble())) : lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkPos, i)); // Tuinity - chunk might not be loaded ++ NibbleArray skyArray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasSkyLight ? null : (chunk.getSkyNibbles()[i - minSection].isAllZero() ? new NibbleArray() : chunk.getSkyNibbles()[i - minSection].toVanillaNibble())) : lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkPos, i)); // Tuinity - chunk might not be loaded + + // copy data for safety +- if (blockArray != null) { ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && blockArray != null) { // Tuinity - data already copied + blockArray = blockArray.copy(); + } +- if (skyArray != null) { ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && skyArray != null) { // Tuinity - data already copied + skyArray = skyArray.copy(); + } + +@@ -404,6 +426,10 @@ public class ChunkRegionLoader { + } + public static NBTTagCompound saveChunk(WorldServer worldserver, IChunkAccess ichunkaccess, AsyncSaveData asyncsavedata) { + // Paper end ++ // Tuinity start - rewrite light impl ++ final int minSection = com.tuinity.tuinity.util.WorldUtil.getMinLightSection(worldserver); ++ final int maxSection = com.tuinity.tuinity.util.WorldUtil.getMaxLightSection(worldserver); ++ // Tuinity end - rewrite light impl + ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos(); + NBTTagCompound nbttagcompound = new NBTTagCompound(); + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); +@@ -437,8 +463,8 @@ public class ChunkRegionLoader { + NibbleArray nibblearray; // block light + NibbleArray nibblearray1; // sky light + if (asyncsavedata == null) { +- nibblearray = lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) +- nibblearray1 = lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) ++ nibblearray = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasBlockLight ? null : (ichunkaccess.getBlockNibbles()[i - minSection].isAllZero() ? new NibbleArray() : ichunkaccess.getBlockNibbles()[i - minSection].toVanillaNibble())) : lightenginethreaded.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, i)); // Tuinity - chunk might not be loaded ++ nibblearray1 = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (!lightenginethreaded.hasSkyLight ? null : (ichunkaccess.getSkyNibbles()[i - minSection].isAllZero() ? new NibbleArray() : ichunkaccess.getSkyNibbles()[i - minSection].toVanillaNibble())) : lightenginethreaded.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, i)); // Tuinity - chunk might not be loaded + } else { + nibblearray = asyncsavedata.blockLight[i + 1]; // +1 to offset the -1 starting index + nibblearray1 = asyncsavedata.skyLight[i + 1]; // +1 to offset the -1 starting index +@@ -452,12 +478,12 @@ public class ChunkRegionLoader { + } + + if (nibblearray != null && !nibblearray.c()) { +- nbttagcompound2.setByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper ++ nbttagcompound2.setByteArray("BlockLight", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.asBytesPoolSafe().clone()); // Paper // Tuinity - data is already cloned + } + + if (nibblearray1 != null && !nibblearray1.c()) { +- nbttagcompound2.setByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper +- } ++ nbttagcompound2.setByteArray("SkyLight", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray1.asBytes() : nibblearray1.asBytesPoolSafe().clone()); // Paper // Tuinity - data is already cloned ++ } else if (nibblearray1 != null && com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) nbttagcompound2.setBoolean(UNINITIALISED_SKYLIGHT_TAG, true); // Tuinity - store uninitialised tags + + nbttaglist.add(nbttagcompound2); + } +@@ -465,7 +491,7 @@ public class ChunkRegionLoader { + + nbttagcompound1.set("Sections", nbttaglist); + if (flag) { +- nbttagcompound1.setBoolean("isLightOn", true); ++ nbttagcompound1.setBoolean("isLightOn", com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? false : true); if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) nbttagcompound1.setInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Tuinity + } + + BiomeStorage biomestorage = ichunkaccess.getBiomeIndex(); +diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java +index cebd808e273dbdb88feb16920dd7a2f60390b34f..33b8f4e0f09fdc41c8ea48b6ed77af199136ab92 100644 +--- a/src/main/java/net/minecraft/server/ChunkSection.java ++++ b/src/main/java/net/minecraft/server/ChunkSection.java +@@ -11,7 +11,7 @@ public class ChunkSection { + short nonEmptyBlockCount; // Paper - package-private + short tickingBlockCount; // Paper - private -> package-private + private short e; +- final DataPaletteBlock blockIds; // Paper - package-private ++ public final DataPaletteBlock blockIds; // Paper - package-private // Tuinity - public + + final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper + +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 04d505c7a19775d0353c10424a84a1ce8885dc2c..9f5b7243ccbe0729a061345c25033d9145b91b3f 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -527,6 +527,185 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + } + } + ++ /* // TODO remove debug ++ this.networkManager.disableAutomaticFlush(); ++ ++ if (MinecraftServer.currentTick % 20 == 0) { ++ int centerX = MathHelper.floor(this.locX()) >> 4; ++ int centerZ = MathHelper.floor(this.locZ()) >> 4; ++ byte[] full = new byte[2048]; ++ byte[] empty = new byte[2048]; ++ java.util.Arrays.fill(full, (byte)-1); ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ int cx = centerX + dx; ++ int cz = centerZ + dz; ++ ++ Chunk chunk = this.getWorldServer().getChunkProvider().getChunkAtIfLoadedImmediately(cx, cz); ++ ++ if (chunk == null) { ++ continue; ++ } ++ ++ for (int y = -1; y <= 16; ++y) { ++ NibbleArray nibble = this.getWorldServer().getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY) ++ .a(new SectionPosition(cx, y, cz)); ++ org.bukkit.Color color; ++ org.bukkit.block.data.BlockData blockColor; ++ if (nibble == null) { ++ color = org.bukkit.Color.PURPLE; ++ blockColor = org.bukkit.Material.PURPLE_WOOL.createBlockData(); ++ continue; ++ } else { ++ if (nibble.c()) { // is null ++ color = org.bukkit.Color.BLUE; ++ blockColor = org.bukkit.Material.BLUE_WOOL.createBlockData(); ++ } else if (java.util.Arrays.equals(nibble.justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist(), full)) { ++ color = org.bukkit.Color.RED; ++ blockColor = org.bukkit.Material.RED_WOOL.createBlockData(); ++ } else if (java.util.Arrays.equals(nibble.justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist(), empty)) { ++ color = org.bukkit.Color.BLACK; ++ blockColor = org.bukkit.Material.BLACK_WOOL.createBlockData(); ++ } else { ++ color = org.bukkit.Color.ORANGE; ++ blockColor = org.bukkit.Material.ORANGE_WOOL.createBlockData(); ++ } ++ } ++ if (false) { ++ if (y < 0 || y > 15 || chunk.getSections()[y] == null || chunk.getSections()[y].isFullOfAir()) { ++ color = org.bukkit.Color.BLACK; ++ blockColor = org.bukkit.Material.BLACK_WOOL.createBlockData(); ++ } else { ++ color = org.bukkit.Color.WHITE; ++ blockColor = org.bukkit.Material.WHITE_WOOL.createBlockData(); ++ } ++ } ++ ++ org.bukkit.Particle.DustOptions dustOptions = new org.bukkit.Particle.DustOptions(color, 1.7f); ++ ++ for (int i = 0; i <= 16; ++i) { ++ // y axis ++ ++ double xVal = i == 0 ? 0.5 : (i == 16 ? 15.5 : i); ++ ++ // left side ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + xVal, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + xVal, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ // right side ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + xVal, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + xVal, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ ++ // x axis ++ ++ // bottom ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 0.5, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 0.5, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ // top ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 15.5, ++ cz * 16 + 0.5, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + xVal, ++ y*16 + 15.5, ++ cz * 16 + 15.5, ++ 1, ++ dustOptions ++ ); ++ ++ // z axis ++ // bottom ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + 0.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + 0.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ ++ //top ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 0.5, ++ y*16 + 15.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ ++ this.getBukkitEntity().spawnParticle(org.bukkit.Particle.REDSTONE, ++ cx * 16 + 15.5, ++ y*16 + 15.5, ++ cz * 16 + xVal, ++ 1, ++ dustOptions ++ ); ++ } ++ } ++ } ++ } ++ } ++ ++ this.networkManager.enableAutomaticFlush(); ++ ++ //System.out.println("Block: " + this.getBukkitEntity().getLocation().getBlock().getLightFromBlocks()); ++ //System.out.println("Sky: " + this.getBukkitEntity().getLocation().getBlock().getLightFromSky()); ++ */ // TODO remove debug ++ + if (this.getHealth() != this.lastHealthSent || this.lastFoodSent != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastSentSaturationZero) { + this.playerConnection.sendPacket(new PacketPlayOutUpdateHealth(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit + this.lastHealthSent = this.getHealth(); +diff --git a/src/main/java/net/minecraft/server/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java +index 1aa070db60f5473576fb5d056cadde5106766489..24e6f3141ff4434f770e956a8d240bf856442933 100644 +--- a/src/main/java/net/minecraft/server/EnumDirection.java ++++ b/src/main/java/net/minecraft/server/EnumDirection.java +@@ -160,8 +160,8 @@ public enum EnumDirection implements INamable { + return EnumDirection.q[MathHelper.a(i % EnumDirection.q.length)]; + } + +- @Nullable +- public static EnumDirection a(int i, int j, int k) { ++ @Nullable public static EnumDirection from(int i, int j, int k) { return a(i, j, k); } // Tuinity - OBFHELPER ++ @Nullable public static EnumDirection a(int i, int j, int k) { + return (EnumDirection) EnumDirection.r.get(BlockPosition.a(i, j, k)); + } + +diff --git a/src/main/java/net/minecraft/server/IChunkAccess.java b/src/main/java/net/minecraft/server/IChunkAccess.java +index 180b6b58dc5663158db84b6f1257591439b48c31..564703d87c47eab34a23cb5d159cc66cdbfc00a3 100644 +--- a/src/main/java/net/minecraft/server/IChunkAccess.java ++++ b/src/main/java/net/minecraft/server/IChunkAccess.java +@@ -24,6 +24,36 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess { + } + // Paper end + ++ // Tuinity start ++ default com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ default void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ ++ default com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ default void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ public default boolean[] getSkyEmptinessMap() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ public default void setSkyEmptinessMap(final boolean[] emptinessMap) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ ++ public default boolean[] getBlockEmptinessMap() { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ ++ public default void setBlockEmptinessMap(final boolean[] emptinessMap) { ++ throw new UnsupportedOperationException(this.getClass().getName()); ++ } ++ // Tuinity end ++ + IBlockData getType(final int x, final int y, final int z); // Paper + @Nullable + IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag); +@@ -122,6 +152,7 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess { + @Nullable + NBTTagCompound j(BlockPosition blockposition); + ++ default Stream getLightSources() { return this.m(); } // Tuinity - OBFHELPER + Stream m(); + + TickList n(); +@@ -142,6 +173,7 @@ public interface IChunkAccess extends IBlockAccess, IStructureAccess { + return ashortlist[i]; + } + ++ default boolean isLit() { return this.r(); } // Tuinity - OBFHELPER + boolean r(); + + void b(boolean flag); +diff --git a/src/main/java/net/minecraft/server/ILightAccess.java b/src/main/java/net/minecraft/server/ILightAccess.java +index be5384ee41290b24b0c419c3e8f4553db34b2399..df28f7a6bf4c650a22ddf046eae4d5e8ca5879a9 100644 +--- a/src/main/java/net/minecraft/server/ILightAccess.java ++++ b/src/main/java/net/minecraft/server/ILightAccess.java +@@ -4,9 +4,10 @@ import javax.annotation.Nullable; + + public interface ILightAccess { + +- @Nullable +- IBlockAccess c(int i, int j); ++ default @Nullable IBlockAccess getFeaturesReadyChunk(int i, int j) { return this.c(i, j); } // Tuinity - OBFHELPER ++ @Nullable IBlockAccess c(int i, int j); + ++ default void markLightSectionDirty(EnumSkyBlock enumskyblock, SectionPosition sectionposition) { this.a(enumskyblock, sectionposition); } // Tuinity - OBFHELPER + default void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition) {} + + IBlockAccess getWorld(); +diff --git a/src/main/java/net/minecraft/server/LightEngineThreaded.java b/src/main/java/net/minecraft/server/LightEngineThreaded.java +index 2f9c97dd4e1d705a87772d18c7ab4883a876af08..168fe23177dfaa401396c1e460f56273ee0a59e4 100644 +--- a/src/main/java/net/minecraft/server/LightEngineThreaded.java ++++ b/src/main/java/net/minecraft/server/LightEngineThreaded.java +@@ -2,6 +2,11 @@ package net.minecraft.server; + + import com.mojang.datafixers.util.Pair; + import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper ++// Tuinity start ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++// Tuinity end + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectList; + import it.unimi.dsi.fastutil.objects.ObjectListIterator; +@@ -15,11 +20,12 @@ import org.apache.logging.log4j.Logger; + public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + private static final Logger LOGGER = LogManager.getLogger(); +- private final ThreadedMailbox b; ++ private final ThreadedMailbox b; private ThreadedMailbox getExecutor() { return this.b; } // Tuinity - OBFHELPER + // Paper start + private static final int MAX_PRIORITIES = PlayerChunkMap.GOLDEN_TICKET + 2; + + private boolean isChunkLightStatus(long pair) { ++ if (true) return true; // Tuinity - viewing ticket levels async can result in the viewing of transient levels, and LIGHT ticket isn't guaranteed to exist for all loading chunks thanks to really dumb unloading behaviors with the chunk system + PlayerChunk playerChunk = playerChunkMap.getVisibleChunk(pair); + if (playerChunk == null) { + return false; +@@ -156,13 +162,218 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + private volatile int f = 5; + private final AtomicBoolean g = new AtomicBoolean(); + ++ // Tuinity start - replace light engine impl ++ protected final com.tuinity.tuinity.chunk.light.StarLightInterface theLightEngine; ++ public final boolean hasBlockLight; ++ public final boolean hasSkyLight; ++ // Tuinity end - replace light engine impl ++ + public LightEngineThreaded(ILightAccess ilightaccess, PlayerChunkMap playerchunkmap, boolean flag, ThreadedMailbox threadedmailbox, Mailbox> mailbox) { + super(ilightaccess, true, flag); + this.d = playerchunkmap; this.playerChunkMap = d; // Paper + this.e = mailbox; + this.b = threadedmailbox; ++ // Tuinity start - replace light engine impl ++ this.hasBlockLight = true; ++ this.hasSkyLight = flag; ++ this.theLightEngine = com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? new com.tuinity.tuinity.chunk.light.StarLightInterface(ilightaccess, flag, true) : null; ++ // Tuinity end - replace light engine impl ++ } ++ ++ // Tuinity start - replace light engine impl ++ protected final IChunkAccess getChunk(final int chunkX, final int chunkZ) { ++ final WorldServer world = (WorldServer)this.theLightEngine.getWorld(); ++ return world.getChunkProvider().getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ protected long relightCounter; ++ ++ public int relight(java.util.Set chunks_param, ++ java.util.function.Consumer chunkLightCallback, ++ java.util.function.IntConsumer onComplete) { ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ throw new IllegalStateException("Must only be called on the main thread"); ++ } ++ ++ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); ++ // add tickets ++ java.util.Map ticketIds = new java.util.HashMap<>(); ++ int totalChunks = 0; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ final ChunkCoordIntPair chunkPos = iterator.next(); ++ ++ final IChunkAccess chunk = this.theLightEngine.getWorld().getChunkProvider().getChunkAtImmediately(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLit() || !chunk.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ final Long id = Long.valueOf(this.relightCounter++); ++ ++ this.theLightEngine.getWorld().getChunkProvider().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); ++ ticketIds.put(chunkPos, id); ++ ++ ++totalChunks; ++ } ++ ++ this.getExecutor().queue(() -> { ++ this.theLightEngine.relightChunks(chunks, (ChunkCoordIntPair chunkPos) -> { ++ chunkLightCallback.accept(chunkPos); ++ ((java.util.concurrent.Executor)this.theLightEngine.getWorld().getChunkProvider().serverThreadQueue).execute(() -> { ++ this.theLightEngine.getWorld().getChunkProvider().playerChunkMap.getUpdatingChunk(chunkPos.pair()).sendPacketToTrackedPlayers(new PacketPlayOutLightUpdate(chunkPos, this, true), false); ++ this.theLightEngine.getWorld().getChunkProvider().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); ++ }); ++ }, onComplete); ++ }); ++ this.queueUpdate(); ++ ++ return totalChunks; ++ } ++ ++ protected final Long2IntOpenHashMap holdingChunks = new Long2IntOpenHashMap(); ++ protected final LongArrayList postWorkTicketRelease = new LongArrayList(); ++ ++ private void addLightWorkTicket(int chunkX, int chunkZ) { ++ final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final int current = this.holdingChunks.putIfAbsent(coordinate, 1); ++ if (current == 0) { ++ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate); ++ this.theLightEngine.getWorld().getChunkProvider().addTicketAtLevel(TicketType.LIGHT_UPDATE, chunkPos, ++ MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), chunkPos); ++ this.theLightEngine.getWorld().getChunkProvider().tickDistanceManager(); ++ } else { ++ this.holdingChunks.put(coordinate, current + 1); ++ } ++ } ++ ++ protected final void releaseLightWorkChunk(int chunkX, int chunkZ) { ++ final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ); ++ final int current = this.holdingChunks.get(coordinate); ++ if (current == 1) { ++ this.holdingChunks.remove(coordinate); ++ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate); ++ this.theLightEngine.getWorld().getChunkProvider().removeTicketAtLevel(TicketType.LIGHT_UPDATE, chunkPos, ++ MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), chunkPos); ++ } else { ++ this.holdingChunks.put(coordinate, current - 1); ++ } ++ } ++ ++ protected final CompletableFuture acquireLightWorkChunk(int chunkX, int chunkZ) { ++ ChunkProviderServer chunkProvider = this.theLightEngine.getWorld().getChunkProvider(); ++ PlayerChunkMap chunkMap = chunkProvider.playerChunkMap; ++ int targetLevel = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); ++ ++ this.addLightWorkTicket(chunkX, chunkZ); ++ ++ // light doesn't always load one radius neighbours... ++ // i.e if they get unloaded ++ boolean neighboursAtFeatures = true; ++ int targetNeighbourLevel = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT.getPreviousStatus()); ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ PlayerChunk neighbour = chunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(dx + chunkX, dz + chunkZ)); ++ ChunkStatus status; ++ if (neighbour == null || neighbour.getTicketLevel() > targetNeighbourLevel || ++ (status = neighbour.getChunkHolderStatus()) == null || ++ !status.isAtLeastStatus(ChunkStatus.LIGHT.getPreviousStatus())) { ++ neighboursAtFeatures = false; ++ break; ++ } ++ } ++ } ++ ++ PlayerChunk playerChunk = chunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ ChunkStatus holderStatus; ++ if (!neighboursAtFeatures || playerChunk == null || playerChunk.getTicketLevel() > targetLevel || ++ (holderStatus = playerChunk.getChunkHolderStatus()) == null || ++ !holderStatus.isAtLeastStatus(ChunkStatus.LIGHT)) { ++ CompletableFuture ret = new CompletableFuture<>(); ++ ++ int[] loads = new int[1]; ++ int requiredLoads = 3 * 3; ++ java.util.function.Consumer onLoad = (chunk) -> { ++ if (++loads[0] == requiredLoads) { ++ ret.complete(this.getChunk(chunkX, chunkZ)); ++ } ++ }; ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ chunkProvider.getChunkAtAsynchronously(chunkX + dx, chunkZ + dz, ++ (dx | dz) == 0 ? ChunkStatus.LIGHT : ChunkStatus.LIGHT.getPreviousStatus(), ++ true, false, onLoad); ++ } ++ } ++ ++ return ret; ++ } ++ ++ return CompletableFuture.completedFuture(playerChunk.getAvailableChunkNow()); + } + ++ // note: task is discarded if the chunk is not at light status or if the chunk is not lit ++ protected final void scheduleLightWorkTask(int chunkX, int chunkZ, LightEngineThreaded.Update type, Runnable task) { ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ this.playerChunkMap.mainInvokingExecutor.execute(() -> { ++ this.scheduleLightWorkTask(chunkX, chunkZ, type, task); ++ }); ++ return; ++ } ++ ++ IChunkAccess current = this.getChunk(chunkX, chunkZ); ++ ++ if (current == null || !current.isLit() || !current.getChunkStatus().isAtLeastStatus(ChunkStatus.LIGHT)) { ++ return; ++ } ++ ++ this.acquireLightWorkChunk(chunkX, chunkZ).whenCompleteAsync((chunk, throwable) -> { ++ if (throwable != null) { ++ MinecraftServer.LOGGER.fatal("Failed to load light chunk for light work", throwable); ++ this.releaseLightWorkChunk(chunkX, chunkZ); ++ } else { ++ this.scheduleTask(chunkX, chunkZ, type, () -> { ++ try { ++ task.run(); ++ } finally { ++ this.postWorkTicketRelease.add(MCUtil.getCoordinateKey(chunkX, chunkZ)); ++ } ++ }); ++ } ++ }, this.playerChunkMap.mainInvokingExecutor); ++ } ++ ++ // override things from superclass ++ ++ @Override ++ public boolean a() { ++ return this.theLightEngine != null ? false : super.a(); ++ } ++ ++ @Override ++ public LightEngineLayerEventListener a(EnumSkyBlock var0) { ++ if (this.theLightEngine == null) { ++ return super.a(var0); ++ } ++ if (var0 == EnumSkyBlock.BLOCK) { ++ return this.theLightEngine.getBlockReader(); ++ } else { ++ return this.theLightEngine.getSkyReader(); ++ } ++ } ++ ++ @Override ++ public int b(BlockPosition var0, int var1) { ++ if (this.theLightEngine == null) { ++ return super.b(var0, var1); ++ } ++ int var2 = this.theLightEngine.getSkyReader().b(var0) - var1; ++ int var3 = this.theLightEngine.getBlockReader().b(var0); ++ return Math.max(var3, var2); ++ } ++ // Tuinity end - replace light engine impl ++ + public void close() {} + + @Override +@@ -179,6 +390,15 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + public void a(BlockPosition blockposition) { + BlockPosition blockposition1 = blockposition.immutableCopy(); + ++ // Tuinity start - replace light engine impl ++ if (this.theLightEngine != null) { ++ this.scheduleLightWorkTask(blockposition1.getX() >> 4, blockposition1.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, () -> { ++ this.theLightEngine.blockChange(blockposition1); ++ }); ++ return; ++ } ++ // Tuinity start - replace light engine impl ++ + this.a(blockposition.getX() >> 4, blockposition.getZ() >> 4, LightEngineThreaded.Update.POST_UPDATE, SystemUtils.a(() -> { + super.a(blockposition1); + }, () -> { +@@ -187,6 +407,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + } + + protected void a(ChunkCoordIntPair chunkcoordintpair) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -211,6 +436,14 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void a(SectionPosition sectionposition, boolean flag) { ++ // Tuinity start - replace light engine impl ++ if (this.theLightEngine != null) { ++ this.scheduleLightWorkTask(sectionposition.getX(), sectionposition.getZ(), LightEngineThreaded.Update.POST_UPDATE, () -> { ++ this.theLightEngine.sectionChange(sectionposition, flag); ++ }); ++ return; ++ } ++ // Tuinity start - replace light engine impl + this.a(sectionposition.a(), sectionposition.c(), () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -222,6 +455,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void a(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { + super.a(chunkcoordintpair, flag); + }, () -> { +@@ -231,6 +469,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void a(EnumSkyBlock enumskyblock, SectionPosition sectionposition, @Nullable NibbleArray nibblearray, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(sectionposition.a(), sectionposition.c(), () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -240,6 +483,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + })); + } + ++ private void scheduleTask(int x, int z, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { this.a(x, z, lightenginethreaded_update, runnable); } // Tuinity - OBFHELPER + private void a(int i, int j, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { + this.a(i, j, this.d.c(ChunkCoordIntPair.pair(i, j)), lightenginethreaded_update, runnable); + } +@@ -252,6 +496,11 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + + @Override + public void b(ChunkCoordIntPair chunkcoordintpair, boolean flag) { ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ return; ++ } ++ // Tuinity end - replace light impl + this.a(chunkcoordintpair.x, chunkcoordintpair.z, () -> { + return 0; + }, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { +@@ -277,6 +526,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + return; + } + // Paper end ++ if (this.theLightEngine == null) { // Tuinity - replace light engine impl + ChunkSection[] achunksection = ichunkaccess.getSections(); + + for (int i = 0; i < 16; ++i) { +@@ -293,16 +543,29 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + super.a(blockposition, ichunkaccess.g(blockposition)); + }); + } ++ } else { // Tuinity start - replace light engine impl ++ Boolean[] emptySections = com.tuinity.tuinity.chunk.light.StarLightEngine.getEmptySectionsForChunk(ichunkaccess); ++ if (!flag) { ++ this.theLightEngine.lightChunk(ichunkaccess, emptySections); ++ } else { ++ this.theLightEngine.forceLoadInChunk(ichunkaccess, emptySections); ++ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have ++ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should ++ // catch what we miss here. ++ this.theLightEngine.checkChunkEdges(chunkcoordintpair.x, chunkcoordintpair.z); ++ } ++ ++ } // Tuinity end - replace light engine impl + + // this.d.c(chunkcoordintpair); // Paper - move into post task below + }, () -> { + return "lightChunk " + chunkcoordintpair + " " + flag; + // Paper start - merge the 2 together + }), () -> { +- this.d.c(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done ++ this.d.c(chunkcoordintpair); // Paper - release light tickets as post task to ensure they stay loaded until fully done // Tuinity - diff on change, copied to top of method for early return if the chunk is already lit + if (skippedPre[0]) return; // Paper - future's already complete + ichunkaccess.b(true); +- super.b(chunkcoordintpair, false); ++ if (this.theLightEngine == null) super.b(chunkcoordintpair, false); // Tuinity - replace light engine impl + // Paper start + future.complete(ichunkaccess); + }); +@@ -311,7 +574,7 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + } + + public void queueUpdate() { +- if ((!this.queue.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { // Paper ++ if ((!this.queue.isEmpty() || (this.theLightEngine == null && super.a())) && this.g.compareAndSet(false, true)) { // Paper // Tuinity - replace light impl + this.b.a((() -> { // Paper - decompile error + this.b(); + this.g.set(false); +@@ -325,17 +588,36 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + private final java.util.List pre = new java.util.ArrayList<>(); + private final java.util.List post = new java.util.ArrayList<>(); + private void b() { ++ //final long start = System.nanoTime(); // TODO remove debug + if (queue.poll(pre, post)) { + pre.forEach(Runnable::run); + pre.clear(); +- super.a(Integer.MAX_VALUE, true, true); ++ if (this.theLightEngine == null) super.a(Integer.MAX_VALUE, true, true); // Tuinity - replace light impl + post.forEach(Runnable::run); + post.clear(); + } else { + // might have level updates to go still +- super.a(Integer.MAX_VALUE, true, true); ++ if (this.theLightEngine == null) super.a(Integer.MAX_VALUE, true, true); // Tuinity - replace light impl ++ } ++ // Tuinity start - replace light impl ++ if (this.theLightEngine != null) { ++ this.theLightEngine.propagateChanges(); ++ if (!this.postWorkTicketRelease.isEmpty()) { ++ LongArrayList copy = this.postWorkTicketRelease.clone(); ++ this.postWorkTicketRelease.clear(); ++ this.playerChunkMap.mainInvokingExecutor.execute(() -> { ++ LongIterator iterator = copy.iterator(); ++ while (iterator.hasNext()) { ++ long coordinate = iterator.nextLong(); ++ this.releaseLightWorkChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate)); ++ } ++ }); ++ } + } ++ // Tuinity end - replace light impl + // Paper end ++ //final long end = System.nanoTime(); // TODO remove debug ++ //System.out.println("Block updates took " + (end - start) * 1.0e-6 + "ms"); // TODO remove debug + } + + public void a(int i) { +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +index 4085426af03f032cf405bdfd1e40a8e5dc27c1d1..348d16ddec3b4da0b6b4e4f49916b966005b5259 100644 +--- a/src/main/java/net/minecraft/server/NibbleArray.java ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -56,6 +56,7 @@ public class NibbleArray { + boolean poolSafe = false; + public java.lang.Runnable cleaner; + private void registerCleaner() { ++ if (com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) return; // Tuinity - purge cleaner usage + if (!poolSafe) { + cleaner = MCUtil.registerCleaner(this, this.a, NibbleArray::releaseBytes); + } else { +@@ -63,7 +64,7 @@ public class NibbleArray { + } + } + // Paper end +- @Nullable protected byte[] a; ++ @Nullable protected byte[] a; public final byte[] justGiveMeTheFuckingByteArrayNoCleanerBullshitJesusFuckingChrist() { return this.a; } + + + public NibbleArray() {} +@@ -74,7 +75,7 @@ public class NibbleArray { + } + public NibbleArray(byte[] abyte, boolean isSafe) { + this.a = abyte; +- if (!isSafe) this.a = getCloneIfSet(); // Paper - clone for safety ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && !isSafe) this.a = getCloneIfSet(); // Paper - clone for safety // Tuinity - no need to clone + registerCleaner(); + // Paper end + if (abyte.length != 2048) { +@@ -162,7 +163,7 @@ public class NibbleArray { + + public NibbleArray copy() { return this.b(); } // Paper - OBFHELPER + public NibbleArray b() { +- return this.a == null ? new NibbleArray() : new NibbleArray(this.a); // Paper - clone in ctor ++ return this.a == null ? new NibbleArray() : new NibbleArray(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? this.a.clone() : this.a); // Paper - clone in ctor // Tuinity - no longer clone in constructor + } + + public String toString() { +diff --git a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +index a22f0cccecc85b4e4fe4603bcfa213f15c23db69..6cc4a035c8b1312b59685b20039d5e82bb1e1a3e 100644 +--- a/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java ++++ b/src/main/java/net/minecraft/server/PacketPlayOutLightUpdate.java +@@ -26,12 +26,12 @@ public class PacketPlayOutLightUpdate implements Packet { + + @Override + public void onPacketDispatch(EntityPlayer player) { +- remainingSends.incrementAndGet(); ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) remainingSends.incrementAndGet(); + } + + @Override + public void onPacketDispatchFinish(EntityPlayer player, ChannelFuture future) { +- if (remainingSends.decrementAndGet() <= 0) { ++ if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine && remainingSends.decrementAndGet() <= 0) { + // incase of any race conditions, schedule this delayed + MCUtil.scheduleTask(5, () -> { + if (remainingSends.get() == 0) { +@@ -44,7 +44,7 @@ public class PacketPlayOutLightUpdate implements Packet { + + @Override + public boolean hasFinishListener() { +- return true; ++ return !com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine; // Tuinity - replace light impl + } + + // Paper end +@@ -54,8 +54,8 @@ public class PacketPlayOutLightUpdate implements Packet { + this.a = chunkcoordintpair.x; + this.b = chunkcoordintpair.z; + this.i = flag; +- this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper +- this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper ++ this.g = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage ++ this.h = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage + + for (int i = 0; i < 18; ++i) { + NibbleArray nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + i)); +@@ -66,7 +66,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.e |= 1 << i; + } else { + this.c |= 1 << i; +- this.g.add(nibblearray.getCloneIfSet()); // Paper ++ this.g.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again + } + } + +@@ -75,7 +75,7 @@ public class PacketPlayOutLightUpdate implements Packet { + this.f |= 1 << i; + } else { + this.d |= 1 << i; +- this.h.add(nibblearray1.getCloneIfSet()); // Paper ++ this.h.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray1.asBytes() : nibblearray1.getCloneIfSet()); // Paper // Tuinity - don't clone again + } + } + } +@@ -88,8 +88,8 @@ public class PacketPlayOutLightUpdate implements Packet { + this.i = flag; + this.c = i; + this.d = j; +- this.g = Lists.newArrayList();cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper +- this.h = Lists.newArrayList();cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper ++ this.g = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner1 = MCUtil.registerListCleaner(this, this.g, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage ++ this.h = Lists.newArrayList();if (!com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine) cleaner2 = MCUtil.registerListCleaner(this, this.h, NibbleArray::releaseBytes); // Paper // Tuinity - purge cleaner usage + + for (int k = 0; k < 18; ++k) { + NibbleArray nibblearray; +@@ -97,7 +97,7 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.c & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.SKY).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.g.add(nibblearray.getCloneIfSet()); // Paper ++ this.g.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again + } else { + this.c &= ~(1 << k); + if (nibblearray != null) { +@@ -109,7 +109,7 @@ public class PacketPlayOutLightUpdate implements Packet { + if ((this.d & 1 << k) != 0) { + nibblearray = lightengine.a(EnumSkyBlock.BLOCK).a(SectionPosition.a(chunkcoordintpair, -1 + k)); + if (nibblearray != null && !nibblearray.c()) { +- this.h.add(nibblearray.getCloneIfSet()); // Paper ++ this.h.add(com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? nibblearray.asBytes() : nibblearray.getCloneIfSet()); // Paper // Tuinity - don't clone again + } else { + this.d &= ~(1 << k); + if (nibblearray != null) { +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 8b4ab23563a9a0c047267143dc3c6c5545d6c125..ac82f1791ce07e9a23cf97ca34974ab25e26be46 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -383,13 +383,14 @@ public class PlayerChunk { + + public void a(EnumSkyBlock enumskyblock, int i) { + Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance ++ if (this.getAvailableChunkNow() != null) this.getAvailableChunkNow().setNeedsSaving(true); // Tuinity - always mark as needing saving + + if (chunk != null) { + chunk.setNeedsSaving(true); + if (enumskyblock == EnumSkyBlock.SKY) { +- this.s |= 1 << i - -1; ++ this.s |= 1 << (i - -1); // Tuinity - fix mojang oopsie + } else { +- this.r |= 1 << i - -1; ++ this.r |= 1 << (i - -1); // Tuinity - fix mojang oopsie + } + + } +diff --git a/src/main/java/net/minecraft/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java +index a3ac883500eaebb353ad3108a17b5c740e384b03..d8b759874545293764690b2ba08a4bd7605c76ae 100644 +--- a/src/main/java/net/minecraft/server/ProtoChunk.java ++++ b/src/main/java/net/minecraft/server/ProtoChunk.java +@@ -48,6 +48,54 @@ public class ProtoChunk implements IChunkAccess { + private volatile boolean u; + final World world; // Paper - Anti-Xray - Add world // Paper - private -> default + ++ // Tuinity start - rewrite light engine ++ protected volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] blockNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(); ++ protected volatile com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] skyNibbles = com.tuinity.tuinity.chunk.light.StarLightEngine.getFilledEmptyLight(); ++ protected volatile boolean[] skyEmptinessMap; ++ protected volatile boolean[] blockEmptinessMap; ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.blockNibbles; ++ } ++ ++ @Override ++ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.blockNibbles = nibbles; ++ } ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.skyNibbles; ++ } ++ ++ @Override ++ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.skyNibbles = nibbles; ++ } ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return this.skyEmptinessMap; ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(boolean[] emptinessMap) { ++ this.skyEmptinessMap = emptinessMap; ++ } ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return this.blockEmptinessMap; ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(boolean[] emptinessMap) { ++ this.blockEmptinessMap = emptinessMap; ++ } ++ ++ // Tuinity end - rewrite light engine ++ + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { this(chunkcoordintpair, chunkconverter, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, World world) { +@@ -173,7 +221,7 @@ public class ProtoChunk implements IChunkAccess { + ChunkSection chunksection = this.a(j >> 4); + IBlockData iblockdata1 = chunksection.setType(i & 15, j & 15, k & 15, iblockdata); + +- if (this.g.b(ChunkStatus.FEATURES) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) { ++ if ((com.tuinity.tuinity.config.TuinityConfig.useNewLightEngine ? (this.g.b(ChunkStatus.LIGHT) && this.isLit()) : (this.g.b(ChunkStatus.FEATURES))) && iblockdata != iblockdata1 && (iblockdata.b((IBlockAccess) this, blockposition) != iblockdata1.b((IBlockAccess) this, blockposition) || iblockdata.f() != iblockdata1.f() || iblockdata.e() || iblockdata1.e())) { // Tuinity - move block updates to only happen after lighting occurs + LightEngine lightengine = this.e(); + + lightengine.a(blockposition); +diff --git a/src/main/java/net/minecraft/server/ProtoChunkExtension.java b/src/main/java/net/minecraft/server/ProtoChunkExtension.java +index 300cbb8b01d94e7eb0cded0c8e118103c416d4b6..2d7c86c9a323fb7294607ee5685cef8f9ef52794 100644 +--- a/src/main/java/net/minecraft/server/ProtoChunkExtension.java ++++ b/src/main/java/net/minecraft/server/ProtoChunkExtension.java +@@ -8,7 +8,50 @@ import javax.annotation.Nullable; + + public class ProtoChunkExtension extends ProtoChunk { + +- private final Chunk a; ++ private final Chunk a; public final Chunk getWrappedChunk() { return this.a; } // Tuinity - OBFHELPER ++ ++ // Tuinity start - rewrite light engine ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.getWrappedChunk().getBlockNibbles(); ++ } ++ ++ @Override ++ public void setBlockNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.getWrappedChunk().setBlockNibbles(nibbles); ++ } ++ ++ @Override ++ public com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.getWrappedChunk().getSkyNibbles(); ++ } ++ ++ @Override ++ public void setSkyNibbles(com.tuinity.tuinity.chunk.light.SWMRNibbleArray[] nibbles) { ++ this.getWrappedChunk().setSkyNibbles(nibbles); ++ } ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return this.getWrappedChunk().getSkyEmptinessMap(); ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) { ++ this.getWrappedChunk().setSkyEmptinessMap(emptinessMap); ++ } ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return this.getWrappedChunk().getBlockEmptinessMap(); ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(boolean[] emptinessMap) { ++ this.getWrappedChunk().setBlockEmptinessMap(emptinessMap); ++ } ++ ++ // Tuinity end - rewrite light engine + + public ProtoChunkExtension(Chunk chunk) { + super(chunk.getPos(), ChunkConverter.a, chunk.world); // Paper - Anti-Xray - Add parameter +diff --git a/src/main/java/net/minecraft/server/SectionPosition.java b/src/main/java/net/minecraft/server/SectionPosition.java +index f95925f1c5d091f1a129d0437bb6e175c6ac080f..0bb3ad0bffc04eba38cd827eaf5c63e8bf2aee93 100644 +--- a/src/main/java/net/minecraft/server/SectionPosition.java ++++ b/src/main/java/net/minecraft/server/SectionPosition.java +@@ -7,7 +7,7 @@ import java.util.stream.StreamSupport; + + public class SectionPosition extends BaseBlockPosition { + +- private SectionPosition(int i, int j, int k) { ++ public SectionPosition(int i, int j, int k) { // Tuinity - private -> public + super(i, j, k); + } + +diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java +index 25cff70b45aa2c92a9ffb2cd968ffef5bb1a6c2f..3c964f26592fc84bb5fc11c60808d11c65d93b16 100644 +--- a/src/main/java/net/minecraft/server/TicketType.java ++++ b/src/main/java/net/minecraft/server/TicketType.java +@@ -28,6 +28,8 @@ public class TicketType { + public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper + public static final TicketType DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads + public static final TicketType REQUIRED_LOAD = a("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail ++ public static final TicketType LIGHT_UPDATE = a("light_update", Comparator.comparingLong(ChunkCoordIntPair::pair)); // Tuinity - ensure chunks stay loaded for lighting ++ public static final TicketType CHUNK_RELIGHT = a("chunk_relight", Long::compareTo); // Tuinity - ensure chunk stays loaded for relighting + + // Tuinity start - delay chunk unloads + boolean delayUnloadViable = true; +@@ -37,6 +39,7 @@ public class TicketType { + TicketType.PRIORITY.delayUnloadViable = false; + TicketType.URGENT.delayUnloadViable = false; + TicketType.DELAYED_UNLOAD.delayUnloadViable = false; ++ TicketType.LIGHT_UPDATE.delayUnloadViable = false; // Tuinity - ensure chunks stay loaded for lighting + } + // Tuinity end - delay chunk unloads + public static TicketType a(String s, Comparator comparator) { +diff --git a/src/main/java/net/minecraft/server/VoxelShape.java b/src/main/java/net/minecraft/server/VoxelShape.java +index 700660dd93b3090334bb3033d5f5fdd6ab684744..e3b72922e2dfad07f3452ec5ee2af379d968c52d 100644 +--- a/src/main/java/net/minecraft/server/VoxelShape.java ++++ b/src/main/java/net/minecraft/server/VoxelShape.java +@@ -57,6 +57,7 @@ public abstract class VoxelShape { + } + // Tuinity end - optimise multi-aabb shapes + ++ public final VoxelShape simplify() { return this.c(); } // Tuinity - OBFHELPER + public VoxelShape c() { + VoxelShape[] avoxelshape = new VoxelShape[]{VoxelShapes.a()}; + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index c0617e513d321439db3d5fd9517da1f38f1fbd88..5c3080fd3e14988db9901c4e8015cf6da7af28a3 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -304,6 +304,13 @@ public class WorldServer extends World implements GeneratorAccessSeed { + final List playersAffectingSpawning = new java.util.ArrayList<>(); + // Tuinity end - optimise checkDespawn + ++ // Tuinity start - rewrite light engine ++ /** ++ * Cannot be modified during light updates. ++ */ ++ public com.tuinity.tuinity.chunk.light.VariableBlockLightHandler customBlockLightHandlers; ++ // Tuinity end - rewrite light engine ++ + // Add env and gen to constructor, WorldData -> WorldDataServer + public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { + super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor diff --git a/patches/Tuinity/patches/server/0062-Optimise-WorldServer-notify.patch b/patches/Tuinity/patches/server/0062-Optimise-WorldServer-notify.patch new file mode 100644 index 00000000..d0aee038 --- /dev/null +++ b/patches/Tuinity/patches/server/0062-Optimise-WorldServer-notify.patch @@ -0,0 +1,285 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 9 Jul 2020 13:34:59 -0700 +Subject: [PATCH] Optimise WorldServer#notify + +Iterating over all of the navigators in the world is pretty expensive. +Instead, only iterate over navigators in the current region that are +eligible for repathing. + +diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java +index 55fa3911703f96cf1f97c82b19d8e2d0d220016b..b92ca4a6de01f3f86367fb8dfe3591b08a3e9218 100644 +--- a/src/main/java/net/minecraft/server/NavigationAbstract.java ++++ b/src/main/java/net/minecraft/server/NavigationAbstract.java +@@ -21,7 +21,7 @@ public abstract class NavigationAbstract { + protected long j; + protected double k; + protected float l; +- protected boolean m; ++ protected boolean m; protected final boolean needsPathRecalculation() { return this.m; } // Tuinity - OBFHELPER + protected long n; + protected PathfinderAbstract o; + private BlockPosition p; +@@ -30,6 +30,13 @@ public abstract class NavigationAbstract { + private final Pathfinder s; public Pathfinder getPathfinder() { return this.s; } // Paper - OBFHELPER + private boolean t; + ++ // Tuinity start ++ public boolean isViableForPathRecalculationChecking() { ++ return !this.needsPathRecalculation() && ++ (this.c != null && !this.c.c() && this.c.e() != 0); ++ } ++ // Tuinity end ++ + public NavigationAbstract(EntityInsentient entityinsentient, World world) { + this.g = Vec3D.ORIGIN; + this.h = BaseBlockPosition.ZERO; +@@ -393,7 +400,7 @@ public abstract class NavigationAbstract { + } + + public void b(BlockPosition blockposition) { +- if (this.c != null && !this.c.c() && this.c.e() != 0) { ++ if (this.c != null && !this.c.c() && this.c.e() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking() + PathPoint pathpoint = this.c.d(); + Vec3D vec3d = new Vec3D(((double) pathpoint.a + this.a.locX()) / 2.0D, ((double) pathpoint.b + this.a.locY()) / 2.0D, ((double) pathpoint.c + this.a.locZ()) / 2.0D); + +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index caaffea1b670ddfd20bf39cbd55da1c5cf561288..e136195f3e806c50492b89811e82940fd38bafa6 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -291,7 +291,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + // Tuinity start + public static enum RegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionDataCreator { +- ++ // Tuinity start - optimise notify() ++ PATHING_NAVIGATORS() { ++ @Override ++ public Object createData(com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager regionManager) { ++ return new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(true); ++ } ++ } ++ // Tuinity end - optimise notify() + ; + + @Override +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 5c3080fd3e14988db9901c4e8015cf6da7af28a3..6ff5ef6b710652f1c4fe6461ff230ee78988f623 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -864,6 +864,15 @@ public class WorldServer extends World implements GeneratorAccessSeed { + gameprofilerfiller.enter("checkDespawn"); + if (!entity.dead) { + entity.checkDespawn(); ++ // Tuinity start - optimise notify() ++ if (entity.inChunk && entity.valid) { ++ if (this.getChunkProvider().isInEntityTickingChunk(entity)) { ++ this.updateNavigatorsInRegion(entity); ++ } ++ } else { ++ this.removeNavigatorsFromData(entity); ++ } ++ // Tuinity end - optimise notify() + } + + gameprofilerfiller.exit(); +@@ -886,7 +895,14 @@ public class WorldServer extends World implements GeneratorAccessSeed { + this.removeEntityFromChunk(entity); + this.entitiesById.remove(entity.getId()); // Tuinity + this.unregisterEntity(entity); ++ } else if (entity.inChunk && entity.valid) { // Tuinity start - optimise notify() ++ if (this.getChunkProvider().isInEntityTickingChunk(entity)) { ++ this.updateNavigatorsInRegion(entity); ++ } ++ } else { ++ this.removeNavigatorsFromData(entity); + } ++ // Tuinity end - optimise notify() + + gameprofilerfiller.exit(); + } +@@ -1295,6 +1311,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + int i = MathHelper.floor(entity.locX() / 16.0D); + int j = Math.min(15, Math.max(0, MathHelper.floor(entity.locY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior + int k = MathHelper.floor(entity.locZ() / 16.0D); ++ // Tuinity start ++ int oldRegionX = entity.chunkX >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ int oldRegionZ = entity.chunkZ >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ int newRegionX = i >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ int newRegionZ = k >> com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.REGION_CHUNK_SIZE_SHIFT; ++ // Tuinity end + + if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) { + // Paper start - remove entity if its in a chunk more correctly. +@@ -1304,6 +1326,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } + // Paper end + ++ // Tuinity start ++ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { ++ this.removeNavigatorsFromData(entity); ++ } ++ // Tuinity end ++ + if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) { + this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY); + } +@@ -1317,6 +1345,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { + } else { + this.getChunkAt(i, k).a(entity); + } ++ // Tuinity start ++ if (entity.inChunk && (oldRegionX != newRegionX || oldRegionZ != newRegionZ)) { ++ this.addNavigatorsIfPathingToRegion(entity); ++ } ++ // Tuinity end + } + + this.getMethodProfiler().exit(); +@@ -1779,9 +1812,96 @@ public class WorldServer extends World implements GeneratorAccessSeed { + // Tuinity end + } + new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid ++ this.removeNavigatorsFromData(entity); // Tuinity - optimise notify() + entity.valid = false; // CraftBukkit + } + ++ // Tuinity start - optimise notify() ++ void removeNavigatorsIfNotPathingFromRegion(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ if (!((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) { ++ navigators.remove(((EntityDrowned)entity).navigationWater); ++ } ++ if (!((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) { ++ navigators.remove(((EntityDrowned)entity).navigationLand); ++ } ++ } else if (entity instanceof EntityInsentient) { ++ if (!((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ navigators.remove(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ } ++ ++ void removeNavigatorsFromData(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ navigators.remove(((EntityDrowned)entity).navigationWater); ++ navigators.remove(((EntityDrowned)entity).navigationLand); ++ } else if (entity instanceof EntityInsentient) { ++ navigators.remove(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ ++ void addNavigatorsIfPathingToRegion(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationWater); ++ } ++ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationLand); ++ } ++ } else if (entity instanceof EntityInsentient) { ++ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ } ++ ++ void updateNavigatorsInRegion(Entity entity) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ); ++ if (section != null) { ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getOrCreateData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ // Copied from above ++ if (entity instanceof EntityDrowned) { ++ if (((EntityDrowned)entity).navigationWater.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationWater); ++ } else { ++ navigators.remove(((EntityDrowned)entity).navigationWater); ++ } ++ if (((EntityDrowned)entity).navigationLand.isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityDrowned)entity).navigationLand); ++ } else { ++ navigators.remove(((EntityDrowned)entity).navigationLand); ++ } ++ } else if (entity instanceof EntityInsentient) { ++ if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ navigators.add(((EntityInsentient)entity).getNavigation()); ++ } else { ++ navigators.remove(((EntityInsentient)entity).getNavigation()); ++ } ++ } ++ } ++ } ++ // Tuinity end - optimise notify() ++ + private void registerEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot + // Paper start - don't double enqueue entity registration +@@ -1961,9 +2081,25 @@ public class WorldServer extends World implements GeneratorAccessSeed { + VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition); + + if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) { ++ // Tuinity start - optimise notify() ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkProvider().playerChunkMap.dataRegionManager.getRegion(blockposition.getX() >> 4, blockposition.getZ() >> 4); ++ if (region == null) { ++ return; ++ } ++ // Tuinity end - optimise notify() + boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper + // Tuinity start +- com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.navigatorsForIteration.iterator(); ++ // Tuinity start - optimise notify() ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator> sectionIterator = null; ++ try { ++ for (sectionIterator = region.getSections(); sectionIterator.hasNext();) { ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = sectionIterator.next(); ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators = (com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet)section.getData(PlayerChunkMap.RegionData.PATHING_NAVIGATORS); ++ if (navigators == null) { ++ continue; ++ } ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigators.iterator(); ++ // Tuinity end - optimise notify() + try { // Tuinity end + + while (iterator.hasNext()) { +@@ -1972,10 +2108,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (!navigationabstract.i()) { + navigationabstract.b(blockposition); + } ++ // Tuinity start - optimise notify() ++ if (!navigationabstract.isViableForPathRecalculationChecking()) { ++ navigators.remove(navigationabstract); ++ } ++ // Tuinity end - optimise notify() + } + } finally { // Tuinity start + iterator.finishedIterating(); + } // Tuinity end ++ } // Tuinity start - optimise notify() ++ } finally { ++ if (sectionIterator != null) { ++ sectionIterator.finishedIterating(); ++ } ++ } // Tuinity end - optimise notify() + + this.tickingEntities = wasTicking; // Paper + } diff --git a/patches/Tuinity/patches/server/0063-Actually-unload-POI-data.patch b/patches/Tuinity/patches/server/0063-Actually-unload-POI-data.patch new file mode 100644 index 00000000..0ffe211e --- /dev/null +++ b/patches/Tuinity/patches/server/0063-Actually-unload-POI-data.patch @@ -0,0 +1,359 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 31 Aug 2020 11:08:17 -0700 +Subject: [PATCH] Actually unload POI data + +While it's not likely for a poi data leak to be meaningful, +sometimes it is. + +This patch also prevents the saving/unloading of POI data when +world saving is disabled. + +diff --git a/src/main/java/net/minecraft/server/LightEngineGraphSection.java b/src/main/java/net/minecraft/server/LightEngineGraphSection.java +index 13d067f48647dea63ef1bf3a2a3e0868074ba75f..04afd7f285db2f281a038e0be6f557b8a692936b 100644 +--- a/src/main/java/net/minecraft/server/LightEngineGraphSection.java ++++ b/src/main/java/net/minecraft/server/LightEngineGraphSection.java +@@ -74,8 +74,10 @@ public abstract class LightEngineGraphSection extends LightEngineGraph { + return i == Long.MAX_VALUE ? this.b(j) : k + 1; + } + ++ public final int getSource(long coordinate) { return this.b(coordinate); } // Tuinity - OBFHELPER + protected abstract int b(long i); + ++ public final void update(long coordinate, int level, boolean flag) { this.b(coordinate, level, flag); } // Tuinity - OBFHELPER + public void b(long i, int j, boolean flag) { + this.a(Long.MAX_VALUE, i, j, flag); + } +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index e136195f3e806c50492b89811e82940fd38bafa6..16779ffa00caf32752170700e1d88092802fa932 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -838,6 +838,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + playerchunk = new PlayerChunk(new ChunkCoordIntPair(i), j, this.lightEngine, this.p, this); + this.dataRegionManager.addChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity + } ++ this.getVillagePlace().dequeueUnload(playerchunk.location.pair()); // Tuinity - unload POI data + + this.updatingChunks.put(i, playerchunk); + this.updatingChunksModified = true; +@@ -963,7 +964,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + } + +- private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more ++ static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more // Tuinity - private -> package private + + protected void unloadChunks(BooleanSupplier booleansupplier) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); +@@ -1114,6 +1115,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.worldLoadListener.a(ichunkaccess.getPos(), (ChunkStatus) null); + } + if (removed) this.dataRegionManager.removeChunk(playerchunk.location.x, playerchunk.location.z); // Tuinity ++ if (removed) this.getVillagePlace().queueUnload(playerchunk.location.pair(), MinecraftServer.currentTickLong + 1); // Tuinity - unload POI data + } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks + + } +@@ -1206,6 +1208,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + this.getVillagePlace().loadInData(chunkcoordintpair, chunkHolder.poiData); + chunkHolder.tasks.forEach(Runnable::run); ++ this.getVillagePlace().dequeueUnload(chunkcoordintpair.pair()); // Tuinity + // Paper end + + if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async +diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java +index d9362b74fda2ea937281f897fbc2cb501775a275..79a11d17a2822b192dec5981d0344ae689c3d385 100644 +--- a/src/main/java/net/minecraft/server/RegionFileSection.java ++++ b/src/main/java/net/minecraft/server/RegionFileSection.java +@@ -25,8 +25,8 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + + private static final Logger LOGGER = LogManager.getLogger(); + // Paper - nuke IOWorker +- private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); +- protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); // Paper - private -> protected ++ private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); protected final Long2ObjectMap> getDataBySection() { return this.c; } // Tuinity - OBFHELPER ++ protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); protected final LongLinkedOpenHashSet getDirtySections() { return this.d; } // Paper - private -> protected // Tuinity - OBFHELPER + private final Function> e; + private final Function f; + private final DataFixer g; +@@ -50,6 +50,40 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + + } + ++ // Tuinity start - actually unload POI data ++ public void unloadData(long coordinate) { ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate); ++ this.writeDirtyData(chunkPos); ++ ++ Long2ObjectMap> data = this.getDataBySection(); ++ int before = data.size(); ++ ++ for (int section = 0; section < 16; ++section) { ++ data.remove(SectionPosition.asLong(chunkPos.x, section, chunkPos.z)); ++ } ++ ++ if (before != data.size()) { ++ this.onUnload(coordinate); ++ } ++ } ++ ++ protected void onUnload(long coordinate) {} ++ ++ public boolean isEmpty(long coordinate) { ++ Long2ObjectMap> data = this.getDataBySection(); ++ int x = MCUtil.getCoordinateX(coordinate); ++ int z = MCUtil.getCoordinateZ(coordinate); ++ for (int section = 0; section < 16; ++section) { ++ Optional optional = data.get(SectionPosition.asLong(x, section, z)); ++ if (optional != null && optional.orElse(null) != null) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Tuinity end - actually unload POI data ++ + @Nullable protected Optional getIfLoaded(long value) { return this.c(value); } // Tuinity - OBFHELPER + @Nullable protected Optional c(long i) { // Tuinity - OBFHELPER + return (Optional) this.c.get(i); +@@ -150,6 +184,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + }); + } + } ++ if (this instanceof VillagePlace) { ((VillagePlace)this).queueUnload(chunkcoordintpair.pair(), MinecraftServer.currentTickLong + 1); } // Tuinity - unload POI data + + } + +@@ -221,6 +256,7 @@ public class RegionFileSection extends RegionFileCache implements AutoCloseab + return dynamic.get("DataVersion").asInt(1945); + } + ++ public final void writeDirtyData(ChunkCoordIntPair chunkcoordintpair) { this.a(chunkcoordintpair); } // Tuinity - OBFHELPER + public void a(ChunkCoordIntPair chunkcoordintpair) { + if (!this.d.isEmpty()) { + for (int i = 0; i < 16; ++i) { +diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java +index 0094babbd59cc81554b9480088464d632824ae8e..fce9967912628c232fe41ccc17fe2296f001ec61 100644 +--- a/src/main/java/net/minecraft/server/VillagePlace.java ++++ b/src/main/java/net/minecraft/server/VillagePlace.java +@@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Pair; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; + import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Tuinity + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import java.io.File; +@@ -22,8 +23,24 @@ import java.util.stream.Stream; + + public class VillagePlace extends RegionFileSection { + +- private final VillagePlace.a a = new VillagePlace.a(); +- private final LongSet b = new LongOpenHashSet(); ++ // Tuinity start - unload poi data ++ // the vanilla tracker needs to be replaced because it does not support level removes ++ private final com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D(); ++ static final int POI_DATA_SOURCE = 7; ++ public static int convertBetweenLevels(final int level) { ++ return POI_DATA_SOURCE - level; ++ } ++ ++ protected void updateDistanceTracking(long section) { ++ if (this.isSectionDistanceTrackerSource(section)) { ++ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); ++ } else { ++ this.villageDistanceTracker.removeSource(section); ++ } ++ } ++ // Tuinity end - unload poi data ++ ++ private final LongSet b = new LongOpenHashSet(); private final LongSet getLoadedChunks() { return this.b; } // Tuinity - OBFHELPER + + private final WorldServer world; // Paper + +@@ -34,9 +51,124 @@ public class VillagePlace extends RegionFileSection { + public VillagePlace(File file, DataFixer datafixer, boolean flag, WorldServer world) { + super(file, VillagePlaceSection::a, VillagePlaceSection::new, datafixer, DataFixTypes.POI_CHUNK, flag); + this.world = world; ++ if (world == null) { throw new IllegalStateException("world must be non-null"); }// Tuinity - require non-null + // Paper end - add world parameter + } + ++ // Tuinity start - actually unload POI data ++ private final java.util.TreeSet queuedUnloads = new java.util.TreeSet<>(); ++ private final Long2ObjectOpenHashMap queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>(); ++ ++ static final class QueuedUnload implements Comparable { ++ ++ private final long unloadTick; ++ private final long coordinate; ++ ++ public QueuedUnload(long unloadTick, long coordinate) { ++ this.unloadTick = unloadTick; ++ this.coordinate = coordinate; ++ } ++ ++ @Override ++ public int compareTo(QueuedUnload other) { ++ if (other.unloadTick == this.unloadTick) { ++ return Long.compare(this.coordinate, other.coordinate); ++ } else { ++ return Long.compare(this.unloadTick, other.unloadTick); ++ } ++ } ++ ++ @Override ++ public int hashCode() { ++ int hash = 1; ++ hash = hash * 31 + Long.hashCode(this.unloadTick); ++ hash = hash * 31 + Long.hashCode(this.coordinate); ++ return hash; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (obj == null || obj.getClass() != QueuedUnload.class) { ++ return false; ++ } ++ QueuedUnload other = (QueuedUnload)obj; ++ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate; ++ } ++ } ++ ++ long determineDelay(long coordinate) { ++ if (this.isEmpty(coordinate)) { ++ return 5 * 60 * 20; ++ } else { ++ return 60 * 20; ++ } ++ } ++ ++ public void queueUnload(long coordinate, long minTarget) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload queue"); ++ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate); ++ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload); ++ if (existing != null) { ++ this.queuedUnloads.remove(existing); ++ } ++ this.queuedUnloads.add(unload); ++ } ++ ++ public void dequeueUnload(long coordinate) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload dequeue"); ++ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate); ++ if (unload != null) { ++ this.queuedUnloads.remove(unload); ++ } ++ } ++ ++ public void pollUnloads(BooleanSupplier canSleepForTick) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload"); ++ long currentTick = MinecraftServer.currentTickLong; ++ ChunkProviderServer chunkProvider = this.world.getChunkProvider(); ++ PlayerChunkMap playerChunkMap = chunkProvider.playerChunkMap; ++ // copied target determination from PlayerChunkMap ++ int target = Math.min(this.queuedUnloads.size() - 100, (int) (this.queuedUnloads.size() * PlayerChunkMap.UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive ++ for (java.util.Iterator iterator = this.queuedUnloads.iterator(); ++ iterator.hasNext() && (this.queuedUnloads.size() > target || canSleepForTick.getAsBoolean());) { ++ QueuedUnload unload = iterator.next(); ++ if (unload.unloadTick > currentTick) { ++ break; ++ } ++ ++ long coordinate = unload.coordinate; ++ ++ iterator.remove(); ++ this.queuedUnloadsByCoordinate.remove(coordinate); ++ ++ if (playerChunkMap.getUnloadingPlayerChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate)) != null ++ || playerChunkMap.getUpdatingChunk(coordinate) != null) { ++ continue; ++ } ++ ++ this.unloadData(coordinate); ++ } ++ } ++ ++ @Override ++ public void unloadData(long coordinate) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async unloading poi data"); ++ super.unloadData(coordinate); ++ } ++ ++ @Override ++ protected void onUnload(long coordinate) { ++ com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload callback"); ++ this.getLoadedChunks().remove(coordinate); ++ int chunkX = MCUtil.getCoordinateX(coordinate); ++ int chunkZ = MCUtil.getCoordinateZ(coordinate); ++ for (int section = 0; section < 16; ++section) { ++ long sectionPos = SectionPosition.asLong(chunkX, section, chunkZ); ++ this.updateDistanceTracking(sectionPos); ++ } ++ } ++ // Tuinity end - actually unload POI data ++ + public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) { + ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition, villageplacetype); + } +@@ -140,10 +272,11 @@ public class VillagePlace extends RegionFileSection { + } + + public int a(SectionPosition sectionposition) { +- this.a.a(); +- return this.a.c(sectionposition.s()); ++ this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking util ++ return convertBetweenLevels(this.villageDistanceTracker.getLevel(MCUtil.getSectionKey(sectionposition))); // Tuinity - replace distance tracking util + } + ++ private boolean isSectionDistanceTrackerSource(long section) { return this.f(section); } // Tuinity - OBFHELPER + private boolean f(long i) { + Optional optional = this.c(i); + +@@ -159,7 +292,7 @@ public class VillagePlace extends RegionFileSection { + super.a(booleansupplier); + } else { + //super.a(booleansupplier); // re-implement below +- while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean()) { ++ while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean() && !this.world.isSavingDisabled()) { // Tuinity - unload POI data - don't write to disk if saving is disabled + ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).r(); + + NBTTagCompound data; +@@ -170,19 +303,24 @@ public class VillagePlace extends RegionFileSection { + chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority + } + } ++ // Tuinity start - unload POI data ++ if (!this.world.isSavingDisabled()) { // don't write to disk if saving is disabled ++ this.pollUnloads(booleansupplier); ++ } ++ // Tuinity end - unload POI data + // Paper end +- this.a.a(); ++ this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking until + } + + @Override + protected void a(long i) { + super.a(i); +- this.a.b(i, this.a.b(i), false); ++ this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util + } + + @Override + protected void b(long i) { +- this.a.b(i, this.a.b(i), false); ++ this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util + } + + public void a(ChunkCoordIntPair chunkcoordintpair, ChunkSection chunksection) { +@@ -247,7 +385,7 @@ public class VillagePlace extends RegionFileSection { + + @Override + protected int b(long i) { +- return VillagePlace.this.f(i) ? 0 : 7; ++ return VillagePlace.this.f(i) ? 0 : 7; // Tuinity - unload poi data - diff on change, this specifies the source level to use for distance tracking + } + + @Override diff --git a/patches/api/0002-Modify-POM.patch b/patches/api/0001-Modify-POM.patch similarity index 100% rename from patches/api/0002-Modify-POM.patch rename to patches/api/0001-Modify-POM.patch diff --git a/patches/api/0001-Yatopia-API-Bundle.patch b/patches/api/0001-Yatopia-API-Bundle.patch deleted file mode 100644 index 1f3daf7f..00000000 --- a/patches/api/0001-Yatopia-API-Bundle.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: YatopiaMC -Date: Sat, 1 Aug 2020 15:51:06 -0500 -Subject: [PATCH] Yatopia API Bundle - -Lagging threshold + Purpur & Rainforest & Origami config - -diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index fecd7b14d317f55eb1ce7b5c6af9913917971427..6df897ed32a94df4a06e1d5ac3d749e6a360ab2f 100644 ---- a/src/main/java/org/bukkit/Bukkit.java -+++ b/src/main/java/org/bukkit/Bukkit.java -@@ -1817,4 +1817,15 @@ public final class Bukkit { - public static Server.Spigot spigot() { - return server.spigot(); - } -+ -+ // Purpur start -+ /** -+ * Check if the server is lagging, according to the laggy threshold setting. -+ * -+ * @return true if lagging -+ */ -+ public static boolean isLagging() { -+ return server.isLagging(); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java -index 06bdfddb7b1acb7bb7b347ad1aa13bff0c823ab1..bd2723618272068bee0fb6be8d702c34cb44762b 100644 ---- a/src/main/java/org/bukkit/ChatColor.java -+++ b/src/main/java/org/bukkit/ChatColor.java -@@ -312,6 +312,7 @@ public enum ChatColor { - * @return Associative {@link org.bukkit.ChatColor} with the given id, - * or null if it doesn't exist - */ -+ @Nullable // Yatopia - public static ChatColor getById(int id) { - return BY_ID.get(id); - } -diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index b45ad8df8b7a44c9e6d12326e5ea85e8d166a16c..40d342ef1a618b7d85731b238b0344402e551251 100644 ---- a/src/main/java/org/bukkit/Server.java -+++ b/src/main/java/org/bukkit/Server.java -@@ -1490,6 +1490,58 @@ public interface Server extends PluginMessageRecipient { - } - // Tuinity end - add config to timings report - -+ // Akarin start -+ /** -+ * @deprecated yatopia does not import akarin config anymore -+ */ -+ @Deprecated -+ @NotNull -+ public org.bukkit.configuration.file.YamlConfiguration getAkarinConfig() -+ { -+ return new org.bukkit.configuration.file.YamlConfiguration(); -+ } -+ // Akarin end -+ -+ // Rainforest start -+ /** -+ * @deprecated yatopia does not import akarin config anymore -+ */ -+ @Deprecated -+ @NotNull -+ public org.bukkit.configuration.file.YamlConfiguration getRainforestConfig() -+ { -+ return new org.bukkit.configuration.file.YamlConfiguration(); -+ } -+ // Rainforest end -+ -+ // Purpur start -+ @NotNull -+ public org.bukkit.configuration.file.YamlConfiguration getPurpurConfig() -+ { -+ throw new UnsupportedOperationException("Not supported yet."); -+ } -+ -+ @NotNull -+ public java.util.Properties getServerProperties() -+ { -+ throw new UnsupportedOperationException("Not supported yet."); -+ } -+ // Purpur end -+ // Origami start - add config to timings report -+ @NotNull -+ public org.bukkit.configuration.file.YamlConfiguration getOrigamiConfig() -+ { -+ throw new UnsupportedOperationException("Not supported yet."); -+ } -+ // Origami end -+ // Yatopia start - add config to timings report -+ @NotNull -+ public org.bukkit.configuration.file.YamlConfiguration getYatopiaConfig() -+ { -+ throw new UnsupportedOperationException("Not supported yet."); -+ } -+ // Yatopia end -+ - /** - * Sends the component to the player - * -@@ -1590,4 +1642,13 @@ public interface Server extends PluginMessageRecipient { - @NotNull - com.destroystokyo.paper.entity.ai.MobGoals getMobGoals(); - // Paper end -+ -+ // Purpur start -+ /** -+ * Check if the server is lagging, according to the laggy threshold setting. -+ * -+ * @return true if lagging -+ */ -+ boolean isLagging(); -+ // Purpur end - } diff --git a/patches/api/0002-Yatopia-Config-Redirect-Config.patch b/patches/api/0002-Yatopia-Config-Redirect-Config.patch new file mode 100644 index 00000000..a8da9b19 --- /dev/null +++ b/patches/api/0002-Yatopia-Config-Redirect-Config.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: YatopiaMC +Date: Sun, 17 Jan 2021 15:37:52 -0600 +Subject: [PATCH] Yatopia Config & Redirect Config + + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 26c1953d23efd370ac7fd47fc3432edba4724139..5b15169e967803803164f4d3c7f6327f4e2de4a3 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1505,6 +1505,22 @@ public interface Server extends PluginMessageRecipient { + } + // Purpur end + ++ // Origami start - add config to timings report ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getOrigamiConfig() ++ { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Origami end ++ ++ // Yatopia start - add config to timings report ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getYatopiaConfig() ++ { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Yatopia end ++ + /** + * Sends the component to the player + * diff --git a/patches/api/0004-Add-last-tick-time-API.patch b/patches/api/0004-Add-last-tick-time-API.patch index cecb10c0..24bc429c 100644 --- a/patches/api/0004-Add-last-tick-time-API.patch +++ b/patches/api/0004-Add-last-tick-time-API.patch @@ -7,10 +7,10 @@ Original patch by: Co-authored-by: tr7zw diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 6df897ed32a94df4a06e1d5ac3d749e6a360ab2f..b08a5a91315f3cd80c4c4ef47f5fc6755cbf73d8 100644 +index ba8eb67291c9848b367419f4c8110161ac7fab0d..c3ffbb86d2e61c15e5cf7cd2c6b381c333c228ee 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -1828,4 +1828,14 @@ public final class Bukkit { +@@ -1837,4 +1837,14 @@ public final class Bukkit { return server.isLagging(); } // Purpur end @@ -26,10 +26,10 @@ index 6df897ed32a94df4a06e1d5ac3d749e6a360ab2f..b08a5a91315f3cd80c4c4ef47f5fc675 + // Yatopia end } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 40d342ef1a618b7d85731b238b0344402e551251..d6ba0895f259c15de9fb974a5c74da709158fc28 100644 +index 5b15169e967803803164f4d3c7f6327f4e2de4a3..10cab1b0939d54bcd2b351c2317baf5ce5e97684 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -1651,4 +1651,24 @@ public interface Server extends PluginMessageRecipient { +@@ -1637,4 +1637,24 @@ public interface Server extends PluginMessageRecipient { */ boolean isLagging(); // Purpur end diff --git a/patches/api/0005-Add-NBT-API-as-a-first-class-lib.patch b/patches/api/0005-Add-NBT-API-as-a-first-class-lib.patch index 55ac29ec..780643c8 100644 --- a/patches/api/0005-Add-NBT-API-as-a-first-class-lib.patch +++ b/patches/api/0005-Add-NBT-API-as-a-first-class-lib.patch @@ -98,13 +98,13 @@ index 3b10fcc13893403b29f0260b8605144679e89b82..1e9a96d8b08cc396acf73dc420830093 + // Yatopia end } diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 76e857c364fe79e20cf9bde54b65e5b7108174fd..dc7e9983b89726625acce95026b45695b3f387b6 100644 +index 08dbe8208fad174f03a0e08c26bb48a0729ec0ce..d83e9814878ff185e659dfaabc2175d1577db0e1 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -698,4 +698,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -746,4 +746,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ - public boolean isTicking(); - // Paper end + boolean isRidableInWater(); + // Purpur end + + // Yatopia start + /** @@ -129,13 +129,13 @@ index 76e857c364fe79e20cf9bde54b65e5b7108174fd..dc7e9983b89726625acce95026b45695 + // Yatopia end } diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 4f2520f7a4ca6d57a85924ada1068a055b9a01fb..9ac89fe309e5cb393bdda3a77f8313991ab77732 100644 +index 23cef1e67236a879525f39da994efc9a9c5cd289..a666626d9658f9a7f784514d34794a2ecb944ac9 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java -@@ -792,4 +792,42 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { - return itemMeta.hasItemFlag(flag); +@@ -1427,4 +1427,42 @@ public class ItemStack implements Cloneable, ConfigurationSerializable { } - // Paper end + + // Purpur end + + // Yatopia start + /** diff --git a/patches/server/0002-Modify-POM.patch b/patches/server/0001-Modify-POM.patch similarity index 89% rename from patches/server/0002-Modify-POM.patch rename to patches/server/0001-Modify-POM.patch index 21eb79b0..b52b2226 100644 --- a/patches/server/0002-Modify-POM.patch +++ b/patches/server/0001-Modify-POM.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Modify POM diff --git a/pom.xml b/pom.xml -index e83e4241a56fe131a75fe21cc1518992c089da2c..3c01daecc01583275b5007e259bddb62035fd361 100644 +index 752d62eb3b87ab24260ec2c029bae0d2b0e3b908..4f56aa4ae78b9d3756983cde52bc1d1adda0c9d4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,11 @@ @@ -23,7 +23,7 @@ index e83e4241a56fe131a75fe21cc1518992c089da2c..3c01daecc01583275b5007e259bddb62 -@@ -19,16 +19,25 @@ +@@ -19,17 +19,26 @@ @@ -46,14 +46,15 @@ index e83e4241a56fe131a75fe21cc1518992c089da2c..3c01daecc01583275b5007e259bddb62 + -- com.tuinity -- tuinity-api + +- net.pl3x.purpur +- purpur-api + org.yatopiamc + yatopia-api + ${project.version} compile - -@@ -159,6 +168,12 @@ +@@ -161,6 +170,12 @@ 1.1.0-SNAPSHOT compile @@ -66,7 +67,7 @@ index e83e4241a56fe131a75fe21cc1518992c089da2c..3c01daecc01583275b5007e259bddb62 -@@ -172,6 +187,20 @@ +@@ -174,6 +189,20 @@ spigotmc-public https://hub.spigotmc.org/nexus/content/groups/public/ @@ -87,7 +88,7 @@ index e83e4241a56fe131a75fe21cc1518992c089da2c..3c01daecc01583275b5007e259bddb62 -@@ -183,15 +212,15 @@ +@@ -185,15 +214,15 @@ diff --git a/patches/server/0001-Yatopia-Server-Fixes.patch b/patches/server/0001-Yatopia-Server-Fixes.patch deleted file mode 100644 index d7649b79..00000000 --- a/patches/server/0001-Yatopia-Server-Fixes.patch +++ /dev/null @@ -1,919 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: YatopiaMC -Date: Sun, 25 Oct 2020 12:23:35 -0500 -Subject: [PATCH] Yatopia-Server-Fixes - - -diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java -index fe7330fabe386966c2d203a190a00a785ea21be0..537456a7427cddd6783f5b5d8ee2d655668c4c53 100644 ---- a/src/main/java/de/minebench/origami/OrigamiConfig.java -+++ b/src/main/java/de/minebench/origami/OrigamiConfig.java -@@ -16,7 +16,7 @@ public final class OrigamiConfig { - private static final Object[] EMPTY = new Object[0]; - - private static File configFile; -- private static YamlConfiguration config; -+ public static YamlConfiguration config; // Yatopia - private static int configVersion; - - public static void init(final File file) { -@@ -112,6 +112,28 @@ public final class OrigamiConfig { - config.addDefault("worlds.default." + path, Double.valueOf(dfl)); - return config.getDouble("worlds." + worldName + "." + path, config.getDouble("worlds.default." + path, dfl)); - } -+ -+ public boolean tickEmptyHoppers = false; -+ public int fullHopperCooldown = 128; -+ private void hopperOptimizations() { -+ tickEmptyHoppers = getBoolean("tick-empty-hoppers", tickEmptyHoppers); -+ fullHopperCooldown = getInt("ticks-per.full-hopper-cooldown", fullHopperCooldown); -+ } -+ -+ public boolean fastFeatureSearchDontLoad = false; -+ private void fastFeatureSearchDontLoad() { -+ fastFeatureSearchDontLoad = getBoolean("fast-feature-search-dont-load", false); -+ } -+ -+ public boolean pigmenDontTargetUnlessHit = false; -+ private void pigmenDontTargetUnlessHit() { -+ pigmenDontTargetUnlessHit = getBoolean("pigmen.dont-target-unless-hit", pigmenDontTargetUnlessHit); -+ } -+ -+ public boolean disableObserverClocks = false; -+ private void observerClock() { -+ disableObserverClocks = getBoolean("disable-observer-clocks", disableObserverClocks); -+ } - } - - } -\ No newline at end of file -diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java.rej b/src/main/java/de/minebench/origami/OrigamiConfig.java.rej -deleted file mode 100644 -index 02f5ccf54210776770d3215afb7c8e82f750d57e..0000000000000000000000000000000000000000 ---- a/src/main/java/de/minebench/origami/OrigamiConfig.java.rej -+++ /dev/null -@@ -1,12 +0,0 @@ --diff a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java (rejected hunks) --@@ -153,6 +153,10 @@ public final class OrigamiConfig { -- public int getTickRate(String type, String typeName, String entityType, int def) { -- return tickRates.getOrDefault(type + "." + entityType + "." + typeName, tickRates.getOrDefault(type + "." + typeName, def)); -- } --+ public boolean pigmenDontTargetUnlessHit = false; --+ private void pigmenDontTargetUnlessHit() { --+ pigmenDontTargetUnlessHit = getBoolean("pigmen.dont-target-unless-hit", pigmenDontTargetUnlessHit); --+ } -- public int pigmenPortalSpawn = 2000; -- public double pigmenPortalBoost = 0.0; -- public boolean pigmenFarmsWaterAi = false; -diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java -index e811295b4d6afcd920f60e0ce5440e43300d9085..d1064bd1c76eb23ce12e4a0703ce9f2622952897 100644 ---- a/src/main/java/net/minecraft/server/BaseBlockPosition.java -+++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java -@@ -111,6 +111,7 @@ public class BaseBlockPosition implements Comparable { - return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), false) < d0 * d0; - } - -+ public final boolean distanceSquared(IPosition pos, double dist) { return a(pos, dist); } // Yatopia - OBFHELPER - public boolean a(IPosition iposition, double d0) { - return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0; - } -diff --git a/src/main/java/net/minecraft/server/Behavior.java b/src/main/java/net/minecraft/server/Behavior.java -index fb4a5ca220c9ef0916ecd4249a0ec7d1d8024ef1..899ca7bf676ccd874e317ba7e19b733fbf444baa 100644 ---- a/src/main/java/net/minecraft/server/Behavior.java -+++ b/src/main/java/net/minecraft/server/Behavior.java -@@ -11,6 +11,7 @@ public abstract class Behavior { - private long c; - private final int d; - private final int e; -+ co.aikar.timings.Timing timing; // Origami - behavior timing - - public Behavior(Map, MemoryStatus> map) { - this(map, 60); -@@ -25,6 +26,9 @@ public abstract class Behavior { - this.d = i; - this.e = j; - this.a = map; -+ // Origami start - behavior timing -+ timing = co.aikar.timings.WorldTimingsHandler.getBehaviorTimings(getClass().getSimpleName()); -+ // Origami end - } - - public Behavior.Status a() { -diff --git a/src/main/java/net/minecraft/server/Behavior.java.rej b/src/main/java/net/minecraft/server/Behavior.java.rej -deleted file mode 100644 -index c3d129452b4f6ff2069bc066e594b1c632ceb0d4..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/Behavior.java.rej -+++ /dev/null -@@ -1,19 +0,0 @@ --diff a/src/main/java/net/minecraft/server/Behavior.java b/src/main/java/net/minecraft/server/Behavior.java (rejected hunks) --@@ -15,6 +15,7 @@ public abstract class Behavior { -- private final String configKey; -- private static final String RATE_TYPE = "behavior"; -- // Origami end --+ co.aikar.timings.Timing timing; // Origami - behavior timing -- -- public Behavior(Map, MemoryStatus> map) { -- this(map, 60); --@@ -37,6 +38,9 @@ public abstract class Behavior { -- } -- this.configKey = key; -- // Origami end --+ // Origami start - behavior timing --+ timing = co.aikar.timings.WorldTimingsHandler.getBehaviorTimings(key); --+ // Origami end -- } -- -- public Behavior.Status a() { -diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java -index 2291135eaef64c403183724cb6e413cd7e472672..6fcc7ed7c129e6a33386d65b37cbba4a44e96f0f 100644 ---- a/src/main/java/net/minecraft/server/BlockPosition.java -+++ b/src/main/java/net/minecraft/server/BlockPosition.java -@@ -56,6 +56,12 @@ public class BlockPosition extends BaseBlockPosition { - this(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); - } - -+ // Yatopia start - helper method -+ public BlockPosition(Entity entity) { -+ this(entity.locX(), entity.locY(), entity.locZ()); -+ } -+ // Yatopia end -+ - public static long getAdjacent(int baseX, int baseY, int baseZ, EnumDirection enumdirection) { return asLong(baseX + enumdirection.getAdjacentX(), baseY + enumdirection.getAdjacentY(), baseZ + enumdirection.getAdjacentZ()); } // Paper - public static long a(long i, EnumDirection enumdirection) { - return a(i, enumdirection.getAdjacentX(), enumdirection.getAdjacentY(), enumdirection.getAdjacentZ()); -diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java -index 17753c8a997aa286460be5d8eb6508e2eaed18ce..56d5cebd155f2b5ee24c1d8b75903316e59688d1 100644 ---- a/src/main/java/net/minecraft/server/CommandDispatcher.java -+++ b/src/main/java/net/minecraft/server/CommandDispatcher.java -@@ -107,6 +107,7 @@ public class CommandDispatcher { - CommandIdleTimeout.a(this.b); - CommandStop.a(this.b); - CommandWhitelist.a(this.b); -+ net.pl3x.purpur.command.TPSBarCommand.register(getDispatcher()); // Purpur - } - - if (commanddispatcher_servertype.d) { -@@ -338,6 +339,7 @@ public class CommandDispatcher { - - } - -+ public static LiteralArgumentBuilder literal(String s) { return a(s); } // Purpur - OBFHELPER - public static LiteralArgumentBuilder a(String s) { - return LiteralArgumentBuilder.literal(s); - } -@@ -357,6 +359,7 @@ public class CommandDispatcher { - }; - } - -+ public com.mojang.brigadier.CommandDispatcher getDispatcher() { return a(); } // Purpur - OBFHELPER - public com.mojang.brigadier.CommandDispatcher a() { - return this.b; - } -diff --git a/src/main/java/net/minecraft/server/CommandDispatcher.java.rej b/src/main/java/net/minecraft/server/CommandDispatcher.java.rej -deleted file mode 100644 -index 7c1db96ec8692728e204a7d8490d74cf53b096a5..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/CommandDispatcher.java.rej -+++ /dev/null -@@ -1,9 +0,0 @@ --diff a/src/main/java/net/minecraft/server/CommandDispatcher.java b/src/main/java/net/minecraft/server/CommandDispatcher.java (rejected hunks) --@@ -109,6 +109,7 @@ public class CommandDispatcher { -- CommandWhitelist.a(this.b); -- net.pl3x.purpur.command.DemoCommand.register(getDispatcher()); // Purpur -- net.pl3x.purpur.command.PingCommand.register(getDispatcher()); // Purpur --+ net.pl3x.purpur.command.TPSBarCommand.register(getDispatcher()); // Purpur -- } -- -- if (commanddispatcher_servertype.d) { -diff --git a/src/main/java/net/minecraft/server/CommandListenerWrapper.java b/src/main/java/net/minecraft/server/CommandListenerWrapper.java -index 86f1cfe454ea0a989775b49a6b88375c766ef647..da53af61d1171db3c167c6e007adf95355771653 100644 ---- a/src/main/java/net/minecraft/server/CommandListenerWrapper.java -+++ b/src/main/java/net/minecraft/server/CommandListenerWrapper.java -@@ -189,6 +189,7 @@ public class CommandListenerWrapper implements ICompletionProvider, com.destroys - } - } - -+ public EntityPlayer getPlayerOrException() throws CommandSyntaxException { return h(); } // Purpur - OBFHELPER - public EntityPlayer h() throws CommandSyntaxException { - if (!(this.k instanceof EntityPlayer)) { - throw CommandListenerWrapper.a.create(); -diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 23ed5cb6f4fd7e81f4ec9d628d362faa280c20ab..7dec4d5aff79c138b8b957d0475f90fb116c582f 100644 ---- a/src/main/java/net/minecraft/server/Entity.java -+++ b/src/main/java/net/minecraft/server/Entity.java -@@ -434,7 +434,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke - public void setPosition(double d0, double d1, double d2) { - this.setPositionRaw(d0, d1, d2); - //this.a(this.size.a(d0, d1, d2)); // Paper - move into setPositionRaw -- if (valid) ((WorldServer) world).chunkCheck(this); // CraftBukkit -+ if (valid && !dead) ((WorldServer) world).chunkCheck(this); // CraftBukkit // Purpur - } - - protected void af() { -@@ -2400,12 +2400,15 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke - return new Vec2F(this.pitch, this.yaw); - } - -+ public BlockPosition portalPos = BlockPosition.ZERO; // Purpur - public void d(BlockPosition blockposition) { - if (this.ai()) { -+ if (!(world.purpurConfig.playerFixStuckPortal && this instanceof EntityPlayer && !blockposition.equals(portalPos))) // Purpur - this.resetPortalCooldown(); - } else { - if (!this.world.isClientSide && !blockposition.equals(this.ac)) { - this.ac = blockposition.immutableCopy(); -+ portalPos = BlockPosition.ZERO; // Purpur - } - - this.inPortal = true; -diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java -index c034869310ca3dadbfe5425c45aaa80dac59ac88..3c94f5b5cb94af4f1089e958ad7fef15f21f155e 100644 ---- a/src/main/java/net/minecraft/server/EntityVillager.java -+++ b/src/main/java/net/minecraft/server/EntityVillager.java -@@ -92,6 +92,13 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation - return behaviorcontroller; - } - -+ // Purpur start -+ @Override -+ public boolean a(EntityHuman entityhuman) { -+ return world.purpurConfig.villagerCanBeLeashed && !this.isLeashed(); -+ } -+ // Purpur end -+ - public void c(WorldServer worldserver) { - BehaviorController behaviorcontroller = this.getBehaviorController(); - -diff --git a/src/main/java/net/minecraft/server/EntityVillager.java.rej b/src/main/java/net/minecraft/server/EntityVillager.java.rej -deleted file mode 100644 -index 538b2c92e050176a1ac0bb4c2315f5b22c7f8eac..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/EntityVillager.java.rej -+++ /dev/null -@@ -1,13 +0,0 @@ --diff a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java (rejected hunks) --@@ -79,6 +79,11 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation -- protected void initPathfinder() { -- if (world.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS)); -- } --+ --+ @Override --+ public boolean a(EntityHuman entityhuman) { --+ return world.purpurConfig.villagerCanBeLeashed && !this.isLeashed(); --+ } -- // Purpur end -- -- @Override -diff --git a/src/main/java/net/minecraft/server/IProjectile.java b/src/main/java/net/minecraft/server/IProjectile.java -index dc9f2a1a132b3a7925bd62aa1da0512afd90b8b1..9819757e891f658c0a20b27286512d0b0d873d05 100644 ---- a/src/main/java/net/minecraft/server/IProjectile.java -+++ b/src/main/java/net/minecraft/server/IProjectile.java -@@ -13,6 +13,7 @@ public abstract class IProjectile extends Entity { - private UUID shooter; - private int c; - private boolean d; -+ public int despawnCounter; // Purpur - moved from EntityArrow - - IProjectile(EntityTypes entitytypes, World world) { - super(entitytypes, world); -@@ -27,6 +28,19 @@ public abstract class IProjectile extends Entity { - - } - -+ // Purpur start -+ protected final void tickDespawnCounter() { -+ if (this.getPurpurDespawnRate() != -1) { -+ ++this.despawnCounter; -+ if (this.despawnCounter >= this.getPurpurDespawnRate()) { -+ this.die(); -+ } -+ } -+ } -+ -+ protected abstract int getPurpurDespawnRate(); -+ // Purpur end -+ - @Nullable - public Entity getShooter() { - // Paper start - MC-50319 - shooter might be in another world (arrows through portals) -@@ -72,6 +86,12 @@ public abstract class IProjectile extends Entity { - } - - super.tick(); -+ -+ // Purpur start -+ if (!(this instanceof EntityArrow)) { -+ this.tickDespawnCounter(); -+ } -+ // Purpur end - } - - private boolean h() { -diff --git a/src/main/java/net/minecraft/server/IProjectile.java.rej b/src/main/java/net/minecraft/server/IProjectile.java.rej -deleted file mode 100644 -index f9080f6f15f51ac6ce521f062010d4485fb97524..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/IProjectile.java.rej -+++ /dev/null -@@ -1,40 +0,0 @@ --diff a/src/main/java/net/minecraft/server/IProjectile.java b/src/main/java/net/minecraft/server/IProjectile.java (rejected hunks) --@@ -13,11 +13,25 @@ public abstract class IProjectile extends Entity { -- private UUID shooter; -- private int c; -- private boolean d; public boolean leftOwner() { return d; } public void setLeftOwner(boolean leftOwner) { this.d = leftOwner; } // Purpur - OBFHELPER --+ public int despawnCounter; // Purpur - moved from EntityArrow -- -- IProjectile(EntityTypes entitytypes, World world) { -- super(entitytypes, world); -- } -- --+ // Purpur start --+ protected final void tickDespawnCounter() { --+ if (this.getPurpurDespawnRate() != -1) { --+ ++this.despawnCounter; --+ if (this.despawnCounter >= this.getPurpurDespawnRate()) { --+ this.die(); --+ } --+ } --+ } --+ --+ protected abstract int getPurpurDespawnRate(); --+ // Purpur end --+ -- public void setShooter(@Nullable Entity entity) { -- if (entity != null) { -- this.shooter = entity.getUniqueID(); --@@ -72,6 +86,12 @@ public abstract class IProjectile extends Entity { -- } -- -- super.tick(); --+ --+ // Purpur start --+ if (!(this instanceof EntityArrow)) { // EntityArrow handles its own despawn counter --+ this.tickDespawnCounter(); --+ } --+ // Purpur end -- } -- -- public boolean checkIfLeftOwner() { return this.h(); } // Purpur - OBFHELPER -diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java -index a62c87bceab2c9700a7b3925f208b0ffa2b9b393..1a6c593f5f20fb3a8e87ccb70cd3de7f0dcb0327 100644 ---- a/src/main/java/net/minecraft/server/StructureGenerator.java -+++ b/src/main/java/net/minecraft/server/StructureGenerator.java -@@ -151,6 +151,11 @@ public abstract class StructureGenerator - } - } - // Paper end -+ // Origami start - seed based feature search doesn't load -+ if (iworldreader instanceof World && ((World) iworldreader).origamiConfig.fastFeatureSearchDontLoad) { -+ return chunkcoordintpair.l(); -+ } -+ // Origami end - IChunkAccess ichunkaccess = iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS); - StructureStart structurestart = structuremanager.a(SectionPosition.a(ichunkaccess.getPos(), 0), this, ichunkaccess); - -diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java.rej b/src/main/java/net/minecraft/server/StructureGenerator.java.rej -deleted file mode 100644 -index dd52a8fe4fd46e57a5d1af49ba1965a483cf4fcc..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/StructureGenerator.java.rej -+++ /dev/null -@@ -1,13 +0,0 @@ --diff a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java (rejected hunks) --@@ -151,6 +151,11 @@ public abstract class StructureGenerator -- } -- } -- // Paper end --+ // Origami start - seed based feature search doesn't load --+ if (iworldreader instanceof World && ((World) iworldreader).origamiConfig.fastFeatureSearchDontLoad) { --+ return chunkcoordintpair.l(); --+ } --+ // Origami end -- // Origami start - option to only find generated features to not generate new chunks -- IChunkAccess ichunkaccess = iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS, !(iworldreader instanceof World) || !((World) iworldreader).origamiConfig.onlyFindGeneratedFeatures); -- if (ichunkaccess == null) { -diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index d0924d78a33f397b9c334f363e6f239c5e5f85a0..2cdfcec68ee66915ea72cccded5f1a2d50c04c30 100644 ---- a/src/main/java/net/minecraft/server/World.java -+++ b/src/main/java/net/minecraft/server/World.java -@@ -96,6 +96,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { - - public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config - public final net.pl3x.purpur.PurpurWorldConfig purpurConfig; // Purpur -+ public final de.minebench.origami.OrigamiConfig.WorldConfig origamiConfig; // Origami - World config - - public final co.aikar.timings.WorldTimingsHandler timings; // Paper - public static BlockPosition lastPhysicsProblem; // Spigot -@@ -157,6 +158,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { - this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper - this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config - this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((((WorldDataServer)worlddatamutable).getName())); // Purpur -+ this.origamiConfig = new de.minebench.origami.OrigamiConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Origami - this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray - this.generator = gen; - this.world = new CraftWorld((WorldServer) this, gen, env); -diff --git a/src/main/java/net/minecraft/server/World.java.rej b/src/main/java/net/minecraft/server/World.java.rej -deleted file mode 100644 -index f4e9c5a0136d3a0ed813efec7fe8a0e7f256cf40..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/World.java.rej -+++ /dev/null -@@ -1,18 +0,0 @@ --diff a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java (rejected hunks) --@@ -94,6 +94,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { -- public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper -- public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray -- --+ public final de.minebench.origami.OrigamiConfig.WorldConfig origamiConfig; // Origami - World Config --+ -- public final co.aikar.timings.WorldTimingsHandler timings; // Paper -- public static BlockPosition lastPhysicsProblem; // Spigot -- private org.spigotmc.TickLimiter entityLimiter; --@@ -124,6 +126,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { -- protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper -- this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot -- this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper --+ this.origamiConfig = new de.minebench.origami.OrigamiConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Origami - World Config -- this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray -- this.generator = gen; -- this.world = new CraftWorld((WorldServer) this, gen, env); -diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java -index b5cf60495b85c6ae6c32ee8a1c65d80e59fdce3d..1f77b251d7e7b0f023793cbf0876fc067caa75c1 100644 ---- a/src/main/java/net/minecraft/server/WorldNBTStorage.java -+++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java -@@ -49,7 +49,8 @@ public class WorldNBTStorage { - File file = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat"); - // Spigot Start - boolean usingWrongFile = false; -- if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // Paper - Check online mode first -+ boolean normalFile = file.exists() && file.isFile(); // Akarin - ensures normal file -+ if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file - { - file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); - if ( file.exists() ) -@@ -60,7 +61,7 @@ public class WorldNBTStorage { - } - // Spigot End - -- if (file.exists() && file.isFile()) { -+ if (normalFile) { // Akarin - avoid double I/O operation - nbttagcompound = NBTCompressedStreamTools.a(file); - } - // Spigot Start -diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java.rej b/src/main/java/net/minecraft/server/WorldNBTStorage.java.rej -deleted file mode 100644 -index 39dce006ebf2bd81a9d6c62c25eb9bd55d24199e..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/WorldNBTStorage.java.rej -+++ /dev/null -@@ -1,20 +0,0 @@ --diff a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java (rejected hunks) --@@ -165,7 +165,8 @@ public class WorldNBTStorage implements IPlayerFileData { -- File file = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat"); -- // Spigot Start -- boolean usingWrongFile = false; --- if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // Paper - Check online mode first --+ boolean normalFile = file.exists() && file.isFile(); // Akarin - ensures normal file --+ if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file -- { -- file = new File( this.playerDir, UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); -- if ( file.exists() ) --@@ -176,7 +177,7 @@ public class WorldNBTStorage implements IPlayerFileData { -- } -- // Spigot End -- --- if (file.exists() && file.isFile()) { --+ if (normalFile) { // Akarin - avoid double I/O operation -- nbttagcompound = NBTCompressedStreamTools.a((InputStream) (new FileInputStream(file))); -- } -- // Spigot Start -diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index f394b208e85f20775f5cdea5f4ed8272c7153007..6d783cc424b39993638cb2326c0c9dc3ab493f54 100644 ---- a/src/main/java/net/minecraft/server/WorldServer.java -+++ b/src/main/java/net/minecraft/server/WorldServer.java -@@ -181,6 +181,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { - }; - public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; - // Paper end -+ private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // AirplaneL - // Paper start - @Override - public boolean isChunkLoaded(int x, int z) { -diff --git a/src/main/java/net/minecraft/server/WorldServer.java.rej b/src/main/java/net/minecraft/server/WorldServer.java.rej -deleted file mode 100644 -index 06b971640f16a99b41eb07b1d0bb43f3ad300c84..0000000000000000000000000000000000000000 ---- a/src/main/java/net/minecraft/server/WorldServer.java.rej -+++ /dev/null -@@ -1,10 +0,0 @@ --diff a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java (rejected hunks) --@@ -978,6 +978,8 @@ public class WorldServer extends World implements GeneratorAccessSeed { -- private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom(); -- // Paper end -- --+ private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // AirplaneL --+ -- // AirplaneL start - create version of chunk tick that returns a bool for updating lighting -- public void a(Chunk chunk, int i) { this.abool(chunk, i); } -- public boolean abool(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper -diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index 750637ecdd44dacac2b7c83a1701d795309d5583..081dc7a2a4a5d80da868b1080b212ddce2bb0bc7 100644 ---- a/src/main/java/net/pl3x/purpur/PurpurConfig.java -+++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java -@@ -1,5 +1,7 @@ - package net.pl3x.purpur; - -+import co.aikar.timings.TimingsManager; -+import com.destroystokyo.paper.PaperConfig; - import com.google.common.base.Throwables; - import net.minecraft.server.MinecraftServer; - import net.pl3x.purpur.command.PurpurCommand; -@@ -129,9 +131,7 @@ public class PurpurConfig { - return config.getString(path, config.getString(path)); - } - -- public static String timingsUrl = "https://timings.pl3x.net"; - private static void timingsSettings() { -- timingsUrl = getString("settings.timings.url", timingsUrl); - if (!TimingsManager.hiddenConfigs.contains("server-ip")) TimingsManager.hiddenConfigs.add("server-ip"); - } - -@@ -163,4 +163,35 @@ public class PurpurConfig { - InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); - enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); - } -+ -+ public static boolean dontSendUselessEntityPackets = false; -+ private static void dontSendUselessEntityPackets() { -+ dontSendUselessEntityPackets = getBoolean("settings.dont-send-useless-entity-packets", dontSendUselessEntityPackets); -+ } -+ -+ public static boolean allowInfinityMending = false; -+ private static void enchantmentSettings() { -+ if (version < 5) { -+ boolean oldValue = getBoolean("settings.enchantment.allow-infinite-and-mending-together", false); -+ set("settings.enchantment.allow-infinity-and-mending-together", oldValue); -+ set("settings.enchantment.allow-infinite-and-mending-together", null); -+ } -+ allowInfinityMending = getBoolean("settings.enchantment.allow-infinity-and-mending-together", allowInfinityMending); -+ } -+ -+ public static boolean useHexColorsInConsole = true; -+ private static void loggerSettings() { -+ useHexColorsInConsole = getBoolean("settings.logger.hex-color-support-in-console", useHexColorsInConsole); -+ } -+ -+ private static void migrateDisableProjectileSaving() { -+ if (PurpurConfig.version < 6) { -+ final boolean saveProjectilesToDisk = getBoolean("world-settings.default.gameplay-mechanics.save-projectiles-to-disk", true); -+ set("world-settings.default.gameplay-mechanics.save-projectiles-to-disk", null); -+ if (!saveProjectilesToDisk) { -+ PaperConfig.config.set("world-settings.default.projectile-load-save-per-chunk-limit", 0); -+ PaperConfig.saveConfig(); -+ } -+ } -+ } - } -diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java.rej b/src/main/java/net/pl3x/purpur/PurpurConfig.java.rej -deleted file mode 100644 -index 0635030ba4c1cea7961238bb0c0be39d8c5a7d50..0000000000000000000000000000000000000000 ---- a/src/main/java/net/pl3x/purpur/PurpurConfig.java.rej -+++ /dev/null -@@ -1,27 +0,0 @@ --diff a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java (rejected hunks) --@@ -1,6 +1,7 @@ -- package net.pl3x.purpur; -- -- import co.aikar.timings.TimingsManager; --+import com.destroystokyo.paper.PaperConfig; -- import com.google.common.base.Throwables; -- import net.minecraft.server.EntitySize; -- import net.minecraft.server.EntityTypes; --@@ -132,6 +133,17 @@ public class PurpurConfig { -- return config.getString(path, config.getString(path)); -- } -- --+ private static void migrateDisableProjectileSaving() { --+ if (PurpurConfig.version < 6) { --+ final boolean saveProjectilesToDisk = getBoolean("world-settings.default.gameplay-mechanics.save-projectiles-to-disk", true); --+ set("world-settings.default.gameplay-mechanics.save-projectiles-to-disk", null); --+ if (!saveProjectilesToDisk) { --+ PaperConfig.config.set("world-settings.default.projectile-load-save-per-chunk-limit", 0); --+ PaperConfig.saveConfig(); --+ } --+ } --+ } --+ -- public static String afkBroadcastAway = "§e§o%s is now AFK"; -- public static String afkBroadcastBack = "§e§o%s is no longer AFK"; -- public static String afkTabListPrefix = "[AFK] "; -diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 361f7857e461578e90cb71e15027dadaf794cb69..e86ccbd36250f4229ce62319a59889bc0ac5befb 100644 ---- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1,7 +1,15 @@ - package net.pl3x.purpur; - -+import com.destroystokyo.paper.PaperConfig; -+import java.util.ArrayList; -+import java.util.HashSet; - import org.bukkit.configuration.ConfigurationSection; - import java.util.List; -+import java.util.Set; -+import net.minecraft.server.Block; -+import net.minecraft.server.IRegistry; -+import net.minecraft.server.MinecraftKey; -+ - import static net.pl3x.purpur.PurpurConfig.log; - - public class PurpurWorldConfig { -@@ -56,4 +64,172 @@ public class PurpurWorldConfig { - PurpurConfig.config.addDefault("world-settings.default." + path, def); - return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); - } -+ -+ public int villagerBrainTicks = 1; -+ public boolean villagerUseBrainTicksOnlyWhenLagging = true; -+ public boolean villagerCanBeLeashed = false; -+ private void villagerSettings() { -+ villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks); -+ villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging); -+ villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); -+ } -+ -+ public boolean villagerTraderCanBeLeashed = false; -+ private void villagerTraderSettings() { -+ villagerTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", villagerTraderCanBeLeashed); -+ } -+ -+ public boolean milkCuresBadOmen = true; -+ public boolean persistentTileEntityDisplayNames = false; -+ private void miscGameplayMechanicsSettings() { -+ milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); -+ persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); -+ } -+ -+ public boolean anvilAllowColors = false; -+ private void anvilSettings() { -+ anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors); -+ } -+ -+ public int cowFeedMushrooms = 0; -+ private void cowSettings() { -+ cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); -+ } -+ -+ public boolean snowGolemDropsPumpkin = false; -+ public boolean snowGolemPutPumpkinBack = false; -+ private void snowGolemSettings() { -+ snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); -+ snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); -+ } -+ -+ public boolean farmlandGetsMoistFromBelow = false; -+ private void farmlandSettings() { -+ farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); -+ } -+ -+ public int entityLifeSpan = 0; -+ private void entitySettings() { -+ entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); -+ } -+ -+ public boolean squidImmuneToEAR = true; -+ public double squidOffsetWaterCheck = 0.0D; -+ private void squidSettings() { -+ squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); -+ squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); -+ } -+ -+ public Set noTickBlocks = new HashSet<>(); -+ private void noTickBlocks() { -+ getList("blocks.no-tick", new ArrayList<>()).forEach(key -> { -+ Block block = IRegistry.BLOCK.get(new MinecraftKey(key.toString())); -+ if (!block.getBlockData().isAir()) { -+ noTickBlocks.add(block); -+ } -+ }); -+ } -+ -+ public boolean dolphinDisableTreasureSearching = false; -+ private void dolphinSettings() { -+ dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); -+ } -+ -+ public boolean turtleEggsBreakFromExpOrbs = true; -+ public boolean turtleEggsBreakFromItems = true; -+ public boolean turtleEggsBreakFromMinecarts = true; -+ private void turtleEggSettings() { -+ turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); -+ turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); -+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); -+ } -+ -+ public int dragonFireballDespawnRate = -1; -+ public int eggDespawnRate = -1; -+ public int enderPearlDespawnRate = -1; -+ public int expBottleDespawnRate = -1; -+ public int fireworkDespawnRate = -1; -+ public int fishingHookDespawnRate = -1; -+ public int largeFireballDespawnRate = -1; -+ public int llamaSpitDespawnRate = -1; -+ public int potionDespawnRate = -1; -+ public int shulkerBulletDespawnRate = -1; -+ public int smallFireballDespawnRate = -1; -+ public int snowballDespawnRate = -1; -+ public int witherSkullDespawnRate = -1; -+ private void projectileDespawnRateSettings() { -+ dragonFireballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.dragon_fireball", dragonFireballDespawnRate); -+ eggDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.egg", eggDespawnRate); -+ enderPearlDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.ender_pearl", enderPearlDespawnRate); -+ expBottleDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.experience_bottle", expBottleDespawnRate); -+ fireworkDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.firework_rocket", fireworkDespawnRate); -+ fishingHookDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.fishing_bobber", fishingHookDespawnRate); -+ largeFireballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.fireball", largeFireballDespawnRate); -+ llamaSpitDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.llama_spit", llamaSpitDespawnRate); -+ potionDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.potion", potionDespawnRate); -+ shulkerBulletDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.shulker_bullet", shulkerBulletDespawnRate); -+ smallFireballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.small_fireball", smallFireballDespawnRate); -+ snowballDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.snowball", snowballDespawnRate); -+ witherSkullDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.wither_skull", witherSkullDespawnRate); -+ } -+ -+ public boolean infinityWorksWithNormalArrows = true; -+ public boolean infinityWorksWithSpectralArrows = false; -+ public boolean infinityWorksWithTippedArrows = false; -+ private void infinityArrowsSettings() { -+ infinityWorksWithNormalArrows = getBoolean("gameplay-mechanics.infinity-bow.normal-arrows", infinityWorksWithNormalArrows); -+ infinityWorksWithSpectralArrows = getBoolean("gameplay-mechanics.infinity-bow.spectral-arrows", infinityWorksWithSpectralArrows); -+ infinityWorksWithTippedArrows = getBoolean("gameplay-mechanics.infinity-bow.tipped-arrows", infinityWorksWithTippedArrows); -+ } -+ -+ public boolean signAllowColors = false; -+ public boolean signRightClickEdit = false; -+ private void signSettings() { -+ signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors); -+ signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); -+ } -+ -+ private void migrateDisableProjectileSaving() { -+ if (PurpurConfig.version < 6) { -+ final boolean saveProjectilesToDisk = PurpurConfig.config.getBoolean("world-settings." + worldName + ".gameplay-mechanics.save-projectiles-to-disk", true); -+ set("gameplay-mechanics.save-projectiles-to-disk", null); -+ if (!saveProjectilesToDisk) { -+ PaperConfig.config.set("world-settings." + worldName + ".projectile-load-save-per-chunk-limit", 0); -+ PaperConfig.saveConfig(); -+ } -+ } -+ } -+ -+ public int bambooMaxHeight = 16; -+ public int bambooSmallHeight = 10; -+ private void bambooSettings() { -+ bambooMaxHeight = getInt("blocks.bamboo.max-height", bambooMaxHeight); -+ bambooSmallHeight = getInt("blocks.bamboo.small-height", bambooSmallHeight); -+ } -+ -+ public double twistingVinesGrowthModifier = 0.10D; -+ private void twistingVinesSettings() { -+ twistingVinesGrowthModifier = getDouble("blocks.twisting_vines.growth-modifier", twistingVinesGrowthModifier); -+ } -+ -+ public double weepingVinesGrowthModifier = 0.10D; -+ private void weepingVinesSettings() { -+ weepingVinesGrowthModifier = getDouble("blocks.weeping_vines.growth-modifier", weepingVinesGrowthModifier); -+ } -+ -+ public int lavaSpeedNether = 10; -+ public int lavaSpeedNotNether = 30; -+ public boolean lavaInfinite = false; -+ public int lavaInfiniteRequiredSources = 2; -+ private void lavaSettings() { -+ lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether); -+ lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); -+ lavaInfinite = getBoolean("blocks.lava.infinite-source", lavaInfinite); -+ lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources); -+ } -+ -+ public boolean playerFixStuckPortal = false; -+ private void playerFixStuckPortal() { -+ playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); -+ } - } -diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java.rej b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java.rej -deleted file mode 100644 -index 0ee12c5b16ce479cdf86b3fd21d3cc5af6da9e9b..0000000000000000000000000000000000000000 ---- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java.rej -+++ /dev/null -@@ -1,13 +0,0 @@ --diff a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java (rejected hunks) --@@ -338,6 +338,11 @@ public class PurpurWorldConfig { -- }); -- } -- --+ public boolean playerFixStuckPortal = false; --+ private void playerFixStuckPortal() { --+ playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); --+ } --+ -- public boolean teleportIfOutsideBorder = false; -- private void teleportIfOutsideBorder() { -- teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 37cf84e0273d1a3bb49aadd2fe19d86730135afd..8f18c57ab283c85571333165e8aeca23256a39e4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -863,6 +863,7 @@ public final class CraftServer implements Server { - com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper - com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config - net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur -+ de.minebench.origami.OrigamiConfig.init((File) console.options.valueOf("origami-settings")); // Origami - for (WorldServer world : console.getWorlds()) { - world.worldDataServer.setDifficulty(config.difficulty); - world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); -@@ -899,6 +900,7 @@ public final class CraftServer implements Server { - world.paperConfig.init(); // Paper - world.tuinityConfig.init(); // Tuinity - Server Config - world.purpurConfig.init(); // Purpur -+ world.origamiConfig.init(); // Origami - } - - Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -2314,6 +2316,13 @@ public final class CraftServer implements Server { - } - // Purpur end - -+ // Origami start -+ @Override -+ public YamlConfiguration getOrigamiConfig() { -+ return de.minebench.origami.OrigamiConfig.config; -+ } -+ // Origami end -+ - @Override - public void restart() { - org.spigotmc.RestartCommand.restart(); -@@ -2451,4 +2460,11 @@ public final class CraftServer implements Server { - return mobGoals; - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public boolean isLagging() { -+ return getServer().lagging; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java.rej b/src/main/java/org/bukkit/craftbukkit/CraftServer.java.rej -deleted file mode 100644 -index b9dbdd5416862e325200e62b7d5821a73c230ec1..0000000000000000000000000000000000000000 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java.rej -+++ /dev/null -@@ -1,12 +0,0 @@ --diff a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java (rejected hunks) --@@ -2451,5 +2451,10 @@ public final class CraftServer implements Server { -- public String getServerName() { -- return getProperties().serverName; -- } --+ --+ @Override --+ public boolean isLagging() { --+ return getServer().lagging; --+ } -- // Purpur end -- } -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index bb93febb86c81e0897f1a0d8d058f4fb29f18b41..d1f337d1371fc476d0c35b8fb4bdf55c345d86be 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -154,6 +154,14 @@ public class Main { - .describedAs("Yml file"); - // Purpur end - -+ // Origami start -+ acceptsAll(asList("origami", "origami-settings"), "File for origami settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("origami.yml")) -+ .describedAs("Yml file"); -+ // Origami end -+ - // Paper start - acceptsAll(asList("server-name"), "Name of the server") - .withRequiredArg() -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java.rej b/src/main/java/org/bukkit/craftbukkit/Main.java.rej -deleted file mode 100644 -index 27a6c3279b77f24edcea24f3a01559145d2f4da1..0000000000000000000000000000000000000000 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java.rej -+++ /dev/null -@@ -1,16 +0,0 @@ --diff a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java (rejected hunks) --@@ -139,6 +139,14 @@ public class Main { -- .describedAs("Yml file"); -- // Paper end -- --+ // Origami Start - Server Config --+ acceptsAll(asList("origami", "origami-settings"), "File for origami settings") --+ .withRequiredArg() --+ .ofType(File.class) --+ .defaultsTo(new File("origami.yml")) --+ .describedAs("Yml file"); --+ // Origami end - Server Config --+ -- // Paper start -- acceptsAll(asList("server-name"), "Name of the server") -- .withRequiredArg() diff --git a/patches/server/0003-Brandings.patch b/patches/server/0002-Brandings.patch similarity index 89% rename from patches/server/0003-Brandings.patch rename to patches/server/0002-Brandings.patch index ee4db681..4f3ad14a 100644 --- a/patches/server/0003-Brandings.patch +++ b/patches/server/0002-Brandings.patch @@ -27,45 +27,46 @@ index 4d8740678049aa749b42618470e9cc838555528d..159f72efe20f8fee940bd00ae7af00f0 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 74ed02fa9296583977bb721014b10ff8b708b43c..a13c7b2b5bc79ecaea404779149ed02c4600a61b 100644 +index c1280478ee4565003883df9607d4a8a0e8fe4faa..c6cfdea783eaa40d4eb581a9208d6cdb72dace5b 100644 --- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -18,6 +18,7 @@ public final class PaperConsole extends SimpleTerminalConsole { +@@ -17,7 +17,7 @@ public final class PaperConsole extends SimpleTerminalConsole { + @Override protected LineReader buildReader(LineReaderBuilder builder) { return super.buildReader(builder - .appName("Paper") +- .appName("Purpur") // Purpur + .appName("Yatopia") // Yatopia .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) .completer(new ConsoleCommandCompleter(this.server)) ); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 679498256770c6ed0010de4f91ed82c9375d871c..23978b1c4ebe3e78d4a076d93cc34ed69ccfd3db 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1523,7 +1523,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // Spigot - Spigot > // CraftBukkit - cb > vanilla! -+ return "Yatopia"; // Yatopia // Tuinity //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! +- public static String serverModName = "Purpur"; ++ public static String serverModName = "Yatopia"; + private static void serverModName() { + serverModName = getString("settings.server-mod-name", serverModName); } - - public CrashReport b(CrashReport crashreport) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8f18c57ab283c85571333165e8aeca23256a39e4..a43981b9d95ae61d63fddc7b534ee8a44722ae2f 100644 +index 53deddce357170a712913d916ba1d58e663fc1a1..1286f0127300144abe4f78a3480acec42a5fc758 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -232,7 +232,7 @@ import javax.annotation.Nullable; // Paper import javax.annotation.Nonnull; // Paper public final class CraftServer implements Server { -- private final String serverName = "Tuinity"; // Paper // Tuinity -+ private final String serverName = "Yatopia"; // Paper // Tuinity // Yatopia +- private final String serverName = "Purpur"; // Paper // Tuinity // Purpur ++ private final String serverName = "Yatopia"; // Paper // Tuinity // Purpur // Yatopia private final String serverVersion; private final String bukkitVersion = Versioning.getBukkitVersion(); private final Logger logger = Logger.getLogger("Minecraft"); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index d1f337d1371fc476d0c35b8fb4bdf55c345d86be..93ff9cef2732553e179791215d9d3c9201945f73 100644 +index 448538cc8a3d16b028a0a6f0f05c9370a02f4259..e51dcb259d511c369806a83b96c2820f316e1401 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -275,7 +275,7 @@ public class Main { @@ -78,28 +79,28 @@ index d1f337d1371fc476d0c35b8fb4bdf55c345d86be..93ff9cef2732553e179791215d9d3c92 //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); // Paper End diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index ac5003dc827217bd1947c71044abcbcbd2210dcd..9ef99b4a9e2a648a9245cfae70d12645a15fb12c 100644 +index 37c561fb775cf7dd955b185b4ea94fecc574be63..9ef99b4a9e2a648a9245cfae70d12645a15fb12c 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -370,7 +370,7 @@ public final class CraftMagicNumbers implements UnsafeValues { @Override public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { -- return new com.destroystokyo.paper.PaperVersionFetcher(); +- return new net.pl3x.purpur.PurpurVersionFetcher(); + return new org.yatopiamc.yatopia.server.YatopiaVersionFetcher(); // Yatopia } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -index 001b1e5197eaa51bfff9031aa6c69876c9a47960..bd5d7508b1c44fba776c5d7fa4454ff5c95c8709 100644 +index 13b98439320ac1401a920c01d7cf5a4b3a23deff..d2f272fd733f7eee9a89029baecccfac7e45baf4 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/com.tuinity/tuinity-api/pom.properties"); // Tuinity -+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.yatopiamc/yatopia-api/pom.properties"); // Yatopia +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/net.pl3x.purpur/purpur-api/pom.properties"); // Tuinity // Purpur ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.yatopiamc/yatopia-api/pom.properties"); // Tuinity // Purpur // Yatopia Properties properties = new Properties(); if (stream != null) { diff --git a/patches/server/0004-Utilities.patch b/patches/server/0003-Utilities.patch similarity index 97% rename from patches/server/0004-Utilities.patch rename to patches/server/0003-Utilities.patch index 882cd824..f8618a3c 100644 --- a/patches/server/0004-Utilities.patch +++ b/patches/server/0003-Utilities.patch @@ -9,10 +9,10 @@ Co-authored-by: Mykyta Komarnytskyy Co-authored-by: Ivan Pekov diff --git a/pom.xml b/pom.xml -index 3c01daecc01583275b5007e259bddb62035fd361..464767f08635fa8668c8dc9dd93323bf4b5c7060 100644 +index 4f56aa4ae78b9d3756983cde52bc1d1adda0c9d4..d8ec87143370144c04502cd7bddf57f2f5e25168 100644 --- a/pom.xml +++ b/pom.xml -@@ -174,6 +174,12 @@ +@@ -176,6 +176,12 @@ commons-math3 3.6.1 diff --git a/patches/server/0005-Add-GameProfileLookupEvent.patch b/patches/server/0004-Add-GameProfileLookupEvent.patch similarity index 100% rename from patches/server/0005-Add-GameProfileLookupEvent.patch rename to patches/server/0004-Add-GameProfileLookupEvent.patch diff --git a/patches/server/0006-Add-last-tick-time-API.patch b/patches/server/0005-Add-last-tick-time-API.patch similarity index 92% rename from patches/server/0006-Add-last-tick-time-API.patch rename to patches/server/0005-Add-last-tick-time-API.patch index c74644b9..5d78746e 100644 --- a/patches/server/0006-Add-last-tick-time-API.patch +++ b/patches/server/0005-Add-last-tick-time-API.patch @@ -7,7 +7,7 @@ Original patch by: Co-authored-by: tr7zw diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 23978b1c4ebe3e78d4a076d93cc34ed69ccfd3db..6f1a6ee340577d25b5edeb28eb2d2c52af511957 100644 +index f6e3bf632a6dd559a58d73eab07f15d6b69c5c13..240e230725ca685389fe39dfa60ac3ca22d3faa0 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -935,6 +935,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java -index 537456a7427cddd6783f5b5d8ee2d655668c4c53..004184a7c3da4f72f68a5fd9b4dd5abd0b8f871d 100644 +index f452cc575adf9137f3b9f1eef1904f8116c7a7ec..15aa603a771c327879a4088609850fb86c6347bd 100644 --- a/src/main/java/de/minebench/origami/OrigamiConfig.java +++ b/src/main/java/de/minebench/origami/OrigamiConfig.java -@@ -134,6 +134,39 @@ public final class OrigamiConfig { - private void observerClock() { - disableObserverClocks = getBoolean("disable-observer-clocks", disableObserverClocks); +@@ -136,6 +136,39 @@ public final class OrigamiConfig { + private void pigmenDontTargetUnlessHit() { + pigmenDontTargetUnlessHit = getBoolean("pigmen.dont-target-unless-hit", pigmenDontTargetUnlessHit); } + + // Yatopia start @@ -59,10 +59,10 @@ index 537456a7427cddd6783f5b5d8ee2d655668c4c53..004184a7c3da4f72f68a5fd9b4dd5abd } \ No newline at end of file diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java -index f35ca7363962fd686a650a34b0fed47e86fde696..c829718565e7f45811d467dd82171f04325effe1 100644 +index 41027a01aa331315e8c17a100f29fa6f00f85988..ab873b37a818b304d097dbe1958885235ba7077f 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java -@@ -2870,7 +2870,7 @@ public abstract class EntityLiving extends Entity { +@@ -2932,7 +2932,7 @@ public abstract class EntityLiving extends Entity { // Paper - end don't run getEntities if we're not going to use its result // Tuinity start - reduce memory allocation from collideNearby List list = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); diff --git a/patches/server/0009-Allow-to-change-the-piston-push-limit.patch b/patches/server/0008-Allow-to-change-the-piston-push-limit.patch similarity index 100% rename from patches/server/0009-Allow-to-change-the-piston-push-limit.patch rename to patches/server/0008-Allow-to-change-the-piston-push-limit.patch diff --git a/patches/server/0010-Add-NBT-API-as-a-first-class-lib.patch b/patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch similarity index 88% rename from patches/server/0010-Add-NBT-API-as-a-first-class-lib.patch rename to patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch index 4db43a2a..3251eb95 100644 --- a/patches/server/0010-Add-NBT-API-as-a-first-class-lib.patch +++ b/patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add NBT API as a first-class lib diff --git a/pom.xml b/pom.xml -index 464767f08635fa8668c8dc9dd93323bf4b5c7060..70e88bdfe5c6c6be1f6555340556b34479f49048 100644 +index d8ec87143370144c04502cd7bddf57f2f5e25168..8af1a91102c5cc4c230f622e6629e46e95f17d44 100644 --- a/pom.xml +++ b/pom.xml -@@ -356,6 +356,10 @@ +@@ -358,6 +358,10 @@ net.minecraft.server net.minecraft.server.v${minecraft_version} diff --git a/patches/server/0011-Modify-default-configs.patch b/patches/server/0010-Modify-default-configs.patch similarity index 95% rename from patches/server/0011-Modify-default-configs.patch rename to patches/server/0010-Modify-default-configs.patch index d01cf1a6..4739cfbb 100644 --- a/patches/server/0011-Modify-default-configs.patch +++ b/patches/server/0010-Modify-default-configs.patch @@ -18,7 +18,7 @@ index ce14283dd1a1fddbea17c2fbaf1c4ef9d7a7479f..4a21a83c448355d61fb946bd0eb5d752 TimingsManager.privacy = getBoolean("timings.server-name-privacy", false); TimingsManager.hiddenConfigs = getList("timings.hidden-config-entries", Lists.newArrayList("database", "settings.bungeecord-addresses", "settings.velocity-support.secret")); diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -index 4735dcba31b556fafe9c7d7440c89e940755c81f..1c6ad58e6c1d2ff4a75043c0bbbbb1539596846b 100644 +index 78250d7db036198ec7119a065444c9253c1ab043..171d1bc9414a9827f333576e46afb79c9fc4016c 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -633,7 +633,7 @@ public class PaperWorldConfig { diff --git a/patches/server/0012-lithium-MixinDirection.patch b/patches/server/0011-lithium-MixinDirection.patch similarity index 92% rename from patches/server/0012-lithium-MixinDirection.patch rename to patches/server/0011-lithium-MixinDirection.patch index d7c8c085..ecc656a3 100644 --- a/patches/server/0012-lithium-MixinDirection.patch +++ b/patches/server/0011-lithium-MixinDirection.patch @@ -7,7 +7,7 @@ Original code by JellySquid, licensed under GNU Lesser General Public License v3 you can find the original code on https://github.com/jellysquid3/lithium-fabric/tree/1.16.x/fabric (Yarn mappings) diff --git a/src/main/java/net/minecraft/server/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java -index 060f5ab597b6469df945a854fffe57ffea3d947f..0f669e5115c20f70585ac49b1f6402d5121ba969 100644 +index a660033af081cef69e4646c9ed9eaa66cf90217f..2f1e7f0d753aace655210e32ddec59a3c46aade9 100644 --- a/src/main/java/net/minecraft/server/EnumDirection.java +++ b/src/main/java/net/minecraft/server/EnumDirection.java @@ -20,13 +20,13 @@ public enum EnumDirection implements INamable { @@ -39,8 +39,8 @@ index 060f5ab597b6469df945a854fffe57ffea3d947f..0f669e5115c20f70585ac49b1f6402d5 + return ALL[this.h]; } - public EnumDirection g() { -@@ -197,8 +201,12 @@ public enum EnumDirection implements INamable { + public EnumDirection rotateCW() { return g(); } // Purpur - OBFHELPER +@@ -200,8 +204,12 @@ public enum EnumDirection implements INamable { return (float) ((this.i & 3) * 90); } diff --git a/patches/server/0013-lithium-MixinBox.patch b/patches/server/0012-lithium-MixinBox.patch similarity index 92% rename from patches/server/0013-lithium-MixinBox.patch rename to patches/server/0012-lithium-MixinBox.patch index 275ba37e..c91dbe8c 100644 --- a/patches/server/0013-lithium-MixinBox.patch +++ b/patches/server/0012-lithium-MixinBox.patch @@ -7,10 +7,10 @@ Original code by JellySquid, licensed under GNU Lesser General Public License v3 you can find the original code on https://github.com/jellysquid3/lithium-fabric/tree/1.16.x/fabric (Yarn mappings) diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java -index acd009844099293befd28c5f1c20d947016fa19b..4fb61603ca2d7cb7df9742c72117f4d33bd2bbdf 100644 +index 5c3d5b22b833d9f835e17803295b87893fd05e62..e1d2e5f61b01f812d53a41cd8ba5d45b5f8c7f19 100644 --- a/src/main/java/net/minecraft/server/AxisAlignedBB.java +++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java -@@ -202,12 +202,38 @@ public class AxisAlignedBB { +@@ -193,12 +193,38 @@ public class AxisAlignedBB { return new AxisAlignedBB(vec3d.x, vec3d.y, vec3d.z, vec3d.x + 1.0D, vec3d.y + 1.0D, vec3d.z + 1.0D); } diff --git a/patches/server/0014-lithium-enum_values.patch b/patches/server/0013-lithium-enum_values.patch similarity index 97% rename from patches/server/0014-lithium-enum_values.patch rename to patches/server/0013-lithium-enum_values.patch index 7d801866..34cb0be0 100644 --- a/patches/server/0014-lithium-enum_values.patch +++ b/patches/server/0013-lithium-enum_values.patch @@ -39,10 +39,10 @@ index 5d3bb5f393a1e0e4a2e8b9a466530a91279697a9..7f084e7f11a829c10d113c7fb39eec0b if (enumdirection2 != EnumDirection.DOWN && world.isBlockFacePowered(blockposition1.shift(enumdirection2), enumdirection2)) { return true; diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java -index c829718565e7f45811d467dd82171f04325effe1..7138756a053452746a0fd32887eb9fdb087846d6 100644 +index ab873b37a818b304d097dbe1958885235ba7077f..62948f586e298d711fbe893ab29d6d61dc6bb62f 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java -@@ -2591,10 +2591,12 @@ public abstract class EntityLiving extends Entity { +@@ -2616,10 +2616,12 @@ public abstract class EntityLiving extends Entity { } diff --git a/patches/server/0015-lithium-MixinGoalSelector.patch b/patches/server/0014-lithium-MixinGoalSelector.patch similarity index 100% rename from patches/server/0015-lithium-MixinGoalSelector.patch rename to patches/server/0014-lithium-MixinGoalSelector.patch diff --git a/patches/server/0016-lithium-HashedList.patch b/patches/server/0015-lithium-HashedList.patch similarity index 100% rename from patches/server/0016-lithium-HashedList.patch rename to patches/server/0015-lithium-HashedList.patch diff --git a/patches/server/0017-Item-stuck-sleep-config.patch b/patches/server/0016-Item-stuck-sleep-config.patch similarity index 91% rename from patches/server/0017-Item-stuck-sleep-config.patch rename to patches/server/0016-Item-stuck-sleep-config.patch index 434d45b7..ec881820 100644 --- a/patches/server/0017-Item-stuck-sleep-config.patch +++ b/patches/server/0016-Item-stuck-sleep-config.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Item stuck sleep config diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java -index 747f5085da9dd4c1614d5e9e1ea459b300d025b3..2001e364a2e1d0657cb3395b5a2a56fa5dff6299 100644 +index 954d37ca9e1079616836d3f441845b37c5a541f3..c456e3d6377f5b59c2383da9d52bdb43d3685064 100644 --- a/src/main/java/net/minecraft/server/EntityItem.java +++ b/src/main/java/net/minecraft/server/EntityItem.java -@@ -82,7 +82,7 @@ public class EntityItem extends Entity { +@@ -85,7 +85,7 @@ public class EntityItem extends Entity { if (this.world.isClientSide) { this.noclip = false; diff --git a/patches/server/0018-Option-for-simpler-Villagers.patch b/patches/server/0017-Option-for-simpler-Villagers.patch similarity index 87% rename from patches/server/0018-Option-for-simpler-Villagers.patch rename to patches/server/0017-Option-for-simpler-Villagers.patch index 639b13fc..2e14aaef 100644 --- a/patches/server/0018-Option-for-simpler-Villagers.patch +++ b/patches/server/0017-Option-for-simpler-Villagers.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Option for simpler Villagers diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java -index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146ab95cba68 100644 +index eef51f8e5734b897164ca9514e7b49b2678416e6..2745cd0d98cfcf25a5304fa8ae0903028a283b25 100644 --- a/src/main/java/net/minecraft/server/EntityVillager.java +++ b/src/main/java/net/minecraft/server/EntityVillager.java @@ -31,6 +31,7 @@ import org.bukkit.event.entity.VillagerReplenishTradeEvent; @@ -16,7 +16,7 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a private static final DataWatcherObject br = DataWatcher.a(EntityVillager.class, DataWatcherRegistry.q); public static final Map bp = ImmutableMap.of(Items.BREAD, 4, Items.POTATO, 1, Items.CARROT, 1, Items.BEETROOT, 1); private static final Set bs = ImmutableSet.of(Items.BREAD, Items.POTATO, Items.CARROT, Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT, new Item[]{Items.BEETROOT_SEEDS}); -@@ -67,13 +68,55 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation +@@ -67,8 +68,14 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation public EntityVillager(EntityTypes entitytypes, World world, VillagerType villagertype) { super(entitytypes, world); this.by = new Reputation(); @@ -31,11 +31,11 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a this.setCanPickupLoot(true); this.setVillagerData(this.getVillagerData().withType(villagertype).withProfession(VillagerProfession.NONE)); this.brainTickOffset = getRandom().nextInt(100); // Purpur - } - -+ // Yatopia start -+ @Override -+ public void initPathfinder() { +@@ -89,6 +96,38 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + protected void initPathfinder() { + this.goalSelector.a(0, new PathfinderGoalHasRider(this)); // Purpur + if (world.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, TEMPT_ITEMS)); ++ // Yatopia Start + if (!simplerVillagerBehavior) { + // safety + return; @@ -66,13 +66,11 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a + this.goalSelector.a(8, new PathfinderGoalRandomStrollLand(this, 0.35D)); + this.goalSelector.a(9, new PathfinderGoalInteract(this, EntityHuman.class, 3.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); -+ } -+ // Yatopia end -+ ++ // Yatopia End + } + @Override - public BehaviorController getBehaviorController() { - return (BehaviorController) super.getBehaviorController(); // CraftBukkit - decompile error -@@ -86,6 +129,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation +@@ -114,6 +153,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation @Override protected BehaviorController a(Dynamic dynamic) { @@ -80,9 +78,9 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a BehaviorController behaviorcontroller = this.cK().a(dynamic); this.a(behaviorcontroller); -@@ -171,10 +215,39 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation +@@ -212,10 +252,39 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation } - // Spigot End + // Purpur end + // Yatopia start + private VillagerProfession getRandomProfession() { @@ -118,17 +116,17 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a + if (simplerVillagerBehavior) return; + // Yatopia end // Purpur start + if (world.purpurConfig.villagerLobotomizeEnabled) inactive = inactive || isLobotomized(); boolean tick = (world.getTime() + brainTickOffset) % world.purpurConfig.villagerBrainTicks == 0; - if (((WorldServer) world).getMinecraftServer().lagging ? tick : world.purpurConfig.villagerUseBrainTicksOnlyWhenLagging || tick) -@@ -344,6 +417,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation - return this.bD == 0 || this.bD < 2 && this.world.getTime() > this.bC + 2400L; +@@ -338,6 +407,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + this.fl(); } + public final boolean canRefresh() { return fc(); } // Yatopia - OBFHELPER - public boolean fc() { - long i = this.bC + 12000L; - long j = this.world.getTime(); -@@ -366,6 +440,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation + private void fl() { + Iterator iterator = this.getOffers().iterator(); + +@@ -412,6 +482,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation return this.fn() && this.fm(); } @@ -136,7 +134,7 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a private void fo() { int i = 2 - this.bD; -@@ -598,6 +673,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation +@@ -644,6 +715,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation } private void a(Entity entity) { @@ -144,7 +142,7 @@ index 3c94f5b5cb94af4f1089e958ad7fef15f21f155e..573e174434ca0c88c968e62874c4146a if (this.world instanceof WorldServer) { Optional> optional = this.bg.getMemory(MemoryModuleType.VISIBLE_MOBS); -@@ -614,6 +690,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation +@@ -660,6 +732,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation } public void a(MemoryModuleType memorymoduletype) { diff --git a/patches/server/0019-Heavily-optimize-furnance-fuel-and-recipe-lookups.patch b/patches/server/0018-Heavily-optimize-furnance-fuel-and-recipe-lookups.patch similarity index 88% rename from patches/server/0019-Heavily-optimize-furnance-fuel-and-recipe-lookups.patch rename to patches/server/0018-Heavily-optimize-furnance-fuel-and-recipe-lookups.patch index 71e1bf3c..ba80230c 100644 --- a/patches/server/0019-Heavily-optimize-furnance-fuel-and-recipe-lookups.patch +++ b/patches/server/0018-Heavily-optimize-furnance-fuel-and-recipe-lookups.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Heavily optimize furnance fuel and recipe lookups Co-authored-by: Mykyta Komarn diff --git a/src/main/java/net/minecraft/server/TileEntityFurnace.java b/src/main/java/net/minecraft/server/TileEntityFurnace.java -index e75e676d196d9f5a3409ec50645fab611b0afdad..fa2b88b54a419f506a195130e664701766720ceb 100644 +index 76ea1d003b43d822e2b85eec3b8740155efd531a..c1d1ce582c94fd20f42b1979d6edbb6b377adff8 100644 --- a/src/main/java/net/minecraft/server/TileEntityFurnace.java +++ b/src/main/java/net/minecraft/server/TileEntityFurnace.java -@@ -283,7 +283,10 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I +@@ -299,7 +299,10 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I this.cookTime = MathHelper.clamp(this.cookTime - 2, 0, this.cookTimeTotal); } } else { @@ -21,7 +21,7 @@ index e75e676d196d9f5a3409ec50645fab611b0afdad..fa2b88b54a419f506a195130e6647017 if (!this.isBurning() && this.canBurn(irecipe)) { // CraftBukkit start -@@ -609,4 +612,18 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I +@@ -627,4 +630,18 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I } } diff --git a/patches/server/0020-Optimize-TileEntity-load-unload.patch b/patches/server/0019-Optimize-TileEntity-load-unload.patch similarity index 93% rename from patches/server/0020-Optimize-TileEntity-load-unload.patch rename to patches/server/0019-Optimize-TileEntity-load-unload.patch index 5c650735..e25c084b 100644 --- a/patches/server/0020-Optimize-TileEntity-load-unload.patch +++ b/patches/server/0019-Optimize-TileEntity-load-unload.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Optimize TileEntity load/unload diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index 2cdfcec68ee66915ea72cccded5f1a2d50c04c30..5759d5e472c089b57ffe9a94c617459ebce77547 100644 +index b47bc7cadd34f4592605c1ecfdfcb33e2d580034..626fab23a727073f502d934fc8f8616326ee8b52 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -42,8 +42,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { diff --git a/patches/server/0021-Global-Eula-file.patch b/patches/server/0020-Global-Eula-file.patch similarity index 93% rename from patches/server/0021-Global-Eula-file.patch rename to patches/server/0020-Global-Eula-file.patch index f9a740e2..0dba7244 100644 --- a/patches/server/0021-Global-Eula-file.patch +++ b/patches/server/0020-Global-Eula-file.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Global Eula file diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java -index 229c3b0f0c650b501f31147adaa17194af57fedd..345f8b811946fd11695dd718d060c5a86cd64703 100644 +index f88cf526d272fe47b5a474c0b344b748ee4009fa..4216448c4cb645fa6c61bad07c99abab5c9cd829 100644 --- a/src/main/java/net/minecraft/server/EULA.java +++ b/src/main/java/net/minecraft/server/EULA.java @@ -15,12 +15,25 @@ public class EULA { diff --git a/patches/server/0022-Redirect-Configs.patch b/patches/server/0021-Redirect-Configs.patch similarity index 94% rename from patches/server/0022-Redirect-Configs.patch rename to patches/server/0021-Redirect-Configs.patch index 6c7e33a9..0d75fe18 100644 --- a/patches/server/0022-Redirect-Configs.patch +++ b/patches/server/0021-Redirect-Configs.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Redirect Configs diff --git a/src/main/java/de/minebench/origami/OrigamiConfig.java b/src/main/java/de/minebench/origami/OrigamiConfig.java -index 004184a7c3da4f72f68a5fd9b4dd5abd0b8f871d..a0ff3cdafbf499802600de2d2174781c45071b38 100644 +index 15aa603a771c327879a4088609850fb86c6347bd..553d6fafbcabafeb008fcf5b60adb8d1a5ffeb6b 100644 --- a/src/main/java/de/minebench/origami/OrigamiConfig.java +++ b/src/main/java/de/minebench/origami/OrigamiConfig.java @@ -20,6 +20,8 @@ public final class OrigamiConfig { @@ -52,7 +52,7 @@ index 0a9f03526abf0638ada15d9810b949887fca9f9a..64b662dc9146d0d414a9668d9b93e07a this.setAllowFlight(dedicatedserverproperties.allowFlight); this.setResourcePack(dedicatedserverproperties.resourcePack, this.ba()); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 82aa5f67ee8ca038094ca922a279d90d78bdac2d..d22e7a502dfc4839e742aa874898292c614b137d 100644 +index e65f4b990c24e6cee850521f9ad02c2bf4c85ad6..756be0886856aabc31e436f82948d3d069f66fef 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -303,7 +303,7 @@ public class Main { diff --git a/patches/server/0023-Add-JsonList-save-timings.patch b/patches/server/0022-Add-JsonList-save-timings.patch similarity index 100% rename from patches/server/0023-Add-JsonList-save-timings.patch rename to patches/server/0022-Add-JsonList-save-timings.patch diff --git a/patches/server/0024-lithium-DataTrackerMixin.patch b/patches/server/0023-lithium-DataTrackerMixin.patch similarity index 100% rename from patches/server/0024-lithium-DataTrackerMixin.patch rename to patches/server/0023-lithium-DataTrackerMixin.patch diff --git a/patches/server/0025-Fix-lead-fall-dmg-config.patch b/patches/server/0024-Fix-lead-fall-dmg-config.patch similarity index 90% rename from patches/server/0025-Fix-lead-fall-dmg-config.patch rename to patches/server/0024-Fix-lead-fall-dmg-config.patch index 735782fe..e397bd69 100644 --- a/patches/server/0025-Fix-lead-fall-dmg-config.patch +++ b/patches/server/0024-Fix-lead-fall-dmg-config.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix lead fall dmg config diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java -index 7dec4d5aff79c138b8b957d0475f90fb116c582f..a7fc8110f524db2b08448e430d043e575bab8480 100644 +index 63eb29635957d4e6ce1274ee17a59af62d442d4e..e629f10cdbd70a354f9edaa75973b169a8588d08 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java -@@ -1292,6 +1292,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke +@@ -1287,6 +1287,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.fallDistance = 0.0F; } else if (d0 < 0.0D) { this.fallDistance = (float) ((double) this.fallDistance - d0); diff --git a/patches/server/0026-Optimize-some-stuff-in-WorldServer-ticking.patch b/patches/server/0025-Optimize-some-stuff-in-WorldServer-ticking.patch similarity index 83% rename from patches/server/0026-Optimize-some-stuff-in-WorldServer-ticking.patch rename to patches/server/0025-Optimize-some-stuff-in-WorldServer-ticking.patch index 020a281a..ff644ef3 100644 --- a/patches/server/0026-Optimize-some-stuff-in-WorldServer-ticking.patch +++ b/patches/server/0025-Optimize-some-stuff-in-WorldServer-ticking.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimize some stuff in WorldServer ticking Replaced some streams and some array lists with glue lists diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 6d783cc424b39993638cb2326c0c9dc3ab493f54..565b41d718eb9a772872f0b8163dd4093fe35750 100644 +index 970a71fb66d235bf772aeddd02c50390ab7e568b..be58626bdd1591758317c9c81cb3a6f2aecfc099 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java -@@ -961,12 +961,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -780,12 +780,21 @@ public class WorldServer extends World implements GeneratorAccessSeed { this.server.getPlayerList().sendAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.i, this.thunderLevel)); } // */ @@ -23,10 +23,10 @@ index 6d783cc424b39993638cb2326c0c9dc3ab493f54..565b41d718eb9a772872f0b8163dd409 + player.tickWeather(); + if (flag != this.isRaining()) player.setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false); + player.updateWeather(this.lastRainLevel, this.rainLevel, this.lastThunderLevel, this.thunderLevel); - } ++ } + if (sleepyMatch && !player.isSpectator() && !player.isDeeplySleeping() && !player.fauxSleeping) { + sleepyMatch = false; -+ } + } + // Yatopia end } @@ -34,20 +34,21 @@ index 6d783cc424b39993638cb2326c0c9dc3ab493f54..565b41d718eb9a772872f0b8163dd409 if (flag != this.isRaining()) { // Only send weather packets to those affected for (int idx = 0; idx < this.players.size(); ++idx) { -@@ -981,10 +990,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -800,11 +809,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { } } // CraftBukkit end -+ */ // Yatopia end ++ */ // Yatopia end - if (this.everyoneSleeping && this.players.stream().noneMatch((entityplayer) -> { -- return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping; // CraftBukkit +- return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping && !(purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk()); // CraftBukkit // Purpur - })) { +- // CraftBukkit start + if (this.everyoneSleeping && sleepyMatch) { // Yatopia - // CraftBukkit start long l = this.worldData.getDayTime() + 24000L; TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (l - l % 24000L) - this.getDayTime()); -@@ -1154,9 +1162,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { + if (this.getGameRules().getBoolean(GameRules.DO_DAYLIGHT_CYCLE)) { +@@ -1009,9 +1016,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { } private void wakeupPlayers() { @@ -59,7 +60,7 @@ index 6d783cc424b39993638cb2326c0c9dc3ab493f54..565b41d718eb9a772872f0b8163dd409 } // Paper start - optimise random block ticking -@@ -1919,8 +1927,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1806,8 +1813,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { // Spigot start if ( entity instanceof EntityHuman ) { @@ -70,7 +71,7 @@ index 6d783cc424b39993638cb2326c0c9dc3ab493f54..565b41d718eb9a772872f0b8163dd409 for (Object o : worldData.data.values() ) { if ( o instanceof WorldMap ) -@@ -1937,7 +1946,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1824,7 +1832,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } } } diff --git a/patches/server/0027-Optimize-BehaviorController.patch b/patches/server/0026-Optimize-BehaviorController.patch similarity index 100% rename from patches/server/0027-Optimize-BehaviorController.patch rename to patches/server/0026-Optimize-BehaviorController.patch diff --git a/patches/server/0028-Add-timings-for-Pathfinder.patch b/patches/server/0027-Add-timings-for-Pathfinder.patch similarity index 84% rename from patches/server/0028-Add-timings-for-Pathfinder.patch rename to patches/server/0027-Add-timings-for-Pathfinder.patch index 7e21f0c2..a208067b 100644 --- a/patches/server/0028-Add-timings-for-Pathfinder.patch +++ b/patches/server/0027-Add-timings-for-Pathfinder.patch @@ -18,7 +18,7 @@ index d8c1a8dd867437443494ac169569139e25c9a635..0efdddc12247ed4aa6acce118c707090 * Gets a timer associated with a plugins tasks. * @param bukkitTask diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java -index 55fa3911703f96cf1f97c82b19d8e2d0d220016b..20780ad78eeb6ae3426e24c655624aa27360e382 100644 +index 1208464fba96daf276c9cc0c1c9b18db75b03abc..ae954c018e47cb114f8706a0cea6f4222b14c161 100644 --- a/src/main/java/net/minecraft/server/NavigationAbstract.java +++ b/src/main/java/net/minecraft/server/NavigationAbstract.java @@ -29,6 +29,7 @@ public abstract class NavigationAbstract { @@ -27,9 +27,9 @@ index 55fa3911703f96cf1f97c82b19d8e2d0d220016b..20780ad78eeb6ae3426e24c655624aa2 private boolean t; + private co.aikar.timings.Timing timing; // Yatopia - public NavigationAbstract(EntityInsentient entityinsentient, World world) { - this.g = Vec3D.ORIGIN; -@@ -40,6 +41,7 @@ public abstract class NavigationAbstract { + // Tuinity start + public boolean isViableForPathRecalculationChecking() { +@@ -47,6 +48,7 @@ public abstract class NavigationAbstract { int i = MathHelper.floor(entityinsentient.b(GenericAttributes.FOLLOW_RANGE) * 16.0D); this.s = this.a(i); @@ -37,7 +37,7 @@ index 55fa3911703f96cf1f97c82b19d8e2d0d220016b..20780ad78eeb6ae3426e24c655624aa2 } public void g() { -@@ -223,6 +225,10 @@ public abstract class NavigationAbstract { +@@ -231,6 +233,10 @@ public abstract class NavigationAbstract { } public void c() { @@ -48,7 +48,7 @@ index 55fa3911703f96cf1f97c82b19d8e2d0d220016b..20780ad78eeb6ae3426e24c655624aa2 ++this.e; if (this.m) { this.j(); -@@ -250,6 +256,11 @@ public abstract class NavigationAbstract { +@@ -258,6 +264,11 @@ public abstract class NavigationAbstract { this.a.getControllerMove().a(vec3d.x, this.b.getType(blockposition.down()).isAir() ? vec3d.y : PathfinderNormal.a((IBlockAccess) this.b, blockposition), vec3d.z, this.d); } } diff --git a/patches/server/0029-Use-offline-uuids-if-we-need-to.patch b/patches/server/0028-Use-offline-uuids-if-we-need-to.patch similarity index 96% rename from patches/server/0029-Use-offline-uuids-if-we-need-to.patch rename to patches/server/0028-Use-offline-uuids-if-we-need-to.patch index 28005fa6..98e63e7e 100644 --- a/patches/server/0029-Use-offline-uuids-if-we-need-to.patch +++ b/patches/server/0028-Use-offline-uuids-if-we-need-to.patch @@ -20,10 +20,10 @@ Thanks to Gabriele C for pointing this issue to us, as he have any interest fixing this. diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index 9796b4e57d6680c9f0dc76decdd985572daafb7e..f2da76f2a937240168fdb02a93dfe226f5b4bf5e 100644 +index 91c2756a8708a2f4154905baec20b9ae484fea0d..a191693b754724f2a5a3cd2a39e5b2171bddb6a8 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java -@@ -1921,7 +1921,7 @@ public abstract class EntityHuman extends EntityLiving { +@@ -1954,7 +1954,7 @@ public abstract class EntityHuman extends EntityLiving { public static UUID a(GameProfile gameprofile) { UUID uuid = gameprofile.getId(); diff --git a/patches/server/0030-Highly-optimize-VillagePlace-filtering.patch b/patches/server/0029-Highly-optimize-VillagePlace-filtering.patch similarity index 95% rename from patches/server/0030-Highly-optimize-VillagePlace-filtering.patch rename to patches/server/0029-Highly-optimize-VillagePlace-filtering.patch index ca1293c8..740fa31a 100644 --- a/patches/server/0030-Highly-optimize-VillagePlace-filtering.patch +++ b/patches/server/0029-Highly-optimize-VillagePlace-filtering.patch @@ -67,7 +67,7 @@ index 0000000000000000000000000000000000000000..e647624f4c9afe8bc603792ad88c807e + } +} diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java -index 6fcc7ed7c129e6a33386d65b37cbba4a44e96f0f..aac055489291cd742bbbfd0aeb0b8face4040e17 100644 +index bc61aaff65a7dc1e7534452b285953b83adb7000..7fddef0afbcf1f9f391c540b8fce1bebf8faa452 100644 --- a/src/main/java/net/minecraft/server/BlockPosition.java +++ b/src/main/java/net/minecraft/server/BlockPosition.java @@ -341,6 +341,16 @@ public class BlockPosition extends BaseBlockPosition { @@ -108,10 +108,10 @@ index 271fddbbf73ca5c0e4e2722d7246c14b778d6072..860943989b10a4f65c0de345a56aaa00 int i = Math.abs(chunkcoordintpair.x - chunkcoordintpair1.x) + 1; int j = Math.abs(chunkcoordintpair.z - chunkcoordintpair1.z) + 1; diff --git a/src/main/java/net/minecraft/server/EntityBee.java b/src/main/java/net/minecraft/server/EntityBee.java -index f73641ddb3e82bc653732105ef0a3d41a28e845f..cd9b52d0233caa6ad60b90e2049c6247f6e1954b 100644 +index bef806bfc60792a990209b57f3cd4b9da0c24acc..6aee58fb0c6b9c1ae4ac6157e02a5050cd835536 100644 --- a/src/main/java/net/minecraft/server/EntityBee.java +++ b/src/main/java/net/minecraft/server/EntityBee.java -@@ -720,15 +720,19 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB +@@ -781,15 +781,19 @@ public class EntityBee extends EntityAnimal implements IEntityAngerable, EntityB private List j() { BlockPosition blockposition = EntityBee.this.getChunkCoordinates(); VillagePlace villageplace = ((WorldServer) EntityBee.this.world).y(); @@ -135,10 +135,10 @@ index f73641ddb3e82bc653732105ef0a3d41a28e845f..cd9b52d0233caa6ad60b90e2049c6247 } diff --git a/src/main/java/net/minecraft/server/PersistentRaid.java b/src/main/java/net/minecraft/server/PersistentRaid.java -index 826dcf9f7eedc3664d66170b97b2a19552a0dc60..81f9b2ec160a015ce7e82ba33dd684e0b3932877 100644 +index 807910c60e6cad58b91474b0477e6fc109eaf281..f003f95aca2c9a41b548c8ca2de1194818268c80 100644 --- a/src/main/java/net/minecraft/server/PersistentRaid.java +++ b/src/main/java/net/minecraft/server/PersistentRaid.java -@@ -68,7 +68,7 @@ public class PersistentRaid extends PersistentBase { +@@ -80,7 +80,7 @@ public class PersistentRaid extends PersistentBase { return null; } else { BlockPosition blockposition = entityplayer.getChunkCoordinates(); @@ -148,7 +148,7 @@ index 826dcf9f7eedc3664d66170b97b2a19552a0dc60..81f9b2ec160a015ce7e82ba33dd684e0 Vec3D vec3d = Vec3D.ORIGIN; diff --git a/src/main/java/net/minecraft/server/SectionPosition.java b/src/main/java/net/minecraft/server/SectionPosition.java -index f95925f1c5d091f1a129d0437bb6e175c6ac080f..11ba5c18d761f6cc76faa3e0f5de3d5b4175749d 100644 +index 0bb3ad0bffc04eba38cd827eaf5c63e8bf2aee93..8e90fabb0681457ec140f8553799463023a7a03b 100644 --- a/src/main/java/net/minecraft/server/SectionPosition.java +++ b/src/main/java/net/minecraft/server/SectionPosition.java @@ -159,6 +159,7 @@ public class SectionPosition extends BaseBlockPosition { @@ -192,19 +192,19 @@ index f95925f1c5d091f1a129d0437bb6e175c6ac080f..11ba5c18d761f6cc76faa3e0f5de3d5b return StreamSupport.stream(new AbstractSpliterator((long) ((l - i + 1) * (i1 - j + 1) * (j1 - k + 1)), 64) { final CursorPosition a = new CursorPosition(i, j, k, l, i1, j1); diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java -index 0094babbd59cc81554b9480088464d632824ae8e..5db147b5c602a00fa758ab5ddc8f18d9a5bbb6ad 100644 +index 44063d599e39e087b3eccfe204ef2fd79c3364e5..17db6fb6c8f2be2ae37be92891603a889cb7dbfd 100644 --- a/src/main/java/net/minecraft/server/VillagePlace.java +++ b/src/main/java/net/minecraft/server/VillagePlace.java -@@ -47,7 +47,7 @@ public class VillagePlace extends RegionFileSection { - } +@@ -180,7 +180,7 @@ public class VillagePlace extends RegionFileSection { + public long count(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { return a(predicate, blockposition, i, villageplace_occupancy); } // Purpur - OBFHELPER public long a(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { - return this.c(predicate, blockposition, i, villageplace_occupancy).count(); + return this.cList(predicate, blockposition, i, villageplace_occupancy).size(); // Yatopia } public boolean a(VillagePlaceType villageplacetype, BlockPosition blockposition) { -@@ -68,6 +68,39 @@ public class VillagePlace extends RegionFileSection { +@@ -201,6 +201,39 @@ public class VillagePlace extends RegionFileSection { }); } @@ -244,7 +244,7 @@ index 0094babbd59cc81554b9480088464d632824ae8e..5db147b5c602a00fa758ab5ddc8f18d9 public Stream c(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { int j = i * i; -@@ -84,10 +117,28 @@ public class VillagePlace extends RegionFileSection { +@@ -217,10 +250,28 @@ public class VillagePlace extends RegionFileSection { }); } @@ -273,7 +273,7 @@ index 0094babbd59cc81554b9480088464d632824ae8e..5db147b5c602a00fa758ab5ddc8f18d9 public Stream b(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { return this.a(predicate, predicate1, blockposition, i, villageplace_occupancy).sorted(Comparator.comparingDouble((blockposition1) -> { return blockposition1.j(blockposition); -@@ -95,31 +146,68 @@ public class VillagePlace extends RegionFileSection { +@@ -228,31 +279,68 @@ public class VillagePlace extends RegionFileSection { } public Optional c(Predicate predicate, Predicate predicate1, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { @@ -344,7 +344,7 @@ index 0094babbd59cc81554b9480088464d632824ae8e..5db147b5c602a00fa758ab5ddc8f18d9 } public boolean b(BlockPosition blockposition) { -@@ -213,7 +301,7 @@ public class VillagePlace extends RegionFileSection { +@@ -352,7 +440,7 @@ public class VillagePlace extends RegionFileSection { } private void a(ChunkSection chunksection, SectionPosition sectionposition, BiConsumer biconsumer) { @@ -353,7 +353,7 @@ index 0094babbd59cc81554b9480088464d632824ae8e..5db147b5c602a00fa758ab5ddc8f18d9 IBlockData iblockdata = chunksection.getType(SectionPosition.b(blockposition.getX()), SectionPosition.b(blockposition.getY()), SectionPosition.b(blockposition.getZ())); VillagePlaceType.b(iblockdata).ifPresent((villageplacetype) -> { -@@ -223,6 +311,16 @@ public class VillagePlace extends RegionFileSection { +@@ -362,6 +450,16 @@ public class VillagePlace extends RegionFileSection { } public void a(IWorldReader iworldreader, BlockPosition blockposition, int i) { @@ -370,7 +370,7 @@ index 0094babbd59cc81554b9480088464d632824ae8e..5db147b5c602a00fa758ab5ddc8f18d9 SectionPosition.b(new ChunkCoordIntPair(blockposition), Math.floorDiv(i, 16)).map((sectionposition) -> { return Pair.of(sectionposition, this.d(sectionposition.s())); }).filter((pair) -> { -@@ -234,6 +332,7 @@ public class VillagePlace extends RegionFileSection { +@@ -373,6 +471,7 @@ public class VillagePlace extends RegionFileSection { }).forEach((chunkcoordintpair) -> { iworldreader.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.EMPTY); }); diff --git a/patches/server/0031-Optimise-portals.patch b/patches/server/0030-Optimise-portals.patch similarity index 95% rename from patches/server/0031-Optimise-portals.patch rename to patches/server/0030-Optimise-portals.patch index 85b82aa8..fe4a89f4 100644 --- a/patches/server/0031-Optimise-portals.patch +++ b/patches/server/0030-Optimise-portals.patch @@ -45,10 +45,10 @@ index e10995ec30dd9a10d781b3c1709fd2db5a9becdd..456af55b5908f7192a63c6b15fb93a34 return optional.map((villageplacerecord) -> { BlockPosition blockposition1 = villageplacerecord.f(); diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java -index 5db147b5c602a00fa758ab5ddc8f18d9a5bbb6ad..a92bdf5ceece3160d31039360b39b935c30f3ff7 100644 +index 17db6fb6c8f2be2ae37be92891603a889cb7dbfd..8849336437d91022cc33c704059d8b075882bbf3 100644 --- a/src/main/java/net/minecraft/server/VillagePlace.java +++ b/src/main/java/net/minecraft/server/VillagePlace.java -@@ -81,6 +81,18 @@ public class VillagePlace extends RegionFileSection { +@@ -214,6 +214,18 @@ public class VillagePlace extends RegionFileSection { } return ret; } @@ -67,7 +67,7 @@ index 5db147b5c602a00fa758ab5ddc8f18d9a5bbb6ad..a92bdf5ceece3160d31039360b39b935 public java.util.List cList(Predicate predicate, BlockPosition pos, int i, VillagePlace.Occupancy occupancy) { int j = i * i; java.util.List ret = new java.util.ArrayList<>(); -@@ -335,6 +347,18 @@ public class VillagePlace extends RegionFileSection { +@@ -474,6 +486,18 @@ public class VillagePlace extends RegionFileSection { */ // Yatopia end } diff --git a/patches/server/0032-Nuke-streams-off-BlockPosition.patch b/patches/server/0031-Nuke-streams-off-BlockPosition.patch similarity index 74% rename from patches/server/0032-Nuke-streams-off-BlockPosition.patch rename to patches/server/0031-Nuke-streams-off-BlockPosition.patch index 09bb1516..e24c8032 100644 --- a/patches/server/0032-Nuke-streams-off-BlockPosition.patch +++ b/patches/server/0031-Nuke-streams-off-BlockPosition.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Nuke streams off BlockPosition diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java -index fdfd955aad65b758696b72a71521ec74e9aa122a..7c3e792b53329c869fbd21e298961b3f254c6ef5 100644 +index 8724ad342bec7c733b3c825bd62dbfa5c28c06dd..9907047028b754fe0e314a7d5c5238ce4286c373 100644 --- a/src/main/java/net/minecraft/server/BlockBase.java +++ b/src/main/java/net/minecraft/server/BlockBase.java -@@ -639,6 +639,7 @@ public abstract class BlockBase { +@@ -681,6 +681,7 @@ public abstract class BlockBase { return this.getBlock().getInventory(this.p(), world, blockposition); } @@ -16,16 +16,8 @@ index fdfd955aad65b758696b72a71521ec74e9aa122a..7c3e792b53329c869fbd21e298961b3f public boolean a(Tag tag) { return this.getBlock().a(tag); } -@@ -647,6 +648,7 @@ public abstract class BlockBase { - return this.getBlock().a(tag) && predicate.test(this); - } - -+ public final boolean isBlock(Block block) { return a(block); } // Yatopia - OBFHELPER - public boolean a(Block block) { - return this.getBlock().a(block); - } diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java -index aac055489291cd742bbbfd0aeb0b8face4040e17..34d0240903deb43ff486daa35a04a4b33788e474 100644 +index 7fddef0afbcf1f9f391c540b8fce1bebf8faa452..f13b4e6ec815792c2f2b49193707da94df427424 100644 --- a/src/main/java/net/minecraft/server/BlockPosition.java +++ b/src/main/java/net/minecraft/server/BlockPosition.java @@ -318,7 +318,15 @@ public class BlockPosition extends BaseBlockPosition { diff --git a/patches/server/0033-Nuke-streams-off-SectionPosition.patch b/patches/server/0032-Nuke-streams-off-SectionPosition.patch similarity index 100% rename from patches/server/0033-Nuke-streams-off-SectionPosition.patch rename to patches/server/0032-Nuke-streams-off-SectionPosition.patch diff --git a/patches/server/0034-Stop-wasting-resources-on-JsonList-get.patch b/patches/server/0033-Stop-wasting-resources-on-JsonList-get.patch similarity index 97% rename from patches/server/0034-Stop-wasting-resources-on-JsonList-get.patch rename to patches/server/0033-Stop-wasting-resources-on-JsonList-get.patch index c596dcc0..e0337a4a 100644 --- a/patches/server/0034-Stop-wasting-resources-on-JsonList-get.patch +++ b/patches/server/0033-Stop-wasting-resources-on-JsonList-get.patch @@ -55,7 +55,7 @@ index 4094ef76b7b05de1bfcc28aa0ef13033abadeb7e..0224a6d0e47e836fa485b39e7b4ce5b8 Throwable throwable = null; diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index 99a67a46bf060aa186a64b4d0608fc60ede609e4..5ef0678c07b04f4b041b3a263d335fb44a14b945 100644 +index 14d8492c405db32e454bc363207a127167432a61..f354712e8e1cd359bc8fb0461f30214b8db24112 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -647,6 +647,7 @@ public abstract class PlayerList { diff --git a/patches/server/0035-ProxyForwardDataEvent.patch b/patches/server/0034-ProxyForwardDataEvent.patch similarity index 98% rename from patches/server/0035-ProxyForwardDataEvent.patch rename to patches/server/0034-ProxyForwardDataEvent.patch index 2cbaed4c..c98ee024 100644 --- a/patches/server/0035-ProxyForwardDataEvent.patch +++ b/patches/server/0034-ProxyForwardDataEvent.patch @@ -83,7 +83,7 @@ index d987483255195c0bde713a92676baced1eaff2b3..bb45fc83d81948c84bc721961474e5e8 } catch (Exception ex) { disconnect("Failed to verify username!"); diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java -index 6a0ec0105399066dede622b45c9471b32c162cf6..8ae1378e4cb5694130630943a84884dddecb9b3b 100644 +index 548c62a838848a9183e14f91b21a9dc309d8a3b2..08227ab446d6332af76491a063653f7f13f43560 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -51,6 +51,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { diff --git a/patches/server/0036-Fix-LightEngineThreaded-memory-leak.patch b/patches/server/0035-Fix-LightEngineThreaded-memory-leak.patch similarity index 79% rename from patches/server/0036-Fix-LightEngineThreaded-memory-leak.patch rename to patches/server/0035-Fix-LightEngineThreaded-memory-leak.patch index 733bd5b1..8e65e146 100644 --- a/patches/server/0036-Fix-LightEngineThreaded-memory-leak.patch +++ b/patches/server/0035-Fix-LightEngineThreaded-memory-leak.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix LightEngineThreaded memory leak diff --git a/src/main/java/net/minecraft/server/LightEngineThreaded.java b/src/main/java/net/minecraft/server/LightEngineThreaded.java -index 2f9c97dd4e1d705a87772d18c7ab4883a876af08..f3494ac1ad659352ca5595adf9e6919bdb4018d0 100644 +index 168fe23177dfaa401396c1e460f56273ee0a59e4..4141cf02690e13350a10cdfa5e09344c56703f04 100644 --- a/src/main/java/net/minecraft/server/LightEngineThreaded.java +++ b/src/main/java/net/minecraft/server/LightEngineThreaded.java -@@ -104,6 +104,8 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { +@@ -110,6 +110,8 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { } } @@ -18,10 +18,10 @@ index 2f9c97dd4e1d705a87772d18c7ab4883a876af08..f3494ac1ad659352ca5595adf9e6919b return this.size == 0 && this.pendingTasks.isEmpty(); } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 565b41d718eb9a772872f0b8163dd4093fe35750..7961470df11789b8452c134b242d02262357df69 100644 +index be58626bdd1591758317c9c81cb3a6f2aecfc099..40e87f3043da14b1265fcdadde02bef3b261a9d1 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java -@@ -1906,6 +1906,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { +@@ -1792,6 +1792,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } // Paper end diff --git a/patches/server/0037-Respect-PlayerKickEvent-leaveMessage.patch b/patches/server/0036-Respect-PlayerKickEvent-leaveMessage.patch similarity index 89% rename from patches/server/0037-Respect-PlayerKickEvent-leaveMessage.patch rename to patches/server/0036-Respect-PlayerKickEvent-leaveMessage.patch index 41da351c..55de424f 100644 --- a/patches/server/0037-Respect-PlayerKickEvent-leaveMessage.patch +++ b/patches/server/0036-Respect-PlayerKickEvent-leaveMessage.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Respect PlayerKickEvent leaveMessage diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index 9d058ddf875e660cb3bd5209e5ddbf1a7abbd04c..63bceb9f1695ce4db5b932ca627d944506b03c0d 100644 +index 75d955948a407d94e6f3a88f86afa8b1d6ba33cb..41f4528cbcf5e5f98b1fba1cd6bae0987405cc21 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java -@@ -323,7 +323,7 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -329,7 +329,7 @@ public class PlayerConnection implements PacketListenerPlayIn { this.networkManager.sendPacket(new PacketPlayOutKickDisconnect(ichatbasecomponent), (future) -> { this.networkManager.close(ichatbasecomponent); }); @@ -17,7 +17,7 @@ index 9d058ddf875e660cb3bd5209e5ddbf1a7abbd04c..63bceb9f1695ce4db5b932ca627d9445 this.networkManager.stopReading(); MinecraftServer minecraftserver = this.minecraftServer; NetworkManager networkmanager = this.networkManager; -@@ -1691,6 +1691,11 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -1759,6 +1759,11 @@ public class PlayerConnection implements PacketListenerPlayIn { @Override public void a(IChatBaseComponent ichatbasecomponent) { @@ -29,7 +29,7 @@ index 9d058ddf875e660cb3bd5209e5ddbf1a7abbd04c..63bceb9f1695ce4db5b932ca627d9445 // CraftBukkit start - Rarely it would send a disconnect line twice if (this.processedDisconnect) { return; -@@ -1706,7 +1711,7 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -1774,7 +1779,7 @@ public class PlayerConnection implements PacketListenerPlayIn { */ this.player.p(); @@ -39,7 +39,7 @@ index 9d058ddf875e660cb3bd5209e5ddbf1a7abbd04c..63bceb9f1695ce4db5b932ca627d9445 this.minecraftServer.getPlayerList().sendMessage(CraftChatMessage.fromString(quitMessage)); } diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index 5ef0678c07b04f4b041b3a263d335fb44a14b945..5afc424481106570b84a9b38c94fe397f1aff636 100644 +index f354712e8e1cd359bc8fb0461f30214b8db24112..7e42654873195d17c9a5a2a718216a943533e658 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -499,6 +499,11 @@ public abstract class PlayerList { diff --git a/patches/server/0038-Shutdown-Bootstrap-thread-pool.patch b/patches/server/0037-Shutdown-Bootstrap-thread-pool.patch similarity index 100% rename from patches/server/0038-Shutdown-Bootstrap-thread-pool.patch rename to patches/server/0037-Shutdown-Bootstrap-thread-pool.patch diff --git a/patches/server/0039-Optimize-Villagers.patch b/patches/server/0038-Optimize-Villagers.patch similarity index 93% rename from patches/server/0039-Optimize-Villagers.patch rename to patches/server/0038-Optimize-Villagers.patch index ca163257..67b01e91 100644 --- a/patches/server/0039-Optimize-Villagers.patch +++ b/patches/server/0038-Optimize-Villagers.patch @@ -69,7 +69,7 @@ index 8d445e9c0875db6cf45e4d8bcfce7cd3d5094d94..4afe59084b85f466c8fb2fac2ac77e50 } } diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java -index a5718af9b614ae505067131f04ebb490617d6aa4..2ea0cfad4b35264cd3b70b930dd28de58c77d0c0 100644 +index b6b4c8c491d692f93d2c38d602ff99b0611b72aa..082b060407f2424c10f865e23feb60b970b851cb 100644 --- a/src/main/java/net/minecraft/server/VillagePlaceType.java +++ b/src/main/java/net/minecraft/server/VillagePlaceType.java @@ -14,11 +14,20 @@ import java.util.stream.Collectors; @@ -94,16 +94,8 @@ index a5718af9b614ae505067131f04ebb490617d6aa4..2ea0cfad4b35264cd3b70b930dd28de5 }; public static final Predicate b = (villageplacetype) -> { return true; -@@ -83,6 +92,7 @@ public class VillagePlaceType { - return this.D; - } - -+ public final Predicate getCompletionCondition() { return c(); } // Yatopia - OBFHELPER - public Predicate c() { - return this.E; - } diff --git a/src/main/java/net/minecraft/server/VillagerProfession.java b/src/main/java/net/minecraft/server/VillagerProfession.java -index 3c60da7ac6faebe9d964e893974e42613c59b4c1..1b012914cb3fcbc4bb456195ade96668b6742cfd 100644 +index 6493f220a0cf627e82e5f3f3c85e9934d9a9ebae..f4c8ba2d94e5875197b674a0e39e997b45186d63 100644 --- a/src/main/java/net/minecraft/server/VillagerProfession.java +++ b/src/main/java/net/minecraft/server/VillagerProfession.java @@ -35,6 +35,7 @@ public class VillagerProfession { diff --git a/patches/server/0040-Optimize-whitelist-command-for-multiple-additions-re.patch b/patches/server/0039-Optimize-whitelist-command-for-multiple-additions-re.patch similarity index 96% rename from patches/server/0040-Optimize-whitelist-command-for-multiple-additions-re.patch rename to patches/server/0039-Optimize-whitelist-command-for-multiple-additions-re.patch index 0cf26727..9fe5331d 100644 --- a/patches/server/0040-Optimize-whitelist-command-for-multiple-additions-re.patch +++ b/patches/server/0039-Optimize-whitelist-command-for-multiple-additions-re.patch @@ -111,10 +111,10 @@ index 893d2c1c74ed28dcdb83b71762ccdcbfd50a8f9d..107091a4cae0e4eaba93f69ae91239ab private static int b(CommandListenerWrapper commandlistenerwrapper) throws CommandSyntaxException { diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index f2da76f2a937240168fdb02a93dfe226f5b4bf5e..dce54e84ded2b81be85489aab919de0ee3ca3c3b 100644 +index a191693b754724f2a5a3cd2a39e5b2171bddb6a8..434cfd0a224a8dc1f13809edcd4b98f0c7aa0899 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java -@@ -77,6 +77,7 @@ public abstract class EntityHuman extends EntityLiving { +@@ -78,6 +78,7 @@ public abstract class EntityHuman extends EntityLiving { // CraftBukkit start public boolean fauxSleeping; public int oldLevel = -1; @@ -122,7 +122,7 @@ index f2da76f2a937240168fdb02a93dfe226f5b4bf5e..dce54e84ded2b81be85489aab919de0e @Override public CraftHumanEntity getBukkitEntity() { -@@ -1890,6 +1891,15 @@ public abstract class EntityHuman extends EntityLiving { +@@ -1923,6 +1924,15 @@ public abstract class EntityHuman extends EntityLiving { return this.getProfile().getName(); } @@ -185,10 +185,10 @@ index 0224a6d0e47e836fa485b39e7b4ce5b83ea554bf..fe578d306575bbdc8ca4a993a648e889 return (String[]) this.d.keySet().toArray(new String[this.d.size()]); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 6f1a6ee340577d25b5edeb28eb2d2c52af511957..ab4315f7c22068aae7fe082d685adac82aeba660 100644 +index 240e230725ca685389fe39dfa60ac3ca22d3faa0..9a6bb7b6cb4d4aabae6f7e7915f993e4ebd6f77f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1975,6 +1975,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant +From: Zoe Date: Sun, 27 Sep 2020 17:07:16 -0500 Subject: [PATCH] Add IntelliJ IDEA runnable diff --git a/patches/server/0044-Fix-IndexOutOfBoundsException-when-sending-too-many-.patch b/patches/server/0043-Fix-IndexOutOfBoundsException-when-sending-too-many-.patch similarity index 100% rename from patches/server/0044-Fix-IndexOutOfBoundsException-when-sending-too-many-.patch rename to patches/server/0043-Fix-IndexOutOfBoundsException-when-sending-too-many-.patch diff --git a/patches/server/0045-Add-nspt-command.patch b/patches/server/0044-Add-nspt-command.patch similarity index 98% rename from patches/server/0045-Add-nspt-command.patch rename to patches/server/0044-Add-nspt-command.patch index 48092bfe..dd0c0052 100644 --- a/patches/server/0045-Add-nspt-command.patch +++ b/patches/server/0044-Add-nspt-command.patch @@ -17,7 +17,7 @@ index 64b662dc9146d0d414a9668d9b93e07aa6665f32..2473eb88ec7be3f4935debe04eeabcc0 de.minebench.origami.OrigamiConfig.init((java.io.File) options.valueOf("origami-settings")); this.setPVP(dedicatedserverproperties.pvp); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d5d32012687355ba8c0c048bbf2f14a62bcb8b62..0355cfd79e0a235f4e227dbeea1cff1ad9b877cd 100644 +index 3d52621bf068517b2d959c65aea211f93b8d4497..7d0f73c83a7f7f3c6becf3ed4348c6b2938a86b1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -921,6 +921,7 @@ public final class CraftServer implements Server { diff --git a/patches/server/0046-Configurable-flight-checks.patch b/patches/server/0045-Configurable-flight-checks.patch similarity index 96% rename from patches/server/0046-Configurable-flight-checks.patch rename to patches/server/0045-Configurable-flight-checks.patch index fe94b3a0..4a0ce225 100644 --- a/patches/server/0046-Configurable-flight-checks.patch +++ b/patches/server/0045-Configurable-flight-checks.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable flight checks diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index 63bceb9f1695ce4db5b932ca627d944506b03c0d..4e343bf4865655020994c40a1c7101afb994a659 100644 +index 41f4528cbcf5e5f98b1fba1cd6bae0987405cc21..540d16cb01e70fe46bce3a3bb4cdfd1841b3d155 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -179,7 +179,7 @@ public class PlayerConnection implements PacketListenerPlayIn { diff --git a/patches/server/0047-Heavily-optimize-recipe-lookups-in-CraftingManager.patch b/patches/server/0046-Heavily-optimize-recipe-lookups-in-CraftingManager.patch similarity index 98% rename from patches/server/0047-Heavily-optimize-recipe-lookups-in-CraftingManager.patch rename to patches/server/0046-Heavily-optimize-recipe-lookups-in-CraftingManager.patch index edf00742..e1de719e 100644 --- a/patches/server/0047-Heavily-optimize-recipe-lookups-in-CraftingManager.patch +++ b/patches/server/0046-Heavily-optimize-recipe-lookups-in-CraftingManager.patch @@ -152,10 +152,10 @@ index 58ecbe1e20581dc9e78cdd2f4ece29cfa014da8a..3da86dc56f33e4f1900f6b4f66ca6696 for (Recipes recipeType : IRegistry.RECIPE_TYPE) { this.recipes.put(recipeType, new Object2ObjectLinkedOpenHashMap<>()); diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java -index afa1dc693bc2e2e68294a1d3dec1c078ea95b286..feeb6b193c47703ec217d9933c526aa2b29080d7 100644 +index 3f9062d8eca3ce53c0fb9e9e40330aa4e3296c9a..ba16239dd351341bc97a8484369d97ecb655b52b 100644 --- a/src/main/java/net/minecraft/server/ItemStack.java +++ b/src/main/java/net/minecraft/server/ItemStack.java -@@ -570,6 +570,7 @@ public final class ItemStack { +@@ -585,6 +585,7 @@ public final class ItemStack { return !this.e() ? this.doMaterialsMatch(itemstack) : !itemstack.isEmpty() && this.getItem() == itemstack.getItem(); } diff --git a/patches/server/0048-Improve-task-performance.patch b/patches/server/0047-Improve-task-performance.patch similarity index 100% rename from patches/server/0048-Improve-task-performance.patch rename to patches/server/0047-Improve-task-performance.patch diff --git a/patches/server/0049-Optimize-advancement-loading.patch b/patches/server/0048-Optimize-advancement-loading.patch similarity index 91% rename from patches/server/0049-Optimize-advancement-loading.patch rename to patches/server/0048-Optimize-advancement-loading.patch index bf6a4c45..37a28865 100644 --- a/patches/server/0049-Optimize-advancement-loading.patch +++ b/patches/server/0048-Optimize-advancement-loading.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Optimize advancement loading Removed some object allocations and reduced loops diff --git a/src/main/java/net/minecraft/server/Advancement.java b/src/main/java/net/minecraft/server/Advancement.java -index c405047c00d354bbc1449fd2f917b73f980ef1a5..8b31098032dc7a08d201e20c917f27348dd2f437 100644 +index 384d4090f8ff1ea718de16affa5c146a2f58d28a..486034a826b67c71dc7d07852013dc07fefadb0f 100644 --- a/src/main/java/net/minecraft/server/Advancement.java +++ b/src/main/java/net/minecraft/server/Advancement.java @@ -68,6 +68,7 @@ public class Advancement { @@ -18,7 +18,7 @@ index c405047c00d354bbc1449fd2f917b73f980ef1a5..8b31098032dc7a08d201e20c917f2734 return this.rewards; } diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java -index c680319e4040be2b60795b22a5e65d6444cc67ed..18e80feae122f9b90e61b66e0f660d15585fe3bb 100644 +index eaa1063ff2bc5621e93043c4de41ca62f1323fde..96069438ad3f4b00e2996247c222153228cbc2e1 100644 --- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java @@ -160,11 +160,16 @@ public class AdvancementDataPlayer { @@ -38,7 +38,7 @@ index c680319e4040be2b60795b22a5e65d6444cc67ed..18e80feae122f9b90e61b66e0f660d15 Advancement advancement = advancementdataworld.a((MinecraftKey) entry.getKey()); if (advancement == null) { -@@ -201,11 +206,37 @@ public class AdvancementDataPlayer { +@@ -202,11 +207,37 @@ public class AdvancementDataPlayer { } } @@ -76,7 +76,7 @@ index c680319e4040be2b60795b22a5e65d6444cc67ed..18e80feae122f9b90e61b66e0f660d15 public void b() { if (org.spigotmc.SpigotConfig.disableAdvancementSaving) return; // Spigot Map map = Maps.newHashMap(); -@@ -330,6 +361,11 @@ public class AdvancementDataPlayer { +@@ -331,6 +362,11 @@ public class AdvancementDataPlayer { AdvancementProgress advancementprogress = this.getProgress(advancement); if (!advancementprogress.isDone()) { @@ -88,7 +88,7 @@ index c680319e4040be2b60795b22a5e65d6444cc67ed..18e80feae122f9b90e61b66e0f660d15 Iterator iterator = advancement.getCriteria().entrySet().iterator(); while (iterator.hasNext()) { -@@ -349,7 +385,7 @@ public class AdvancementDataPlayer { +@@ -350,7 +386,7 @@ public class AdvancementDataPlayer { } } @@ -97,7 +97,7 @@ index c680319e4040be2b60795b22a5e65d6444cc67ed..18e80feae122f9b90e61b66e0f660d15 } private void d(Advancement advancement) { -@@ -444,6 +480,7 @@ public class AdvancementDataPlayer { +@@ -445,6 +481,7 @@ public class AdvancementDataPlayer { this.data.put(advancement, advancementprogress); } diff --git a/patches/server/0050-lithium-PerlinNoiseSamplerMixin.patch b/patches/server/0049-lithium-PerlinNoiseSamplerMixin.patch similarity index 100% rename from patches/server/0050-lithium-PerlinNoiseSamplerMixin.patch rename to patches/server/0049-lithium-PerlinNoiseSamplerMixin.patch diff --git a/patches/server/0051-lithium-VoronoiBiomeAccessTypeMixin.patch b/patches/server/0050-lithium-VoronoiBiomeAccessTypeMixin.patch similarity index 100% rename from patches/server/0051-lithium-VoronoiBiomeAccessTypeMixin.patch rename to patches/server/0050-lithium-VoronoiBiomeAccessTypeMixin.patch diff --git a/patches/server/0052-lithium-NoiseChunkGeneratorMixin.patch b/patches/server/0051-lithium-NoiseChunkGeneratorMixin.patch similarity index 100% rename from patches/server/0052-lithium-NoiseChunkGeneratorMixin.patch rename to patches/server/0051-lithium-NoiseChunkGeneratorMixin.patch diff --git a/patches/server/0053-lithium-reduce-allocations.patch b/patches/server/0052-lithium-reduce-allocations.patch similarity index 100% rename from patches/server/0053-lithium-reduce-allocations.patch rename to patches/server/0052-lithium-reduce-allocations.patch diff --git a/patches/server/0054-Smarter-statistics-ticking.patch b/patches/server/0053-Smarter-statistics-ticking.patch similarity index 94% rename from patches/server/0054-Smarter-statistics-ticking.patch rename to patches/server/0053-Smarter-statistics-ticking.patch index 371766a5..c041716d 100644 --- a/patches/server/0054-Smarter-statistics-ticking.patch +++ b/patches/server/0053-Smarter-statistics-ticking.patch @@ -8,10 +8,10 @@ In vanilla, statistics that count time spent for an action (i.e. time played or With an interval of 20, this patch saves roughly 3ms per tick on a server w/ 80 players online. diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index 8434e220ca8d65579eb03caa0b1fc0883ccdf874..7cb937cf03551d5c265a75cb0a52413946506402 100644 +index d2b3db34d69269a220185d203bc1232042a9e437..c9f490e4e4e7a5a3a9ad99f864ff8fb2acc5b5b2 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java -@@ -163,18 +163,23 @@ public abstract class EntityHuman extends EntityLiving { +@@ -175,18 +175,23 @@ public abstract class EntityHuman extends EntityLiving { this.p(); if (!this.world.isClientSide) { this.foodData.a(this); diff --git a/patches/server/0055-Configurable-criterion-triggers.patch b/patches/server/0054-Configurable-criterion-triggers.patch similarity index 91% rename from patches/server/0055-Configurable-criterion-triggers.patch rename to patches/server/0054-Configurable-criterion-triggers.patch index 36a5429a..93ce170a 100644 --- a/patches/server/0055-Configurable-criterion-triggers.patch +++ b/patches/server/0054-Configurable-criterion-triggers.patch @@ -6,10 +6,10 @@ 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. diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index de0923395dce3571e604b24d420da48cbe5b9cf4..f1333f2fcdf9f934645dc8b2d086f8cb2d6c72c7 100644 +index 7596eaf605bf73dd44c06b66bcc0e5a36242fe7a..46813a0a65977233acdabb225552e8cfc5d06ab9 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java -@@ -433,6 +433,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -435,6 +435,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { @Override protected void a(IBlockData iblockdata) { @@ -17,7 +17,7 @@ index de0923395dce3571e604b24d420da48cbe5b9cf4..f1333f2fcdf9f934645dc8b2d086f8cb CriterionTriggers.d.a(this, iblockdata); } -@@ -575,7 +576,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { +@@ -765,7 +766,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.playerConnection.sendPacket(new PacketPlayOutExperience(this.exp, this.expTotal, this.expLevel)); } diff --git a/patches/server/0056-Configurable-BlockPhysicsEvent.patch b/patches/server/0055-Configurable-BlockPhysicsEvent.patch similarity index 83% rename from patches/server/0056-Configurable-BlockPhysicsEvent.patch rename to patches/server/0055-Configurable-BlockPhysicsEvent.patch index 2382cd83..bff63dfa 100644 --- a/patches/server/0056-Configurable-BlockPhysicsEvent.patch +++ b/patches/server/0055-Configurable-BlockPhysicsEvent.patch @@ -8,18 +8,18 @@ Paper does alleviate this quite well by only firing if plugins are listening, bu This patch implements a hard toggle for the event. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index ab4315f7c22068aae7fe082d685adac82aeba660..dac1e68dc8dac578605470c148fd028792d54932 100644 +index 9a6bb7b6cb4d4aabae6f7e7915f993e4ebd6f77f..954462353d35c06b0568c56669e31d0ad15bcf67 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1411,7 +1411,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper + worldserver.hasPhysicsEvent = org.yatopiamc.yatopia.server.YatopiaConfig.fireBlockPhysicsEvent && org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper // Yatopia + worldserver.hasEntityMoveEvent = net.pl3x.purpur.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur + worldserver.hasRidableMoveEvent = net.pl3x.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - - this.methodProfiler.a(() -> { diff --git a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java b/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java index 5578f5784a87019d3f091173b25b5ec1cc059fd6..4f99aa4b578333795296da2424f38aae65a808a3 100644 --- a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java diff --git a/patches/server/0057-Infinity-No-Arrows.patch b/patches/server/0056-Infinity-No-Arrows.patch similarity index 82% rename from patches/server/0057-Infinity-No-Arrows.patch rename to patches/server/0056-Infinity-No-Arrows.patch index 93dee8c7..79267b52 100644 --- a/patches/server/0057-Infinity-No-Arrows.patch +++ b/patches/server/0056-Infinity-No-Arrows.patch @@ -1,14 +1,14 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Bud Gidiere +From: Zoe Date: Sun, 25 Oct 2020 12:24:19 -0500 Subject: [PATCH] Infinity No Arrows diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index 7cb937cf03551d5c265a75cb0a52413946506402..bbc71abb20cde8493ee9064b1558adcdbf2b0852 100644 +index c9f490e4e4e7a5a3a9ad99f864ff8fb2acc5b5b2..b3c76fcf65b028e4aa53699ee93a5c3ba59981f2 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java -@@ -2144,7 +2144,7 @@ public abstract class EntityHuman extends EntityLiving { +@@ -2179,7 +2179,7 @@ public abstract class EntityHuman extends EntityLiving { } } @@ -18,10 +18,10 @@ index 7cb937cf03551d5c265a75cb0a52413946506402..bbc71abb20cde8493ee9064b1558adcd } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index e86ccbd36250f4229ce62319a59889bc0ac5befb..db6aff16237fad8484d59de607d1edb3b5a1e621 100644 +index 7c9ea94960e8147d5d193b40c17178f8c28b4acf..7fa0a12781028b8352897556c8613ad41cf29036 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -176,10 +176,12 @@ public class PurpurWorldConfig { +@@ -200,10 +200,12 @@ public class PurpurWorldConfig { public boolean infinityWorksWithNormalArrows = true; public boolean infinityWorksWithSpectralArrows = false; public boolean infinityWorksWithTippedArrows = false; @@ -33,4 +33,4 @@ index e86ccbd36250f4229ce62319a59889bc0ac5befb..db6aff16237fad8484d59de607d1edb3 + infinityWorksWithNoArrows = getBoolean("gameplay-mechanics.infinity-bow.no-arrows", infinityWorksWithNoArrows); } - public boolean signAllowColors = false; + public int dragonFireballDespawnRate = -1; diff --git a/patches/server/0058-Custom-Locale-Support.patch b/patches/server/0057-Custom-Locale-Support.patch similarity index 98% rename from patches/server/0058-Custom-Locale-Support.patch rename to patches/server/0057-Custom-Locale-Support.patch index b185eee8..bc7f9aad 100644 --- a/patches/server/0058-Custom-Locale-Support.patch +++ b/patches/server/0057-Custom-Locale-Support.patch @@ -1,5 +1,5 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Bud Gidiere +From: Zoe Date: Sun, 25 Oct 2020 11:45:38 -0500 Subject: [PATCH] Custom Locale Support diff --git a/patches/server/0059-Configurable-movement-checks.patch b/patches/server/0058-Configurable-movement-checks.patch similarity index 89% rename from patches/server/0059-Configurable-movement-checks.patch rename to patches/server/0058-Configurable-movement-checks.patch index cd797dbe..7aaad010 100644 --- a/patches/server/0059-Configurable-movement-checks.patch +++ b/patches/server/0058-Configurable-movement-checks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable movement checks diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index 4e343bf4865655020994c40a1c7101afb994a659..bd356f8334936c1c7c1a2961eee9c0cb4d7f77ba 100644 +index 540d16cb01e70fe46bce3a3bb4cdfd1841b3d155..4b0b4c2c9af4c0a43b30f84a87cb1539fe34da27 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java -@@ -443,7 +443,7 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -449,7 +449,7 @@ public class PlayerConnection implements PacketListenerPlayIn { } // Paper end @@ -17,16 +17,16 @@ index 4e343bf4865655020994c40a1c7101afb994a659..bd356f8334936c1c7c1a2961eee9c0cb // CraftBukkit end PlayerConnection.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", entity.getDisplayName().getString(), this.player.getDisplayName().getString(), d6, d7, d8); this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity)); -@@ -468,7 +468,7 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -476,7 +476,7 @@ public class PlayerConnection implements PacketListenerPlayIn { d10 = d6 * d6 + d7 * d7 + d8 * d8; boolean flag1 = false; - if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot + if (org.yatopiamc.yatopia.server.YatopiaConfig.checkVehicleMovedWrongly && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot // Yatopia - Configurable movement checks - flag1 = true; + flag1 = true; // Tuinity - diff on change, this should be moved wrongly PlayerConnection.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", entity.getDisplayName().getString(), this.player.getDisplayName().getString(), Math.sqrt(d10)); } -@@ -1165,7 +1165,7 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -1199,7 +1199,7 @@ public class PlayerConnection implements PacketListenerPlayIn { if (!this.player.H() && (!this.player.getWorldServer().getGameRules().getBoolean(GameRules.DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isGliding())) { float f2 = this.player.isGliding() ? 300.0F : 100.0F; @@ -35,14 +35,14 @@ index 4e343bf4865655020994c40a1c7101afb994a659..bd356f8334936c1c7c1a2961eee9c0cb // CraftBukkit end PlayerConnection.LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getDisplayName().getString(), d7, d8, d9); this.a(this.player.locX(), this.player.locY(), this.player.locZ(), this.player.yaw, this.player.pitch); -@@ -1231,7 +1231,7 @@ public class PlayerConnection implements PacketListenerPlayIn { +@@ -1265,7 +1265,7 @@ public class PlayerConnection implements PacketListenerPlayIn { d11 = d7 * d7 + d8 * d8 + d9 * d9; boolean flag1 = false; - if (!this.player.H() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.playerInteractManager.isCreative() && this.player.playerInteractManager.getGameMode() != EnumGamemode.SPECTATOR) { // Spigot + if (org.yatopiamc.yatopia.server.YatopiaConfig.checkMovedWrongly && !this.player.H() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.playerInteractManager.isCreative() && this.player.playerInteractManager.getGameMode() != EnumGamemode.SPECTATOR) { // Spigot // Yatopia - Configurable movement checks - flag1 = true; - PlayerConnection.LOGGER.warn("{} moved wrongly!", this.player.getDisplayName().getString()); + flag1 = true; // Tuinity - diff on change, this should be moved wrongly + PlayerConnection.LOGGER.warn("{} moved wrongly! ({})", this.player.getDisplayName().getString(), d11); // Purpur } diff --git a/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java b/src/main/java/org/yatopiamc/yatopia/server/YatopiaConfig.java index 4f99aa4b578333795296da2424f38aae65a808a3..34b5a087d1d5d84b193adbd756add060a2d49354 100644 diff --git a/patches/server/0060-Configurable-enchanting-table-tick.patch b/patches/server/0059-Configurable-enchanting-table-tick.patch similarity index 100% rename from patches/server/0060-Configurable-enchanting-table-tick.patch rename to patches/server/0059-Configurable-enchanting-table-tick.patch diff --git a/patches/server/0061-Do-not-update-distance-map-when-animal-and-mob-spawn.patch b/patches/server/0060-Do-not-update-distance-map-when-animal-and-mob-spawn.patch similarity index 93% rename from patches/server/0061-Do-not-update-distance-map-when-animal-and-mob-spawn.patch rename to patches/server/0060-Do-not-update-distance-map-when-animal-and-mob-spawn.patch index 252db7d4..c03ffb74 100644 --- a/patches/server/0061-Do-not-update-distance-map-when-animal-and-mob-spawn.patch +++ b/patches/server/0060-Do-not-update-distance-map-when-animal-and-mob-spawn.patch @@ -9,7 +9,7 @@ Licensed under Bukkit and CraftBukkit's original license, GPLv3 The following patch should be treated as it is licensed in GPLv3 diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index ecf5a01e47e2a61d88badce44adad30d6e755cfb..7ee7be7c69c80f72dadbb5296c49a6f07196f670 100644 +index ce2864f44dcc2003a85f7a211073b2b0bb617cd2..8212a8799ce09cec99deedb73d1f71480009345a 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -974,7 +974,7 @@ public class ChunkProviderServer extends IChunkProvider { diff --git a/patches/server/0062-Add-a-special-case-for-floodgate-and-offline-uuids.patch b/patches/server/0061-Add-a-special-case-for-floodgate-and-offline-uuids.patch similarity index 100% rename from patches/server/0062-Add-a-special-case-for-floodgate-and-offline-uuids.patch rename to patches/server/0061-Add-a-special-case-for-floodgate-and-offline-uuids.patch diff --git a/patches/server/0063-PaperPR-Fix-username-connecting-with-no-texture-bein.patch b/patches/server/0062-PaperPR-Fix-username-connecting-with-no-texture-bein.patch similarity index 100% rename from patches/server/0063-PaperPR-Fix-username-connecting-with-no-texture-bein.patch rename to patches/server/0062-PaperPR-Fix-username-connecting-with-no-texture-bein.patch diff --git a/patches/server/0064-Optimised-hallowen-checker.patch b/patches/server/0063-Optimised-hallowen-checker.patch similarity index 90% rename from patches/server/0064-Optimised-hallowen-checker.patch rename to patches/server/0063-Optimised-hallowen-checker.patch index 6ae3fae1..7b996323 100644 --- a/patches/server/0064-Optimised-hallowen-checker.patch +++ b/patches/server/0063-Optimised-hallowen-checker.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimised hallowen checker diff --git a/src/main/java/net/minecraft/server/EntityBat.java b/src/main/java/net/minecraft/server/EntityBat.java -index 0a59e02d762a096cb3de62e0f8105cc5a5fab8d4..bdcbdc21f986852277dcc41a2b0f385f8caeb9f7 100644 +index 7718ec8ad3c1833e0bcc713c2e96e054e87453ad..778cd2d0e091b455fddf05d9c07605a6c305de94 100644 --- a/src/main/java/net/minecraft/server/EntityBat.java +++ b/src/main/java/net/minecraft/server/EntityBat.java -@@ -222,11 +222,16 @@ public class EntityBat extends EntityAmbient { +@@ -280,11 +280,16 @@ public class EntityBat extends EntityAmbient { } private static boolean eJ() { @@ -26,10 +26,10 @@ index 0a59e02d762a096cb3de62e0f8105cc5a5fab8d4..bdcbdc21f986852277dcc41a2b0f385f @Override diff --git a/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java b/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java -index f73304240a626f3f7d9355e6e5f2963a06c4bb7d..3ca3280d4a4d3cd8e0b4aff8431d8fe5904d23a4 100644 +index ee4c26de15a5c304889f38f49f4584e8d4ccc5fe..ca9075d6bae36c29d9eb4727321afcbced5f3db4 100644 --- a/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java +++ b/src/main/java/net/minecraft/server/EntitySkeletonAbstract.java -@@ -109,11 +109,15 @@ public abstract class EntitySkeletonAbstract extends EntityMonster implements IR +@@ -111,11 +111,15 @@ public abstract class EntitySkeletonAbstract extends EntityMonster implements IR this.eL(); this.setCanPickupLoot(this.world.paperConfig.skeletonsAlwaysCanPickUpLoot || this.random.nextFloat() < 0.55F * difficultydamagescaler.d()); // Paper if (this.getEquipment(EnumItemSlot.HEAD).isEmpty()) { @@ -46,10 +46,10 @@ index f73304240a626f3f7d9355e6e5f2963a06c4bb7d..3ca3280d4a4d3cd8e0b4aff8431d8fe5 this.dropChanceArmor[EnumItemSlot.HEAD.b()] = 0.0F; } diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java -index 752e39ad94ea9e8254853a3fda846be2bd436918..f470650838ab0e349a7ffc79fcb4b84460d32832 100644 +index a5699314be3f47ed9b27a5d21a396c5282592c7b..16d73dc50b76523dd03e12d566646874e86fabca 100644 --- a/src/main/java/net/minecraft/server/EntityZombie.java +++ b/src/main/java/net/minecraft/server/EntityZombie.java -@@ -472,11 +472,15 @@ public class EntityZombie extends EntityMonster { +@@ -520,11 +520,15 @@ public class EntityZombie extends EntityMonster { } if (this.getEquipment(EnumItemSlot.HEAD).isEmpty()) { @@ -66,10 +66,10 @@ index 752e39ad94ea9e8254853a3fda846be2bd436918..f470650838ab0e349a7ffc79fcb4b844 this.dropChanceArmor[EnumItemSlot.HEAD.b()] = 0.0F; } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index dac1e68dc8dac578605470c148fd028792d54932..581b675fae4ca8a3d774f4353d1e58fc31b557c7 100644 +index 954462353d35c06b0568c56669e31d0ad15bcf67..87cf9cd88d1fb5ae70d19e5618ebfb67d281304a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1339,6 +1339,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant= generatoraccess.getSeaLevel()) { return false; } else { @@ -17,7 +17,7 @@ index bdcbdc21f986852277dcc41a2b0f385f8caeb9f7..dff2dc82c2a6b26c6e41b3949953abca byte b0 = 4; if (eJ()) { -@@ -217,6 +217,7 @@ public class EntityBat extends EntityAmbient { +@@ -275,6 +275,7 @@ public class EntityBat extends EntityAmbient { return false; } diff --git a/patches/server/0066-add-config-for-logging-login-location.patch b/patches/server/0065-add-config-for-logging-login-location.patch similarity index 95% rename from patches/server/0066-add-config-for-logging-login-location.patch rename to patches/server/0065-add-config-for-logging-login-location.patch index 59978378..c5a2f6ae 100644 --- a/patches/server/0066-add-config-for-logging-login-location.patch +++ b/patches/server/0065-add-config-for-logging-login-location.patch @@ -5,7 +5,7 @@ Subject: [PATCH] add config for logging login location diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index 5afc424481106570b84a9b38c94fe397f1aff636..0efbc7e156549f397c6ed53671d247e1e2edf9d5 100644 +index 7e42654873195d17c9a5a2a718216a943533e658..fd2fe2f5e53f34957f80223e1694a57308de4016 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -395,7 +395,14 @@ public abstract class PlayerList { diff --git a/pom.xml b/pom.xml deleted file mode 100644 index a6414d26..00000000 --- a/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - 4.0.0 - - org.yatopiamc - yatopia-parent - dev-SNAPSHOT - pom - Yatopia (Parent) - - - Yatopia-API - Yatopia-Server - - - - - - codemc-repo - https://repo.codemc.io/repository/maven-public/ - - - - ivan - https://repo.mrivanplays.com/repository/ivan/ - - - - - - - aikar - https://repo.aikar.co/nexus/content/repositories/aikar/ - - - - - - codemc-releases - https://repo.codemc.org/repository/maven-releases/ - - - codemc-snapshots - https://repo.codemc.org/repository/maven-snapshots/ - - - - - - - net.yatopia - patchcredits - 0.0.2-SNAPSHOT - - - update-credits - - update - - - - - - - diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh deleted file mode 100755 index 21c7333a..00000000 --- a/scripts/applyPatches.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env bash - -# SCRIPT HEADER start -basedir=$1 -source "$basedir/scripts/functions.sh" -gpgsign="$($gitcmd config commit.gpgsign || echo "false")" -echo " " -echo "----------------------------------------" -echo " $(bashcolor 1 32)Task$(bashcolorend) - Apply Patches" -echo " This will apply all of Yatopia patches on top of the Paper." -echo " " -echo " $(bashcolor 1 32)Subtask:$(bashcolorend)" -echo " - Import Sources" -echo " " -echo " $(bashcolor 1 32)Modules:$(bashcolorend)" -echo " - $(bashcolor 1 32)1$(bashcolorend) : API" -echo " - $(bashcolor 1 32)2$(bashcolorend) : Server" -echo "----------------------------------------" -# SCRIPT HEADER end - -needimport=$2 -function enableCommitSigningIfNeeded() { - if [[ "$gpgsign" == "true" ]]; then - $gitcmd config commit.gpgsign true - fi -} -function applyPatch() { - baseproject=$1 - basename=$(basename $baseproject) - target=$2 - branch=$3 - patch_folder=$4 - cd $basedir/$2 - # Skip if that software have no patch - haspatch=-f "$basedir/patches/$patch_folder/"*.patch >/dev/null 2>&1 # too many files - if [ ! haspatch ]; then - echo " $(bashcolor 1 33)($5/$6) Skipped$(bashcolorend) - No patch found for $target under patches/$patch_folder" - return - fi - - # Disable GPG signing before AM, slows things down and doesn't play nicely. - # There is also zero rational or logical reason to do so for these sub-repo AMs. - # Calm down kids, it's re-enabled (if needed) immediately after, pass or fail. - $gitcmd config commit.gpgsign false - - if [[ $needimport != "1" ]]; then - if [ $baseproject != "Tuinity/Tuinity-API" ]; then - echo " $(bashcolor 1 32)($5/$6)$(bashcolorend) - Import new introduced NMS files.." - basedir && $scriptdir/importSources.sh $basedir "Yatopia" || exit 1 - fi - fi - - echo " " - echo " $(bashcolor 1 32)($5/$6)$(bashcolorend) - Apply patches to $target.." - - cd $basedir/$2 - git branch -d $2 - git branch $2 - git checkout $2 - - $gitcmd am --abort >/dev/null 2>&1 - # Apply our patches on top Paper in our dirs - $gitcmd am --reject --3way --whitespace=fix "$basedir/patches/$patch_folder/"*.patch - cd $basedir - - if [ "$?" != "0" ]; then - echo " Something did not apply cleanly to $target." - echo " Please review above details and finish the apply then" - echo " save the changes with rebuildPatches.sh" - echo " or use 'git am --abort' to cancel this applying." - echo " $(bashcolor 1 33)($5/$6) Suspended$(bashcolorend) - Resolve the conflict or abort the apply" - echo " " - cd "$basedir/$target" - exit 1 - else - echo " $(bashcolor 1 32)($6/$6) Succeed$(bashcolorend) - Patches applied cleanly to $target" - echo " " - fi -} - -rm -rf $basedir/Yatopia/Yatopia-Server - -rm -rf $basedir/Yatopia/Yatopia-API - -$1/scripts/resetToUpstream.sh $1 -$1/scripts/getUpstream.sh $1 - -(applyPatch Yatopia/Yatopia-API ${FORK_NAME}-API HEAD api $API_REPO 0 2 && - applyPatch Yatopia/Yatopia-Server ${FORK_NAME}-Server HEAD server $SERVER_REPO 1 2 && enableCommitSigningIfNeeded) || ( - enableCommitSigningIfNeeded - exit 1 -) diff --git a/scripts/applyUpstream.sh b/scripts/applyUpstream.sh deleted file mode 100755 index a759bef9..00000000 --- a/scripts/applyUpstream.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -searchtxts=(Server API) -gpgsign="$(git config commit.gpgsign || echo "false")" -scriptdir=$1/scripts -function enableCommitSigningIfNeeded() { - if [[ "$gpgsign" == "true" ]]; then - git config commit.gpgsign true - fi -} - -if [ $2 == "removed" ]; then - exit 0 -fi - -# Disable GPG signing before AM, slows things down and doesn't play nicely. -# There is also zero rational or logical reason to do so for these sub-repo AMs. -# Calm down kids, it's re-enabled (if needed) immediately after, pass or fail. -git config commit.gpgsign false -cd "$1"/patches/"$2" -for D in ${searchtxts[@]}; do - if [ -d $1/patches/$2/$dnoslashlower ]; then - echo $D - dnoslash=$D - cd "$1"/Yatopia-"$dnoslash" - echo "Appyling $2 $dnoslash files!" - dnoslashlower="${dnoslash,,}" - if [ $dnoslashlower != "api" ]; then - echo "$" - echo "Import new introduced NMS files.." - $scriptdir/importSources.sh $1 $2 || exit 1 - fi - for filename in $1/patches/$2/$dnoslashlower/*.patch; do - # Abort previous applying operation - git am --abort >/dev/null 2>&1 - # Apply our patches on top Paper in our dirs - git am --reject --3way --whitespace=fix $filename || ( - filenamend="${filename##*/}" - filenamens=${filenamend%/*} - filenameedited=${filenamens%.*} # retain the part before the period - filenameedited=${filenameedited:5} # retain the part after the frist slash - git add . - git commit -m $filenameedited - ) - done - fi -done -enableCommitSigningIfNeeded diff --git a/scripts/commitUpstream.sh b/scripts/commitUpstream.sh deleted file mode 100755 index 5e2fe2ed..00000000 --- a/scripts/commitUpstream.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -( -set -e - -function changeLog() { - base=$(git ls-tree HEAD $1 | cut -d' ' -f3 | cut -f1) - cd $1 && git log --oneline ${base}...HEAD -} -tuinity=$(changeLog Tuinity) -akarin=$(changeLog Akarin) -empirecraft=$(changeLog Empirecraft) -origami=$(changeLog Origami) -purpur=$(changeLog Purpur) -airplaneL=$(changeLog AirplaneLite) - -updated="" -logsuffix="" -if [ ! -z "$tuinity" ]; then - logsuffix="$logsuffix\n\nTuinity Changes:\n$tuinity" - updated="Tuinity" -fi -if [ ! -z "$akarin" ]; then - logsuffix="$logsuffix\n\nAkarin Changes:\n$akarin" - if [ -z "$updated" ]; then updated="Akarin"; else updated="$updated/Akarin"; fi -fi -if [ ! -z "$empirecraft" ]; then - logsuffix="$logsuffix\n\nEMC Changes:\n$empirecraft" - if [ -z "$updated" ]; then updated="EMC"; else updated="$updated/EMC"; fi -fi -if [ ! -z "$origami" ]; then - logsuffix="$logsuffix\n\nOrigami Changes:\n$origami" - if [ -z "$updated" ]; then updated="Origami"; else updated="$updated/Origami"; fi -fi -if [ ! -z "$purpur" ]; then - logsuffix="$logsuffix\n\nPurpur Changes:\n$purpur" - if [ -z "$updated" ]; then updated="Purpur"; else updated="$updated/Purpur"; fi -fi -if [ ! -z "$airplaneL" ]; then - logsuffix="$logsuffix\n\nAirplaneLite Changes:\n$airplaneL" - if [ -z "$updated" ]; then updated="AirplaneLite"; else updated="$updated/AirplaneLite"; fi -fi -disclaimer="Upstream/An Sidestream has released updates that appears to apply and compile correctly\nThis update has NOT been tested by YatopiaMC and as with ANY update, please do your own testing." - -if [ ! -z "$1" ]; then - disclaimer="$@" -fi - -log="Updated Upstream and Sidestream(s) ($updated)\n\n${disclaimer}${logsuffix}" - -echo -e "$log" | git commit -F - - -) || exit 1 diff --git a/scripts/fetchUpstream.sh b/scripts/fetchUpstream.sh deleted file mode 100755 index 5c41b61d..00000000 --- a/scripts/fetchUpstream.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -PS1=$ -basedir=`pwd` - -function update { - branch=$2 - if [ -z "$2" ]; then - branch="master" - fi - cd "$basedir/$1" - git fetch && git reset --hard origin/$branch - git add $1 -} - -function updateAll { - update AirplaneLite master - update Akarin ver/1.16.4 - update Empirecraft master - update Origami 1.16 - update Purpur ver/1.16.5 - update Tuinity ver/1.16.5 - git submodule update --recursive -} - -if [ -z "$1" ]; then - updateAll -elif [ "$1" == "true" ]; then - update Tuinity ver/1.16.4 - git submodule update --recursive -elif [ "$1" == "false" ]; then - if [ "$2" == "true" ]; then - git submodule update --init -f - cd "$basedir" - cd Tuinity - git clean -fx - git clean -fd - git fetch - git reset --hard origin/ver/1.16.5 - git submodule update --init --recursive -f - else - updateAll - fi -else - updateAll -fi \ No newline at end of file diff --git a/scripts/fixPatch.sh b/scripts/fixPatch.sh deleted file mode 100755 index 215ec3cc..00000000 --- a/scripts/fixPatch.sh +++ /dev/null @@ -1,23 +0,0 @@ -cd Yatopia-$2 -for filename in $1/patches/$2/*.patch; do - # Abort previous applying operation - git am --abort >/dev/null 2>&1 - # Apply our patches on top Paper in our dirs - git am --reject --whitespace=fix --3way --ignore-whitespace $filename || ( - filenamend="${filename##*/}" - filenamens=${filenamend%/*} - filenameedited=${filenamens%.*} # retain the part before the period - filenameedited=${filenameedited:5} # retain the part after the frist slash - git add . - git commit -m $filenameedited - ) - echo "Press any key to continue" - while [ true ]; do - read -t 3 -n 1 - if [ $? = 0 ]; then - exit - else - echo "waiting for the keypress" - fi - done -done diff --git a/scripts/functions.sh b/scripts/functions.sh deleted file mode 100755 index 33ce4634..00000000 --- a/scripts/functions.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env bash -# CONFIG set -FORK_NAME="Yatopia" -API_REPO="" -SERVER_REPO="" -PAPER_API_REPO="" -PAPER_SERVER_REPO="" -MCDEV_REPO="" - -# Added Multithreading to builds -# By JosephWorks -mvncmd="mvn -T 1.5C" - -gitcmd="git -c commit.gpgsign=false -c core.quotepath=false -c core.safecrlf=false -c i18n.commit.encoding=UTF-8 -c i18n.logoutputencoding=UTF-8" - -# DIR configure -# resolve shell-specifics -case "$(echo "$SHELL" | sed -E 's|/usr(/local)?||g')" in - "/bin/zsh") - RCPATH="$HOME/.zshrc" - SOURCE="${BASH_SOURCE[0]:-${(%):-%N}}" - ;; - *) - RCPATH="$HOME/.bashrc" - if [[ -f "$HOME/.bash_aliases" ]]; then - RCPATH="$HOME/.bash_aliases" - fi - SOURCE="${BASH_SOURCE[0]}" - ;; -esac - -while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink - DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - SOURCE="$(readlink "$SOURCE")" - [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located -done -SOURCE=$([[ "$SOURCE" = /* ]] && echo "$SOURCE" || echo "$PWD/${SOURCE#./}") -scriptdir=$(dirname "$SOURCE") -basedir=$(dirname "$scriptdir") - -function basedir { - cd "$basedir" -} - -function paperdir { - cd "$basedir/Tuinity" -} - -gitcmd() { - $gitcmd "$@" -} - -# COLOUR functions -color() { - if [ $2 ]; then - echo -e "\e[$1;$2m" - else - echo -e "\e[$1m" - fi -} - -colorend() { - echo -e "\e[m" -} - -function bashcolor { - if [ $2 ]; then - echo -e "\e[$1;$2m" - else - echo -e "\e[$1m" - fi -} - -function bashcolorend { - echo -e "\e[m" -} - -# GIT functions -gitstash() { - STASHED=$($gitcmd stash 2>/dev/null|| return 0) # errors are ok -} - -gitunstash() { - if [[ "$STASHED" != "No local changes to save" ]] ; then - $gitcmd stash pop 2>/dev/null|| return 0 # errors are ok - fi -} - -function gethead { - basedir - git log -1 --oneline -} - -function gitpush { - if [ "$(git config minecraft.push-${FORK_NAME})" == "1" ]; then - echo "Push - $1 ($3) to $2" - ( - basedir - git remote rm script-push > /dev/null 2>&1 - git remote add script-push $2 >/dev/null 2>&1 - git push script-push $3 -f - ) - fi -} - -# PATCH functions -function cleanupPatches { - cd "$1" - for patch in *.patch; do - gitver=$(tail -n 2 $patch | grep -ve "^$" | tail -n 1) - diffs=$(git diff --staged $patch | grep -E "^(\+|\-)" | grep -Ev "(From [a-z0-9]{32,}|\-\-\- a|\+\+\+ b|.index|Date\: )") - - testver=$(echo "$diffs" | tail -n 2 | grep -ve "^$" | tail -n 1 | grep "$gitver") - if [ "x$testver" != "x" ]; then - diffs=$(echo "$diffs" | tail -n +3) - fi - - if [ "x$diffs" == "x" ] ; then - git reset HEAD $patch >/dev/null - git checkout -- $patch >/dev/null - fi - done -} - -function containsElement { - local e - for e in "${@:2}"; do - [[ "$e" == "$1" ]] && return 0; - done - return 1 -} diff --git a/scripts/generateImports.sh b/scripts/generateImports.sh deleted file mode 100755 index 8f919553..00000000 --- a/scripts/generateImports.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -echo "[Yatopia] State: Generate Imports" - -# For a description of this script, see updateUpstream.sh. - -# get base dir regardless of execution location -basedir=$1 - -source "$basedir/scripts/functions.sh" - -paperworkdir="$basedir/Tuinity/Paper/work" -minecraftversion=$(cat $paperworkdir/BuildData/info.json | grep minecraftVersion | cut -d '"' -f 4) -decompile="$paperworkdir/Minecraft/$minecraftversion/spigot" - -# create dev dir -basedir -mkdir -p mc-dev/src/net/minecraft/server -cd mc-dev - -# prepare to push -if [ ! -d ".git" ]; then - $gitcmd init -fi - -# reset dev files to raw nms in spigot naming -rm src/net/minecraft/server/*.java -cp $decompile/net/minecraft/server/*.java src/net/minecraft/server - -# diff and only preserve new added files -paperserver="$basedir/Tuinity/Tuinity-Server/src/main/java/net/minecraft/server" -cd $basedir/mc-dev/src/net/minecraft/server/ - -for file in $(/bin/ls $paperserver); do - if [ -f "$file" ]; then - rm -f "$file" - fi -done - -# push the dev project -cd $basedir/mc-dev -$gitcmd add . -A -$gitcmd commit . -m "Yatopia-base" -$gitcmd tag -a "Yatopia-base" -m "Yatopia-base" 2>/dev/null -# gitpush . $MCDEV_REPO $paperVer diff --git a/scripts/getUpstream.sh b/scripts/getUpstream.sh deleted file mode 100755 index 1b7aef96..00000000 --- a/scripts/getUpstream.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -patchdir="$1/patches" -searchtxts=(server api) -i=0 - -echo "Starting Upstream Patching!" -cd $patchdir -for D in */; do - if [ -d "${D}" ]; then - dnoslash=${D%/*} - if [[ $dnoslash != "server" ]]; then - if [[ $dnoslash != "api" ]]; then - echo "Found $dnoslash directory!" - for file in ${searchtxts[@]}; do - if [ -f "$1/patches/$dnoslash/$file.txt" ]; then - i=0 - rm -rf -f "$1/patches/$dnoslash/$file/" - echo "Looking for $file file!" - IFS='&' - read -ra ADDR <<<$(cat $patchdir/$dnoslash/$file.txt) - for patch in ${ADDR[@]}; do - echo "Found $patch in $file!" - echo $1/$dnoslash/patches/$file - for filename in $1/$dnoslash/patches/$file/*.patch; do - filenamend="${filename##*/}" - filenamens=${filenamend%/*} - filenameedited=${filenamens::-6} - filenameedited=${filenameedited:5} # retain the part after the frist slash - if [[ $filenameedited == $patch ]]; then - echo "Found Matching file!" - if [[ $i == 0 ]]; then - echo "Making $file dir in $dnoslash patch dir" - mkdir $1/patches/$dnoslash/$file - fi - ((i = i + 1)) - printf -v num "%04d" $i - echo "Making ${num}-${patch}.patch file for Yatopia" - cp $1/$dnoslash/patches/$file/$filenamens $1/patches/$dnoslash/$file/"${num}-${patch}.patch" - fi - done - done - IFS=' ' - fi - done - $1/scripts/applyUpstream.sh $1 $dnoslash || exit 1 - fi - fi - fi -done diff --git a/scripts/importSources.sh b/scripts/importSources.sh deleted file mode 100755 index 92106e88..00000000 --- a/scripts/importSources.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env bash - -#maintask=$2 -#if [[ $maintask == "0" ]]; then -# TASKTITLE="Import Sources" -#else -# TASKTITLE="Import Sources (Subtask)" -#fi - -# SCRIPT HEADER start -basedir=$1 -echo "$2" -source "$basedir/scripts/functions.sh" -echo " " -echo "----------------------------------------" -echo " $(bashcolor 1 32)Task$(bashcolorend) - $TASKTITLE" -echo " This will import unimported newly added/mod sources to Paper workspace" -echo "----------------------------------------" -# SCRIPT HEADER end - -# For a description of this script, see updateUpstream.sh. -paperworkdir="$basedir/Tuinity/Paper/work" -forkname="$2" -paperserverdir="$basedir/Yatopia-Server" -papersrcdir="$basedir/Yatopia-Server/src/main/java" -papernmsdir="$papersrcdir/net/minecraft/server" - -( - # fast-fail if Paper not set - if [ ! -d "$papernmsdir" ]; then - echo " $(bashcolor 1 31)Exception$(bashcolorend) - Paper sources not generated, run updateUpstream.sh to setup." - exit 1 - fi -) - -minecraftversion=$(cat "$basedir"/Tuinity/Paper/work/BuildData/info.json | grep minecraftVersion | cut -d '"' -f 4) -decompiledir=$paperworkdir/Minecraft/$minecraftversion/spigot - -nms="net/minecraft/server" -export IMPORT_LOG="" # for commit message, list all files and source for libs -basedir - -function importToPaperWorkspace() { - if [ -f "$papernmsdir/$1.java" ]; then - return 0 - fi - - file="$1.java" - target="$papernmsdir/$file" - base="$decompiledir/$nms/$file" - - if [[ ! -f "$target" ]]; then - export IMPORT_LOG="$IMPORT_LOG Import: $file\n" - echo "Import: $file" - cp "$base" "$target" - fi -} - -function importLibraryToPaperWorkspace() { - group=$1 - lib=$2 - prefix=$3 - shift 3 - for file in "$@"; do - file="$prefix/$file" - target="$papersrcdir/$file" - targetdir=$(dirname "$target") - mkdir -p "${targetdir}" - - base="$paperworkdir/Minecraft/$minecraftversion/libraries/${group}/${lib}/$file" - if [ ! -f "$base" ]; then - echo " $(bashcolor 1 31)Exception$(bashcolorend) - Cannot find file $file.java of lib $lib in group $group to import, re-decomplie or remove the import." - exit 1 - fi - - export IMPORT_LOG="$IMPORT_LOG Import: $file from lib $lib\n" - echo "Import: $file ($lib)" - sed 's/\r$//' "$base" >"$target" || exit 1 - done -} - -( - # Reset to last NORMAL commit if already have imported before - cd "$paperserverdir" - lastcommit=$(git log -1 --pretty=oneline --abbrev-commit) - if [[ "$lastcommit" == *"Extra dev imports of $forkname"* ]]; then - git reset --hard HEAD^ - fi -) - -# Filter and import every files which have patch to modify - -if [[ $forkname != "Yatopia" ]]; then - if [[ $forkname != null ]]; then - patchedFiles=$(cat patches/$forkname/server/* | grep "+++ b/src/main/java/net/minecraft/server/" | sort | uniq | sed 's/\+\+\+ b\/src\/main\/java\/net\/minecraft\/server\///g' | sed 's/.java//g') - patchedFilesNonNMS=$(cat patches/$forkname/server/* | grep "create mode " | grep -Po "src/main/java/net/minecraft/server/(.*?).java" | sort | uniq | sed 's/src\/main\/java\/net\/minecraft\/server\///g' | sed 's/.java//g') - else - patchedFiles=$(cat patches/server/* | grep "+++ b/src/main/java/net/minecraft/server/" | sort | uniq | sed 's/\+\+\+ b\/src\/main\/java\/net\/minecraft\/server\///g' | sed 's/.java//g') - patchedFilesNonNMS=$(cat patches/server/* | grep "create mode " | grep -Po "src/main/java/net/minecraft/server/(.*?).java" | sort | uniq | sed 's/src\/main\/java\/net\/minecraft\/server\///g' | sed 's/.java//g') - fi -else - patchedFiles=$(cat patches/server/* | grep "+++ b/src/main/java/net/minecraft/server/" | sort | uniq | sed 's/\+\+\+ b\/src\/main\/java\/net\/minecraft\/server\///g' | sed 's/.java//g') - patchedFilesNonNMS=$(cat patches/server/* | grep "create mode " | grep -Po "src/main/java/net/minecraft/server/(.*?).java" | sort | uniq | sed 's/src\/main\/java\/net\/minecraft\/server\///g' | sed 's/.java//g') -fi -( - cd "$paperserverdir" -) - -basedir -for f in $patchedFiles; do - containsElement "$f" ${patchedFilesNonNMS[@]} - if [ "$?" == "1" ]; then - if [ ! -f "$papersrcdir/$nms/$f.java" ]; then - if [ ! -f "$decompiledir/$nms/$f.java" ]; then - echo " $(bashcolor 1 31)Exception$(bashcolorend) - Cannot find NMS file $f.java to import, re-decomplie or remove the import." - exit 1 - else - importToPaperWorkspace $f - fi - fi - fi -done - -# NMS import format: -# importToPaperWorkspace MinecraftServer - -# Library import format (multiple files are supported): -# NOTE: Imported libraries aren't a temporary change, YOU NEED TO LEAVE THEM AS IS!!!! -# importLibraryToPaperWorkspace com.mojang datafixerupper com/mojang/datafixers/util Either.java -# Submit imports by commit with file descriptions -( - cd "$paperserverdir" - # rm -rf nms-patches - git add . &>/dev/null - echo -e "Extra dev imports of $forkname\n\n$IMPORT_LOG" - git commit -m "Extra dev imports of $forkname" - echo " $(bashcolor 1 32)Succeed$(bashcolorend) - Sources have been imported to Paper/Paper-Server (branch upstream)" - - if [[ $maintask != "0" ]]; then # this is magical - echo "----------------------------------------" - echo " Subtask finished" - echo "----------------------------------------" - fi -) diff --git a/scripts/initUpstream.sh b/scripts/initUpstream.sh deleted file mode 100755 index 69cd034e..00000000 --- a/scripts/initUpstream.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -cd $1 -git submodule update --init -f -cd Tuinity -git submodule update --init --recursive -f \ No newline at end of file diff --git a/scripts/instRemappedJar.sh b/scripts/instRemappedJar.sh deleted file mode 100755 index 8fd739c1..00000000 --- a/scripts/instRemappedJar.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# SCRIPT HEADER start -echo " " -echo "----------------------------------------" -echo " $(bashcolor 1 32)Task$(bashcolorend) - Install remapped jar" -echo " This will install the minecraft-server dependency in your maven local repository" -echo " Use this only if you have dependency issues with it" -echo "----------------------------------------" -# SCRIPT HEADER end - -PS1=$ -basedir=$(pwd) - -minecraftversion=$(cat "$basedir/Tuinity/Paper/work/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) -jarpath="$basedir/Tuinity/Paper/work/Minecraft/$minecraftversion" - -cd "$basedir/Tuinity/Paper/work/CraftBukkit" || exit 1 # Need to be in a directory with a valid POM otherwise maven complains -mvn install:install-file -q -Dfile="$jarpath-mapped.jar" -Dpackaging=jar -DgroupId=io.papermc -DartifactId=minecraft-server -Dversion="$minecraftversion-SNAPSHOT" -if [ "$?" != "0" ]; then - echo "Failed to install minecraft-server dependency" - exit 1 -fi \ No newline at end of file diff --git a/scripts/installLauncher.sh b/scripts/installLauncher.sh deleted file mode 100755 index da6bb211..00000000 --- a/scripts/installLauncher.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -# SCRIPT HEADER start -basedir=$1 -source "$basedir/scripts/functions.sh" -echo " " -echo "----------------------------------------" -echo " $(bashcolor 1 32)Task$(bashcolorend) - Install Launcher" -echo " This will build a launcher that similar to Paperclip by the server jar." -echo " " -echo "----------------------------------------" -# SCRIPT HEADER end - -# Copied from https://github.com/PaperMC/Paper/blob/d54ce6c17fb7a35238d6b9f734d30a4289886773/scripts/paperclip.sh -# License from Paper applies to this file - -set -e -paperworkdir="$basedir/Tuinity/Paper/work" -mcver=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) -serverjar="$basedir/Yatopia-Server/target/yatopia-$mcver.jar" -vanillajar="$paperworkdir/Minecraft/$mcver/$mcver.jar" - -( - cd "$paperworkdir/Paperclip" - mvn clean package "-Dmcver=$mcver" "-Dpaperjar=$serverjar" "-Dvanillajar=$vanillajar" -) -mkdir -p "$basedir/target" -cp "$paperworkdir/Paperclip/assembly/target/paperclip-${mcver}.jar" "$basedir/yatopia-${mcver}-paperclip.jar" - -echo "" -echo " $(bashcolor 1 32)Success$(bashcolorend) - Saved launcher jar to target/yatopia-${mcver}-launcher.jar" diff --git a/scripts/rebuildPatches.sh b/scripts/rebuildPatches.sh deleted file mode 100755 index fa4cf30b..00000000 --- a/scripts/rebuildPatches.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -# SCRIPT HEADER start -basedir=$1 -source "$basedir/scripts/functions.sh" -echo " " -echo "----------------------------------------" -echo " $(bashcolor 1 32)Task$(bashcolorend) - Rebuild Patches" -echo " This will diff the sources of Yatopia and Paper to build patches." -echo " " -echo " $(bashcolor 1 32)Modules:$(bashcolorend)" -echo " - $(bashcolor 1 32)1$(bashcolorend) : API" -echo " - $(bashcolor 1 32)2$(bashcolorend) : Server" -echo "----------------------------------------" -# SCRIPT HEADER end - -function savePatches() { - targetname=$1 - basedir - mkdir -p $basedir/patches/$2 - if [ -d ".git/rebase-apply" ]; then - # in middle of a rebase, be smarter - echo "REBASE DETECTED - PARTIAL SAVE" - last=$(cat ".git/rebase-apply/last") - next=$(cat ".git/rebase-apply/next") - declare -a files=("$basedir/patches/$2/"*.patch) - for i in $(seq -f "%04g" 1 1 $last); do - if [ $i -lt $next ]; then - rm "${files[$(expr $i - 1)]}" - fi - done - else - rm -rf $basedir/patches/$2/*.patch - fi - - cd "$basedir/$targetname" - $gitcmd format-patch --no-signature --zero-commit --full-index --no-stat -N -o "$basedir/patches/$2" master >/dev/null - basedir - $gitcmd add -A "$basedir/patches/$2" - echo " $(bashcolor 1 32)($3/$4)$(bashcolorend) - Patches saved for $targetname to patches/$2" -} - -savePatches ${FORK_NAME}-API api 1 2 -savePatches ${FORK_NAME}-Server server 2 2 -# gitpushproject diff --git a/scripts/resetToUpstream.sh b/scripts/resetToUpstream.sh deleted file mode 100755 index b2a439f7..00000000 --- a/scripts/resetToUpstream.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -searchtxts=(Server API) -basedir=$1 -basename=$(basename $baseproject) -branch=HEAD -for type in ${searchtxts[@]}; do - baseproject=Tuinity/Tuinity-$type - target=Yatopia-$type - echo "$baseproject, $target, $branch, $basedir, $type, $basename" - echo "Setup upstream project.." - echo "$basedir/$baseproject" - cd "$basedir/$baseproject" - git fetch --all &>/dev/null - # Create the upstream branch in Paper project with current state - git checkout master >/dev/null 2>&1 # possibly already in - git branch -D upstream &>/dev/null - git branch -f upstream "$branch" &>/dev/null && git checkout upstream &>/dev/null - cd $basedir - # Create source project dirs - if [ ! -d "$basedir/$target" ]; then - mkdir "$basedir/$target" - cd "$basedir/$target" - # git remote add origin "$5" - fi - echo "$basedir/$target" - cd "$basedir/$target" - git init >/dev/null 2>&1 - - echo " " - echo "Reset $target to $basename.." - # Add the generated Paper project as the upstream remote of subproject - git remote rm upstream &>/dev/null - git remote add upstream "$basedir/$baseproject" &>/dev/null - # Ensure that we are in the branch we want so not overriding things - git checkout master &>/dev/null || git checkout -b master &>/dev/null - git fetch upstream &>/dev/null - # Reset our source project to Paper - cd "$basedir/$target" && git reset --hard upstream/upstream &>/dev/null -done diff --git a/scripts/updateUpstream.sh b/scripts/updateUpstream.sh deleted file mode 100755 index 7456ab2c..00000000 --- a/scripts/updateUpstream.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -# SCRIPT HEADER start -basedir=$1 -source "$basedir/scripts/functions.sh" -echo "----------------------------------------" -echo " $(bashcolor 1 32)Task$(bashcolorend) - Update Upstream" -echo " This will update and patch Paper, importing necessary sources for patching." -#echo " " -#echo " $(bashcolor 1 32)Subtask:$(bashcolorend)" -#echo " - Import Sources" -echo " " -echo " $(bashcolor 1 32)Projects:$(bashcolorend)" -echo " - $(bashcolor 1 32)1$(bashcolorend) : Paper" -echo " - $(bashcolor 1 32)2$(bashcolorend) : Yatopia" -echo "----------------------------------------" -# SCRIPT HEADER end - -# This script are capable of patching paper which have the same effect with renewing the source codes of paper to its corresponding remote/official state, and also are able to reset the patches of paper to its head commit to override dirty changes which needs a argument with --resetPaper. - -# After the patching, it will copying sources that do no exist in the Yatopia workspace but referenced in Yatopia patches into our workspace, depending on the content of our patches, this will be addressed by calling importSources.sh. - -# Following by invoking generateImports.sh, it will generate new added/imported files of paper compared to the original decompiled sources into mc-dev folder under the root dir of the project, whose intention is unclear yet. - -# exit immediately if a command exits with a non-zero status -set -e - -subtasks=1 - -if [ -z "$2" ]; then - $basedir/scripts/fetchUpstream.sh -else - if [ -z "$3" ]; then - $basedir/scripts/fetchUpstream.sh true - else - $basedir/scripts/fetchUpstream.sh false true - fi -fi - -# patch paper -echo " $(bashcolor 1 32)(0/$subtasks)$(bashcolorend) - Apply patches of Tuinity.." -echo " " -paperVer=$(gethead Tuinity) -paperdir -./tuinity patch - -echo " $(bashcolor 1 32)($subtasks/$subtasks) Succeed$(bashcolorend) - Submodules have been updated, regenerated and imported, run 'Yatopia patch' to test/fix patches, and by 'Yatopia rbp' to rebuild patches that fixed with the updated upstream." -echo " " diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..2b6949d2 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,20 @@ +import java.util.Locale + +val forkName = "Yatopia" +val forkNameLowercase = forkName.toLowerCase(Locale.ENGLISH) + +rootProject.name = forkNameLowercase + +setupSubproject("$forkNameLowercase-api") { + projectDir = File("$forkName-API") + buildFileName = "../subprojects/api.gradle.kts" +} +setupSubproject("$forkNameLowercase-server") { + projectDir = File("$forkName-Server") + buildFileName = "../subprojects/server.gradle.kts" +} + +inline fun setupSubproject(name: String, block: ProjectDescriptor.() -> Unit) { + include(name) + project(":$name").apply(block) +} diff --git a/subprojects/api.gradle.kts b/subprojects/api.gradle.kts new file mode 100644 index 00000000..51f0b461 --- /dev/null +++ b/subprojects/api.gradle.kts @@ -0,0 +1,11 @@ +repositories { + loadRepositories(project) +} + +dependencies { + loadDependencies(project) +} + +java { + withJavadocJar() +} diff --git a/subprojects/server.gradle.kts b/subprojects/server.gradle.kts new file mode 100644 index 00000000..af091b92 --- /dev/null +++ b/subprojects/server.gradle.kts @@ -0,0 +1,7 @@ +repositories { + loadRepositories(project) +} + +dependencies { + loadDependencies(project) +} diff --git a/upstream/AirplaneLite b/upstream/AirplaneLite new file mode 160000 index 00000000..3231979d --- /dev/null +++ b/upstream/AirplaneLite @@ -0,0 +1 @@ +Subproject commit 3231979da51ae8c6197c5984314c344ae07b458f diff --git a/Akarin b/upstream/Akarin similarity index 100% rename from Akarin rename to upstream/Akarin diff --git a/Empirecraft b/upstream/Empirecraft similarity index 100% rename from Empirecraft rename to upstream/Empirecraft diff --git a/Origami b/upstream/Origami similarity index 100% rename from Origami rename to upstream/Origami diff --git a/upstream/Purpur b/upstream/Purpur new file mode 160000 index 00000000..643d9681 --- /dev/null +++ b/upstream/Purpur @@ -0,0 +1 @@ +Subproject commit 643d9681ff2d06283d4266341c0c65a531ea55fa diff --git a/upstream/Tuinity b/upstream/Tuinity new file mode 160000 index 00000000..d3ee2222 --- /dev/null +++ b/upstream/Tuinity @@ -0,0 +1 @@ +Subproject commit d3ee22224addb3d6997feedf4eba95962786f7d7 diff --git a/upstreamCommits/AirplaneLite b/upstreamCommits/AirplaneLite new file mode 100644 index 00000000..3e4f61bd --- /dev/null +++ b/upstreamCommits/AirplaneLite @@ -0,0 +1 @@ +3231979da51ae8c6197c5984314c344ae07b458f \ No newline at end of file diff --git a/upstreamCommits/Akarin b/upstreamCommits/Akarin new file mode 100644 index 00000000..4fed80ba --- /dev/null +++ b/upstreamCommits/Akarin @@ -0,0 +1 @@ +3950fd57413a0aceb3369e17be981ceec0be140a \ No newline at end of file diff --git a/upstreamCommits/Empirecraft b/upstreamCommits/Empirecraft new file mode 100644 index 00000000..68b85a38 --- /dev/null +++ b/upstreamCommits/Empirecraft @@ -0,0 +1 @@ +a8914cb8ed18d96eb1452d1e052c5e6d3007f1d7 \ No newline at end of file diff --git a/upstreamCommits/Origami b/upstreamCommits/Origami new file mode 100644 index 00000000..6e50f163 --- /dev/null +++ b/upstreamCommits/Origami @@ -0,0 +1 @@ +ab42df90d612411d873d4d3d426f17c4fbc78053 \ No newline at end of file diff --git a/upstreamCommits/Purpur b/upstreamCommits/Purpur new file mode 100644 index 00000000..5df85c7d --- /dev/null +++ b/upstreamCommits/Purpur @@ -0,0 +1 @@ +643d9681ff2d06283d4266341c0c65a531ea55fa \ No newline at end of file diff --git a/upstreamCommits/Tuinity b/upstreamCommits/Tuinity new file mode 100644 index 00000000..07f6713a --- /dev/null +++ b/upstreamCommits/Tuinity @@ -0,0 +1 @@ +d3ee22224addb3d6997feedf4eba95962786f7d7 \ No newline at end of file diff --git a/upstreamConfig/0001-Tuinity.properties b/upstreamConfig/0001-Tuinity.properties new file mode 100755 index 00000000..c56d97f4 --- /dev/null +++ b/upstreamConfig/0001-Tuinity.properties @@ -0,0 +1,4 @@ +name=Tuinity +useBlackList=True +list=server/Do-not-add-passengers-of-entities-that-were-were-abo.patch +branch=origin/master \ No newline at end of file diff --git a/upstreamConfig/0002-Purpur.properties b/upstreamConfig/0002-Purpur.properties new file mode 100755 index 00000000..4a009032 --- /dev/null +++ b/upstreamConfig/0002-Purpur.properties @@ -0,0 +1,4 @@ +name=Purpur +useBlackList=True +list=server/Tuinity-Server-Changes.patch,API/Tuinity-API-Changes.patch,server/Flying-squids-Oh-my.patch,server/Add-MC-4-fix-back.patch +branch=origin/ver/1.16.5 diff --git a/upstreamConfig/0003-AirplaneLite.properties b/upstreamConfig/0003-AirplaneLite.properties new file mode 100644 index 00000000..afb36aba --- /dev/null +++ b/upstreamConfig/0003-AirplaneLite.properties @@ -0,0 +1,4 @@ +name=AirplaneLite +useBlackList=False +list=server/AirplaneLite-MC-Dev-Fixes.patch,server/Strip-raytracing-for-EntityLiving-hasLineOfSight.patch,server/Simpler-ShapelessRecipes-comparison-for-Vanilla.patch,server/Queue-lighting-update-only-once.patch,server/Use-unmodifiableMap-instead-of-making-copy.patch,server/Swap-priority-of-checks-in-chunk-ticking.patch,server/Reduce-projectile-chunk-loading.patch,server/Optimize-random-calls-in-chunk-ticking.patch,server/Don-t-get-entity-equipment-if-not-needed.patch +branch=origin/master diff --git a/upstreamConfig/0004-Akarin.properties b/upstreamConfig/0004-Akarin.properties new file mode 100644 index 00000000..937e1c5b --- /dev/null +++ b/upstreamConfig/0004-Akarin.properties @@ -0,0 +1,4 @@ +name=Akarin +useBlackList=False +list=server/Disable-the-Snooper.patch,server/Avoid-double-I-O-operation-on-load-player-file.patch,server/Swaps-the-predicate-order-of-collision.patch +branch=origin/ver/1.16.4 diff --git a/upstreamConfig/0005-Empirecraft.properties b/upstreamConfig/0005-Empirecraft.properties new file mode 100644 index 00000000..44572f58 --- /dev/null +++ b/upstreamConfig/0005-Empirecraft.properties @@ -0,0 +1,4 @@ +name=Empirecraft +useBlackList=false +list=API/Add-ChatColor.getById.patch,server/Don-t-trigger-Lootable-Refresh-for-non-player-intera.patch,server/Fix-Bukkit.createInventory-with-type-LECTERN.patch,server/dont-load-chunks-for-physics.patch +branch=origin/master diff --git a/upstreamConfig/0006-Origami.properties b/upstreamConfig/0006-Origami.properties new file mode 100644 index 00000000..c4d80539 --- /dev/null +++ b/upstreamConfig/0006-Origami.properties @@ -0,0 +1,4 @@ +name=Origami +useBlackList=false +list=server/Origami-Server-Config.patch,server/Optimize-inventory-API-item-handling.patch,server/Don-t-load-chunk-with-seed-based-feature-search.patch,server/Remove-some-streams-and-object-allocations.patch,server/Hopper-Optimizations.patch,server/Add-option-to-disable-observer-clocks.patch,server/Add-timings-for-Behavior.patch,server/Don-t-wake-up-entities-when-damage-event-is-cancelle.patch,server/Fix-exp-drop-of-zombie-pigmen-MC-56653.patch +branch=origin/1.16 diff --git a/yatopia b/yatopia deleted file mode 100755 index 1b38937e..00000000 --- a/yatopia +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env bash - -# Yatopia Build Script - -############################################################################ -# Multicore building # -# By JosephWorks # -# -------------------------------------------------------------------------# -# To use multicore building, use "$mvncmd" instead of "maven" # -# 20-50% speed improvement is quite common. # -############################################################################ - -# Exit immediately if a command exits with a non-zero status -set -e - -source "./scripts/functions.sh" - -JAVA_VERSION=$(java -version 2>&1 | awk 'NR==1{ gsub(/"/,""); print $3 }') - -failed=0 -case "$1" in -"reset") - ( - $scriptdir/resetToUpstream.sh "$basedir" - ) || failed=1 - ;; -"i" | "in" | "init" | "initsubmodules") - ( - set -e - basedir - $scriptdir/initUpstream.sh "$basedir" || exit 1 - ) || failed=1 - ;; -"r" | "rb" | "rbp" | "rebuild") - ( - set -e - basedir - $scriptdir/rebuildPatches.sh "$basedir" || exit 1 - ) || failed=1 - ;; -"a" | "p" | "patch" | "apply") - ( - set -e - $scriptdir/applyPatches.sh "$basedir" || exit 1 - ) || failed=1 - ;; -"b" | "bu" | "build" | "install") - ( - echo "$JAVA_VERSION" - basedir - $mvncmd -N install surefire-report:report - cd ${FORK_NAME}-API - $mvncmd -e clean install && (cd ../Tuinity/Paper/Paper-MojangAPI && $mvncmd -e clean install) && cd ../${FORK_NAME}-Server && $mvncmd -e clean install - ) || failed=1 - ;; -"j" | "launcher" | "jar" | "paperclip") - ( - basedir - $scriptdir/installLauncher.sh "$basedir" - ) || failed=1 - ;; -"u" | "up" | "upstream" | "update") - ( - basedir - $scriptdir/updateUpstream.sh "$basedir" - ) - ;; -"tu" | "tup" | "tupstream" | "tupdate") - ( - $scriptdir/updateUpstream.sh "$basedir" true - ) - ;; -"r" | "root") - basedir - ;; -"a" | "api") - cd "$basedir/Yatopia-API" - ;; -"s" | "server") - cd "$basedir/Yatopia-Server" - ;; -"c" | "clean") - rm -rf Yatopia-API - rm -rf Yatopia-Server - rm -rf Paper - echo "Cleaned build files" - ;; -"clear") - rm -rf Yatopia-API - rm -rf Yatopia-Server - ;; -"fp" | "fpatch" | "fixpatch") - $scriptdir/fixPatch.sh "$basedir" $2 || exit 1 - ;; -"f" | "fu" | "full" | "fullbuild") - ( - echo "$JAVA_VERSION" - basedir - $scriptdir/updateUpstream.sh "$basedir" false true || exit 1 - set -e - $scriptdir/applyPatches.sh "$basedir" || exit 1 - basedir - $mvncmd -N install surefire-report:report - cd ${FORK_NAME}-API - ($mvncmd -e clean install && (cd ../Tuinity/Paper/Paper-MojangAPI && $mvncmd -e clean install) && cd ../${FORK_NAME}-Server && $mvncmd -e clean install surefire-report:report) || exit 1 - $scriptdir/installLauncher.sh "$basedir" || exit 1 - ) || failed=1 - ;; -"e" | "edit") - case "$2" in - "s" | "server") - export Yatopia_LAST_EDIT="$basedir/Yatopia-Server" - cd "$basedir/Yatopia-Server" - ( - set -e - gitstash - cd "$basedir/Paper/Paper-Server" - $gitcmd fetch --all - # Create the upstream branch in Paper project with current state - $gitcmd checkout master # possibly already in - $gitcmd branch -D upstream || true - $gitcmd branch -f upstream HEAD - cd "$basedir/Yatopia-Server" - $gitcmd rebase -i upstream/upstream - gitunstash - ) - ;; - "a" | "api") - export Yatopia_LAST_EDIT="$basedir/Yatopia-API" - cd "$basedir/Yatopia-API" - ( - set -e - gitstash - cd "$basedir/Paper/Paper-API" - $gitcmd fetch --all - # Create the upstream branch in Paper project with current state - $gitcmd checkout master # possibly already in - $gitcmd branch -D upstream || true - $gitcmd branch -f upstream HEAD - cd "$basedir/Yatopia-API" - $gitcmd rebase -i upstream/upstream - gitunstash - ) - ;; - "c" | "continue") - cd "$Yatopia_LAST_EDIT" - unset Yatopia_LAST_EDIT - ( - set -e - $gitcmd add . - $gitcmd commit --amend - $gitcmd rebase --continue - basedir - $scriptdir/rebuildPatches.sh "$basedir" - ) - ;; - *) - echo "You must edit either the api or server." - ;; - esac - ;; -"setup") - if [[ -f ~/.bashrc ]]; then - NAME="Yatopia" - if [[ ! -z "${2+x}" ]]; then - NAME="$2" - fi - (grep "alias $NAME=" ~/.bashrc >/dev/null) && (sed -i "s|alias $NAME=.*|alias $NAME='. $SOURCE'|g" ~/.bashrc) || (echo "alias $NAME='. $SOURCE'" >>~/.bashrc) - alias "$NAME=. $SOURCE" - echo "You can now just type '$NAME' at any time to access the Yatopia tool." - fi - ;; -"mcserverdep") - ./scripts/instRemappedJar.sh - ;; -"commitUpstream" | "cu" | "commitUp" | "commerge") - ./scripts/commitUpstream.sh - ;; -*) - echo "Yatopia build tool command. This provides a variety of commands to build and manage the Yatopia build" - echo "environment. For all of the functionality of this command to be available, you must first run the" - echo "'setup' command. View below for details. For essential building and patching, you do not need to do the setup." - echo "" - echo " Normal commands:" - echo " * r, rebuild | Rebuild patches, can be called from anywhere." - echo " * p, patch | Apply all patches to top of Paper without building it. Can be run from anywhere." - echo " * u, update | Updates all submodules. Can be run from anywhere." - echo " * b, build | Build the projects, including the API and the Server. Can be ran anywhere." - echo " * j, jar | Build the projects and build the launcher jar. Can be ran anywhere." - echo " * d, deploy | Build the projects and deploy through Maven. Can be ran anywhere." - echo " * tupstream | Updates only tuinity submodule. Can be ran everywhere." - echo "" - echo " These commands require the setup command before use:" - echo " * r, root | Change directory to the root of the project." - echo " * a. api | Move to the Yatopia-API directory." - echo " * s, server | Move to the Yatopia-Server directory." - echo " * e, edit | Use to edit a specific patch, give it the argument \"server\" or \"api\"" - echo " | respectively to edit the correct project. Use the argument \"continue\" after" - echo " | the changes have been made to finish and rebuild patches. Can be called from anywhere." - echo "" - echo " * setup | Add an alias to .bashrc to allow full functionality of this script. Run as:" - echo " | . ./Yatopia setup" - echo " | After you run this command you'll be able to just run 'Yatopia' from anywhere." - echo " | The default name for the resulting alias is 'Yatopia', you can give an argument to override" - echo " | this default, such as:" - echo " | . ./Yatopia setup example" - echo " | Which will allow you to run 'example' instead." - ;; -esac - -unset RCPATH -unset SOURCE -unset basedir -unset -f color -unset -f colorend -unset -f gitstash -unset -f gitunstash -if [[ "$failed" == "1" ]]; then - unset failed - false -else - unset failed - true -fi