diff --git a/README.md b/README.md index f5124b3b..60715a88 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ If these excellent projects hadn't existed, Leaf wouldn't have become great. ## 🔥 Special Thanks -Jianke Cloud Host +[![Jianke Cloud Host](public/image/JiankeServer.jpg)](https://cloud.swordsman.com.cn/?i8ab42c) \ cloud of swordsman | 剑客云 If you want to find a cheaper, high performance, stable, lower latency host, then cloud of swordsman is a good choice! Registers and purchases in [here](https://cloud.swordsman.com.cn/?i8ab42c). @@ -131,7 +131,15 @@ If you want to find a cheaper, high performance, stable, lower latency host, the 如果你想找一个低价高性能、低延迟的云服务商,剑客云是个不错的选择!你可以在 [这里](https://cloud.swordsman.com.cn/?i8ab42c) 注册。 --- -![YourKit](https://www.yourkit.com/images/yklogo.png) +[![雨云](public/image/RainYun.jpg)](https://www.rainyun.com/NzE2NTc1_) \ +RainYun | 雨云 + +Global multi-line routing with cloud storage. Refund available within 7 days. Reliable uptime and expert support. RainYun — stable, cost-effective, and ready for fast cloud deployment. Visit [RainYun]([雨云](https://www.rainyun.com/NzE2NTc1_)) + +国际多线路选择,配套云存储 — 购买服务后七天内不满意可以申请退订,强大的技术支持团队和高在线率客服。雨云云服务器,用稳定和性价比,助力您快速上云。点击前往 [雨云](https://www.rainyun.com/NzE2NTc1_)。 + +--- +![YourKit](https://www.yourkit.com/images/yklogo.png) \ YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. diff --git a/leaf-api/paper-patches/features/0017-Configurable-LibraryLoader-maven-repos.patch b/leaf-api/paper-patches/features/0017-Configurable-LibraryLoader-maven-repos.patch index 3e78fd90..c5f98235 100644 --- a/leaf-api/paper-patches/features/0017-Configurable-LibraryLoader-maven-repos.patch +++ b/leaf-api/paper-patches/features/0017-Configurable-LibraryLoader-maven-repos.patch @@ -3,76 +3,91 @@ From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Sat, 3 Feb 2024 18:45:53 -0500 Subject: [PATCH] Configurable LibraryLoader maven repos -TODO - Dreeam: Support multi maven repos for lib downloading. - Add JVM flag `-DLeaf.library-download-repo=link` to choose library download repo link. e.g. `-DLeaf.library-download-repo=https://maven.aliyun.com/repository/public` diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java -index 107705db2d82b7c191e5e625ec888e0bc3b03831..77a58fc7c173b1724d44b0eeaf23b4a1b22b5fcb 100644 +index 107705db2d82b7c191e5e625ec888e0bc3b03831..d7dc6ff0b372de04c956cae6fc27d2679b88e0f7 100644 --- a/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java +++ b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java -@@ -105,7 +105,7 @@ public class MavenLibraryResolver implements ClassPathLibrary { - * dependencies from +@@ -3,9 +3,11 @@ package io.papermc.paper.plugin.loader.library.impl; + import io.papermc.paper.plugin.loader.library.ClassPathLibrary; + import io.papermc.paper.plugin.loader.library.LibraryLoadingException; + import io.papermc.paper.plugin.loader.library.LibraryStore; ++ + import java.io.File; + import java.util.ArrayList; + import java.util.List; ++ + import org.apache.maven.repository.internal.MavenRepositorySystemUtils; + import org.eclipse.aether.DefaultRepositorySystemSession; + import org.eclipse.aether.RepositorySystem; +@@ -57,6 +59,13 @@ public class MavenLibraryResolver implements ClassPathLibrary { + private final List repositories = new ArrayList<>(); + private final List dependencies = new ArrayList<>(); + ++ // Leaf start - Configurable LibraryLoader maven repos ++ @org.jspecify.annotations.Nullable ++ public static final RemoteRepository MAVEN_CENTRAL_MIRROR_REPO = getCentralMirrorRepo(); ++ private static final String[] MAVEN_CENTRAL_URLS = new String[]{ ++ }; ++ // Leaf end - Configurable LibraryLoader maven repos ++ + /** + * Creates a new maven library resolver instance. + *

+@@ -102,9 +111,24 @@ public class MavenLibraryResolver implements ClassPathLibrary { + * repository. + * + * @param remoteRepository the configuration that defines the maven repository this library resolver should fetch +- * dependencies from ++ * dependencies from */ public void addRepository(final RemoteRepository remoteRepository) { -- this.repositories.add(remoteRepository); -+ this.repositories.add(org.dreeam.leaf.plugin.loader.MavenCentralMirror.getCentralRepo(remoteRepository)); // Leaf - Configurable LibraryLoader maven repos ++ // Leaf start - Configurable LibraryLoader maven repos ++ for (String url : MAVEN_CENTRAL_URLS) { ++ if (remoteRepository.getUrl().startsWith(url)) { ++ RemoteRepository mirrorRepo = MAVEN_CENTRAL_MIRROR_REPO; ++ if (mirrorRepo != null) { ++ this.repositories.add(mirrorRepo); ++ return; ++ } ++ LOGGER.warn( ++ "Use of Maven Central as a CDN is against the Maven Central Terms of Service. Use MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR instead.", ++ new RuntimeException("Plugin used Maven Central for library resolution") ++ ); ++ } ++ } ++ // Leaf end - Configurable LibraryLoader maven repos + this.repositories.add(remoteRepository); } - /** +@@ -130,4 +154,15 @@ public class MavenLibraryResolver implements ClassPathLibrary { + store.addLibrary(file.toPath()); + } + } ++ ++ // Leaf start - Configurable LibraryLoader maven repos ++ @org.jspecify.annotations.Nullable ++ private static RemoteRepository getCentralMirrorRepo() { ++ String mirrorAddr = System.getProperty("Leaf.library-download-repo"); ++ if (mirrorAddr != null) { ++ new RemoteRepository.Builder("central", "default", mirrorAddr).build(); ++ } ++ return null; ++ } ++ // Leaf end - Configurable LibraryLoader maven repos + } diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java -index cfe41c0a67c8d729b6bd23b0cfa32db3c9db9f74..9f167a9cb4a93a79b8ed709b61214ce0138a875d 100644 +index cfe41c0a67c8d729b6bd23b0cfa32db3c9db9f74..05d3b793bf9cb320774f9f488a3b78bedab6a9d4 100644 --- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java -@@ -74,7 +74,19 @@ public class LibraryLoader { - session.setSystemProperties(System.getProperties()); - session.setReadOnly(); +@@ -47,6 +47,8 @@ public class LibraryLoader { + public static java.util.function.BiFunction LIBRARY_LOADER_FACTORY; // Paper - rewrite reflection in libraries + public static java.util.function.Function, List> REMAPPER; // Paper - remap libraries -- this.repositories = repository.newResolutionRepositories(session, Arrays.asList(new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2").build())); -+ // Leaf start - Configurable LibraryLoader maven repos -+ this.repositories = repository.newResolutionRepositories( -+ session, -+ List.of(org.dreeam.leaf.plugin.loader.MavenCentralMirror.getCentralRepo("https://repo.maven.apache.org/maven2")) -+ ); -+ /* // Dreeam TODO -+ this.repositories = repository.newResolutionRepositories(session, List.of( -+ new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2").build(), -+ new RemoteRepository.Builder("aliyun", "default", "https://maven.aliyun.com/repository/public").build(), -+ new RemoteRepository.Builder("tencentclound", "default", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public/").build(), -+ new RemoteRepository.Builder("huaweicloud", "default", "https://repo.huaweicloud.com/repository/maven/").build() -+ ));*/ -+ // Leaf end - Configurable LibraryLoader maven repos - } ++ if (io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver.MAVEN_CENTRAL_MIRROR_REPO != null) return List.of(io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver.MAVEN_CENTRAL_MIRROR_REPO); // Leaf - Configurable LibraryLoader maven repos ++ + public LibraryLoader(@NotNull Logger logger) { + this.logger = logger; - @Nullable -diff --git a/src/main/java/org/dreeam/leaf/plugin/loader/MavenCentralMirror.java b/src/main/java/org/dreeam/leaf/plugin/loader/MavenCentralMirror.java -new file mode 100644 -index 0000000000000000000000000000000000000000..95534cb6d771a0fe288e7c843c41b0036bdc7095 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/plugin/loader/MavenCentralMirror.java -@@ -0,0 +1,24 @@ -+package org.dreeam.leaf.plugin.loader; -+ -+import org.eclipse.aether.repository.RemoteRepository; -+ -+public class MavenCentralMirror { -+ -+ public static final String MAVEN_CENTRAL_MIRROR_REPO = System.getProperty("Leaf.library-download-repo"); -+ -+ public static RemoteRepository getCentralRepo(RemoteRepository repo) { -+ if (MAVEN_CENTRAL_MIRROR_REPO != null && repo.getUrl().contains("repo.maven.apache.org/maven2")) { -+ repo = new RemoteRepository.Builder("central", "default", MAVEN_CENTRAL_MIRROR_REPO).build(); -+ } -+ -+ return repo; -+ } -+ -+ public static RemoteRepository getCentralRepo(String repo) { -+ if (MAVEN_CENTRAL_MIRROR_REPO != null) { -+ repo = MAVEN_CENTRAL_MIRROR_REPO; -+ } -+ -+ return new RemoteRepository.Builder("central", "default", repo).build(); -+ } -+} diff --git a/leaf-archived-patches/removed/hardfork/api/0004-Paper-Avoid-and-discourage-use-of-Maven-Central-as-a.patch b/leaf-archived-patches/removed/hardfork/api/0004-Paper-Avoid-and-discourage-use-of-Maven-Central-as-a.patch new file mode 100644 index 00000000..6c8f08ba --- /dev/null +++ b/leaf-archived-patches/removed/hardfork/api/0004-Paper-Avoid-and-discourage-use-of-Maven-Central-as-a.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 18 Jun 2025 10:47:21 -0700 +Subject: [PATCH] Paper: Avoid and discourage use of Maven Central as a CDN + +Removed since Leaf 1.21.6, added in Paper + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper + +https://github.com/PaperMC/Paper/commit/62b7f86dae659deb2fc450285452d7c1439f92dc + +Default LibraryLoader to Google's Maven Central mirror, add MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR, and warn on use of Maven Central with MavenLibraryResolver + +https://www.sonatype.com/blog/maven-central-and-the-tragedy-of-the-commons +https://www.sonatype.com/blog/beyond-ips-addressing-organizational-overconsumption-in-maven-central + +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java +index 107705db2d82b7c191e5e625ec888e0bc3b03831..ebb52c2c8d5fe8ca25513aadae8168180a3d426e 100644 +--- a/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java +@@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory; + * MavenLibraryResolver resolver = new MavenLibraryResolver(); + * resolver.addDependency(new Dependency(new DefaultArtifact("org.jooq:jooq:3.17.7"), null)); + * resolver.addRepository(new RemoteRepository.Builder( +- * "central", "default", "https://repo1.maven.org/maven2/" ++ * "central", "default", MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR // Paper - Avoid and discourage use of Maven Central as a CDN + * ).build()); + * } + *

+@@ -50,6 +50,24 @@ import org.slf4j.LoggerFactory; + @NullMarked + public class MavenLibraryResolver implements ClassPathLibrary { + ++ // Paper start - Avoid and discourage use of Maven Central as a CDN ++ /** ++ * The default Maven Central mirror, configurable through the {@code PAPER_DEFAULT_CENTRAL_REPOSITORY} environment ++ * variable. Use this instead of Maven Central directly when you do not have your own mirror, as using ++ * Maven Central as a CDN is against the Maven Central Terms of Service, and you will cause users to hit ++ * rate limits. ++ * ++ *

This repository is also used by the legacy {@link org.bukkit.plugin.java.LibraryLoader}.

++ */ ++ public static final String MAVEN_CENTRAL_DEFAULT_MIRROR = getDefaultMavenCentralMirror(); ++ private static final List MAVEN_CENTRAL_URLS = List.of( ++ "https://repo1.maven.org/maven2", ++ "http://repo1.maven.org/maven2", ++ "https://repo.maven.apache.org/maven2", ++ "http://repo.maven.apache.org/maven2" ++ ); ++ // Paper end - Avoid and discourage use of Maven Central as a CDN ++ + private static final Logger LOGGER = LoggerFactory.getLogger("MavenLibraryResolver"); + + private final RepositorySystem repository; +@@ -105,6 +123,14 @@ public class MavenLibraryResolver implements ClassPathLibrary { + * dependencies from + */ + public void addRepository(final RemoteRepository remoteRepository) { ++ // Paper start - Avoid and discourage use of Maven Central as a CDN ++ if (MAVEN_CENTRAL_URLS.stream().anyMatch(remoteRepository.getUrl()::startsWith)) { ++ LOGGER.warn( ++ "Use of Maven Central as a CDN is against the Maven Central Terms of Service. Use MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR instead.", ++ new RuntimeException("Plugin used Maven Central for library resolution") ++ ); ++ } ++ // Paper end - Avoid and discourage use of Maven Central as a CDN + this.repositories.add(remoteRepository); + } + +@@ -130,4 +156,17 @@ public class MavenLibraryResolver implements ClassPathLibrary { + store.addLibrary(file.toPath()); + } + } ++ ++ // Paper start - Avoid and discourage use of Maven Central as a CDN ++ private static String getDefaultMavenCentralMirror() { ++ String central = System.getenv("PAPER_DEFAULT_CENTRAL_REPOSITORY"); ++ if (central == null) { ++ central = System.getProperty("org.bukkit.plugin.java.LibraryLoader.centralURL"); ++ } ++ if (central == null) { ++ central = "https://maven-central.storage-download.googleapis.com/maven2"; ++ } ++ return central; ++ } ++ // Paper end - Avoid and discourage use of Maven Central as a CDN + } +diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +index 7e4e702845f61703f0741add59f7cfc0afea1543..012ba8ee3d84a7bb09068e42fd1bae8ad221622e 100644 +--- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +@@ -47,19 +47,11 @@ public class LibraryLoader { + public static java.util.function.BiFunction LIBRARY_LOADER_FACTORY; // Paper - rewrite reflection in libraries + public static java.util.function.Function, List> REMAPPER; // Paper - remap libraries + +- // TODO: Consider moving this and adding per plugin support for defining repositories ++ // Paper start - Avoid and discourage use of Maven Central as a CDN + private static List getRepositories() { +- String central = System.getenv("PAPER_DEFAULT_CENTRAL_REPOSITORY"); +- if (central == null) { +- central = System.getProperty("org.bukkit.plugin.java.LibraryLoader.centralURL"); +- } +- if (central == null) { +- central = "https://repo.maven.apache.org/maven2"; +- } +- +- return Arrays.asList(new RemoteRepository.Builder("central", "default", central).build()); +- ++ return List.of(new RemoteRepository.Builder("central", "default", io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR).build()); + } ++ // Paper end - Avoid and discourage use of Maven Central as a CDN + + public LibraryLoader(@NotNull Logger logger) { + this.logger = logger; +@@ -87,7 +79,7 @@ public class LibraryLoader { + session.setSystemProperties(System.getProperties()); + session.setReadOnly(); + +- this.repositories = repository.newResolutionRepositories(session, getRepositories()); ++ this.repositories = repository.newResolutionRepositories(session, getRepositories()); // Paper - Avoid and discourage use of Maven Central as a CDN + } + + @Nullable diff --git a/leaf-server/minecraft-patches/features/0274-PaperPR-Fix-excess-slot-updates-inventory-state-id-d.patch b/leaf-archived-patches/removed/hardfork/server/0275-Paper-Fix-excess-slot-updates.patch similarity index 58% rename from leaf-server/minecraft-patches/features/0274-PaperPR-Fix-excess-slot-updates-inventory-state-id-d.patch rename to leaf-archived-patches/removed/hardfork/server/0275-Paper-Fix-excess-slot-updates.patch index 24f71905..41e1a530 100644 --- a/leaf-server/minecraft-patches/features/0274-PaperPR-Fix-excess-slot-updates-inventory-state-id-d.patch +++ b/leaf-archived-patches/removed/hardfork/server/0275-Paper-Fix-excess-slot-updates.patch @@ -1,23 +1,18 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Leymooo -Date: Wed, 11 Jun 2025 05:30:39 +0300 -Subject: [PATCH] PaperPR: Fix excess slot updates / inventory state id desync +From: AJ Ferguson +Date: Mon, 6 Jan 2025 20:31:00 +1100 +Subject: [PATCH] Paper: Fix excess slot updates + +Removed since Leaf 1.21.6, added in Paper Original license: GPLv3 -Original project: https://github.com/SparklyPower/SparklyPaper +Original project: https://github.com/PaperMC/Paper Paper pull request: https://github.com/PaperMC/Paper/pull/12654 -Fixes inventory state id desync with high ping causing inventory to be "buggy" (ghost items, items jumping to/from cursor, etc) - -Without patch: https://youtu.be/hOXt3Rpkgtg -With patch: https://youtu.be/MtOYD7uieS4 - -https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/df01cf867a2255024aacf9ac2ff6a56b2ffb7ce5#nms-patches/net/minecraft/world/inventory/Container.patch - -https://discord.com/channels/289587909051416579/555462289851940864/1382165293308182699 +https://github.com/PaperMC/Paper/commit/e714de636543d61fcd3682705484c8a15f4f3ca6 diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java -index 9dd3187fd968ab95e9d55b4c8cc74e782cc0f241..c5ede24d00f444d04c835af3a7f0154227492be4 100644 +index 9dd3187fd968ab95e9d55b4c8cc74e782cc0f241..46c3353a9722cb9c8073cadbad0ced57bab0c390 100644 --- a/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -559,7 +559,7 @@ public abstract class AbstractContainerMenu { @@ -25,7 +20,7 @@ index 9dd3187fd968ab95e9d55b4c8cc74e782cc0f241..c5ede24d00f444d04c835af3a7f01542 slot.setChanged(); // CraftBukkit start - Make sure the client has the right slot contents - if (player instanceof ServerPlayer serverPlayer && slot.getMaxStackSize() != 64) { -+ if (player instanceof ServerPlayer serverPlayer && slot.getMaxStackSize() != net.minecraft.world.Container.MAX_STACK) { // Paper - Fix excess slot updates / inventory state id desync ++ if (player instanceof ServerPlayer serverPlayer && slot.getMaxStackSize() != Container.MAX_STACK) { // Paper - craftbukkkit - Fix excess slot updates serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem())); // Updating a crafting inventory makes the client reset the result slot, have to send it again if (this.getBukkitView().getType() == org.bukkit.event.inventory.InventoryType.WORKBENCH || this.getBukkitView().getType() == org.bukkit.event.inventory.InventoryType.CRAFTING) { diff --git a/leaf-archived-patches/work/server/0062-Add-missing-rewrite-rules.patch b/leaf-archived-patches/work/server/0062-Add-missing-rewrite-rules.patch new file mode 100644 index 00000000..fe3c123f --- /dev/null +++ b/leaf-archived-patches/work/server/0062-Add-missing-rewrite-rules.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Add missing rewrite rules + +TODO: needs to check and fix it. +The new added rewriting rules cause incompatible with Nova plugin (The call Registry<*>::freeze in RegistryEventsPatch#transform) +It breaks kotlin reflection, makes it can't get all members of Registry class and its parent interfaces. +to validate using Nova plugin: +``` +println(net.minecraft.core.Registry::class.members.size) +println(net.minecraft.core.Registry::class.java.methods.size) +``` + +Tracking PR: https://github.com/PaperMC/asm-utils/pull/48 + +diff --git a/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java b/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java +index a3045afbc0cc057e99189b909367b21cf6a9e03f..2831b8b48a12815f8fb7e82327f654e90d1530a3 100644 +--- a/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java ++++ b/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java +@@ -22,7 +22,14 @@ public final class ReflectionRemapper { + private static final RewriteRuleVisitorFactory VISITOR_FACTORY = RewriteRuleVisitorFactory.create( + Opcodes.ASM9, + chain -> chain.then(new BaseReflectionRules(PAPER_REFLECTION_HOLDER).rules()) +- .then(DefineClassRule.create(PAPER_REFLECTION_HOLDER_DESC, true)), ++ // Leaf start - Add missing rewrite rules ++ .then( ++ io.papermc.asm.rules.RewriteRule.forOwnerClass(Class.class, rf -> { ++ rf.plainStaticRewrite(java.lang.constant.ClassDesc.of(PAPER_REFLECTION_HOLDER), b -> b ++ .match("forName").desc("(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;")); ++ }) ++ ).then(DefineClassRule.create(PAPER_REFLECTION_HOLDER_DESC, true)), ++ // Leaf end - Add missing rewrite rules + ClassInfoProvider.basic() + ); + diff --git a/leaf-server/minecraft-patches/features/0178-Asynchronous-locator.patch b/leaf-server/minecraft-patches/features/0178-Asynchronous-locator.patch index 828d7b47..9daee4e7 100644 --- a/leaf-server/minecraft-patches/features/0178-Asynchronous-locator.patch +++ b/leaf-server/minecraft-patches/features/0178-Asynchronous-locator.patch @@ -7,48 +7,78 @@ Original license: MIT Original project: https://github.com/thebrightspark/AsyncLocator diff --git a/net/minecraft/server/commands/LocateCommand.java b/net/minecraft/server/commands/LocateCommand.java -index a734b2597c3491db35d9660e169f8e8b6320900b..8a79a339757d6ac49713bf6db0fb675d9893fd1b 100644 +index a734b2597c3491db35d9660e169f8e8b6320900b..5274f09b0abf148aea1c0baa39edbfdac1acc4f5 100644 --- a/net/minecraft/server/commands/LocateCommand.java +++ b/net/minecraft/server/commands/LocateCommand.java -@@ -106,6 +106,38 @@ public class LocateCommand { +@@ -106,6 +106,34 @@ public class LocateCommand { BlockPos blockPos = BlockPos.containing(source.getPosition()); ServerLevel level = source.getLevel(); Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); + // Leaf start - Asynchronous locator + if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled) { -+ net.minecraft.commands.CommandSource locatorSource = source.source; -+ if (locatorSource instanceof net.minecraft.server.level.ServerPlayer || locatorSource instanceof net.minecraft.server.MinecraftServer) { -+ BlockPos originPos = BlockPos.containing(source.getPosition()); ++ BlockPos originPos = BlockPos.containing(source.getPosition()); ++ org.dreeam.leaf.async.locate.AsyncLocator.locate(source.getLevel(), holderSet, originPos, 100, false) ++ .thenOnServerThread(pair -> { ++ stopwatch.stop(); ++ if (pair != null) { ++ showLocateResult( ++ source, ++ structure, ++ originPos, ++ pair, ++ "commands.locate.structure.success", ++ false, ++ stopwatch.elapsed() ++ ); ++ } else { ++ source.sendFailure( ++ Component.literal( ++ ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable()).getMessage() ++ ) ++ ); ++ } ++ }); + -+ org.dreeam.leaf.async.locate.AsyncLocator.locate(source.getLevel(), holderSet, originPos, 100, false) -+ .thenOnServerThread(pair -> { -+ stopwatch.stop(); -+ if (pair != null) { -+ showLocateResult( -+ source, -+ structure, -+ originPos, -+ pair, -+ "commands.locate.structure.success", -+ false, -+ stopwatch.elapsed() -+ ); -+ } else { -+ source.sendFailure( -+ Component.literal( -+ ERROR_STRUCTURE_NOT_FOUND.create(structure.asPrintable()).getMessage() -+ ) -+ ); -+ } -+ }); -+ -+ return 0; -+ } ++ return 0; + } + // Leaf end - Asynchronous locator Pair> pair = level.getChunkSource().getGenerator().findNearestMapStructure(level, holderSet, blockPos, 100, false); stopwatch.stop(); if (pair == null) { +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index eaaa66c4d86d4ebda0acf8f1dbe8ecb55aa28285..8f41326fda8c5f9f6926038508be6c6529b051bc 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -852,14 +852,25 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + + @Override + public boolean pollTask() { ++ // Leaf start - Async Locator + // Paper start - rewrite chunk system +- final ServerChunkCache serverChunkCache = ServerChunkCache.this; +- if (serverChunkCache.runDistanceManagerUpdates()) { +- return true; ++ java.util.function.Supplier supplier = () -> { ++ final ServerChunkCache serverChunkCache = ServerChunkCache.this; ++ if (serverChunkCache.runDistanceManagerUpdates()) { ++ return true; ++ } else { ++ return super.pollTask() | ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask(); ++ } ++ }; ++ if (org.dreeam.leaf.config.modules.async.AsyncLocator.enabled && Thread.currentThread() instanceof org.dreeam.leaf.async.locate.AsyncLocator.AsyncLocatorThread) { ++ return MinecraftServer.getServer().scheduleWithResult((java.util.concurrent.CompletableFuture future) -> { ++ future.complete(supplier.get()); ++ }).join(); + } else { +- return super.pollTask() | ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask(); ++ return supplier.get(); + } + // Paper end - rewrite chunk system ++ // Leaf end - Async Locator + } + } + } diff --git a/net/minecraft/world/entity/animal/Dolphin.java b/net/minecraft/world/entity/animal/Dolphin.java index 23696a5e2871ea07f34d4b4f6a20e2896ac3f5bd..c4fda92e078c9ba745b2548ecaaffffff97fb0fd 100644 --- a/net/minecraft/world/entity/animal/Dolphin.java diff --git a/leaf-server/minecraft-patches/features/0238-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0238-Async-target-finding.patch index 1f922183..41ad8fa9 100644 --- a/leaf-server/minecraft-patches/features/0238-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0238-Async-target-finding.patch @@ -134,7 +134,7 @@ index 50630dabe4220b8ac810a0395d70360879fdeabc..2e03020a67c7f59a3c554ab720fba831 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index ce39571c292c0dd58075e159c343898a374f008a..117a8ef98c369bf0919e44fe823d6af0758816b5 100644 +index ce39571c292c0dd58075e159c343898a374f008a..27da552e2542153a58d6177f592cf30d858c41a9 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -175,7 +175,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -163,26 +163,13 @@ index ce39571c292c0dd58075e159c343898a374f008a..117a8ef98c369bf0919e44fe823d6af0 @Override public @Nullable LevelChunk getChunkIfLoaded(int x, int z) { -@@ -337,6 +347,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); - } - -+ // Leaf start - Async target finding -+ public final void leafMidTickTasks() { -+ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.midTick(); -+ } -+ // Leaf end - Async target finding -+ - @Override - public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus status) { - return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status); -@@ -707,6 +723,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -707,6 +717,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle this.realPlayers = Lists.newArrayList(); // Leaves - skip this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking + // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.asyncGoalExecutor = new org.dreeam.leaf.async.ai.AsyncGoalExecutor(server.asyncGoalThread, this); ++ this.asyncGoalExecutor = new org.dreeam.leaf.async.ai.AsyncGoalExecutor(this); + } else { + this.asyncGoalExecutor = null; + } @@ -190,39 +177,8 @@ index ce39571c292c0dd58075e159c343898a374f008a..117a8ef98c369bf0919e44fe823d6af0 } // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads -@@ -980,12 +1003,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - this.moonrise$midTickTasks(); // Paper - rewrite chunk system - // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick -+ this.leafMidTickTasks(); // Leaf - Async target finding - } - } - } - } - ); - this.tickBlockEntities(); -+ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.tick(); // Leaf - Async target finding - } - // Paper - rewrite chunk system - } -@@ -1468,6 +1493,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - // Paper end - rewrite chunk system - -+ this.leafMidTickTasks(); // Leaf - Async target finding - } - - private void tickBlock(BlockPos pos, Block block) { -@@ -1484,6 +1510,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - // Paper end - rewrite chunk system - -+ this.leafMidTickTasks(); // Leaf - Async target finding - } - - // Paper start - log detailed entity tick information diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index 4a1a336914eff8a178dcd992bd1bfea25a8be76b..dbfd7301c7b2644ac26a5ee3f804236ee48f5aed 100644 +index 4a1a336914eff8a178dcd992bd1bfea25a8be76b..7e3b35df01b63735fd4d5202788477a1342708a5 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -138,6 +138,12 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -238,68 +194,39 @@ index 4a1a336914eff8a178dcd992bd1bfea25a8be76b..dbfd7301c7b2644ac26a5ee3f804236e protected Mob(EntityType entityType, Level level) { super(entityType, level); -@@ -216,12 +222,22 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - } - // Paper end - Skip AI during inactive ticks for non-aware mobs - boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking -+ // Leaf start - Async target finding -+ boolean running = this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1; -+ this.tickingTarget = false; - if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking - this.goalSelector.tick(); - } -+ this.tickingTarget = true; +@@ -222,6 +228,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority this.targetSelector.tick(); } ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ if (!running && (this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1)) { -+ ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); -+ } ++ ((ServerLevel) this.level()).asyncGoalExecutor.tickMob(this); + } + // Leaf end - Async target finding } // Paper end -@@ -782,17 +798,29 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - // Paper end - Allow nerfed mobs to jump and float - this.sensing.tick(); - int i = this.tickCount + this.getId(); -+ // Leaf start - Async target finding -+ boolean running = this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1; - if (i % 2 != 0 && this.tickCount > 1) { -+ this.tickingTarget = true; - if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.targetSelector.tickRunningGoals(false); -+ this.tickingTarget = false; - if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.goalSelector.tickRunningGoals(false); - } else { -+ this.tickingTarget = true; - if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.targetSelector.tick(); -+ this.tickingTarget = false; +@@ -793,6 +804,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.goalSelector.tick(); } ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ if (!running && (this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1)) { -+ ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); -+ } ++ ((ServerLevel) this.level()).asyncGoalExecutor.tickMob(this); + } + // Leaf end - Async target finding this.navigation.tick(); this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..ed3c5ad51ec0b9a1997e732e11db99ce06db7307 100644 +index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..7b668d3bc679239668642a6d1d124bab67a26000 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java @@ -67,15 +67,24 @@ public class AvoidEntityGoal extends Goal { @Override public boolean canUse() { -+ // Leaf start - Async Avoid Entity Finding ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (!poll()) { + getNearestEntityAsync(); @@ -313,11 +240,11 @@ index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..ed3c5ad51ec0b9a1997e732e11db99ce this.mob, this.mob.getX(), - this.mob.getY(), -+ this.mob.getEyeY(), // Leaf - Async Avoid Entity Finding ++ this.mob.getEyeY(), // Leaf - Async target finding this.mob.getZ() ); + } -+ // Leaf end - Async Avoid Entity Finding ++ // Leaf end - Async target finding if (this.toAvoid == null) { return false; } else { @@ -325,7 +252,7 @@ index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..ed3c5ad51ec0b9a1997e732e11db99ce } } -+ // Leaf start - Async Avoid Entity Finding ++ // Leaf start - Async target finding + private boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + var serverLevel = getServerLevel(this.mob); @@ -353,20 +280,20 @@ index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..ed3c5ad51ec0b9a1997e732e11db99ce + z + ); + } -+ // Leaf end - Async Avoid Entity Finding ++ // Leaf end - Async target finding + @Override public boolean canContinueToUse() { return !this.pathNav.isDone(); diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java -index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..9f2658f49640c4f5c2de0c2810b3042b385f688a 100644 +index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..75dfa51d350bf767b269670c78d9a6253491acc7 100644 --- a/net/minecraft/world/entity/ai/goal/BegGoal.java +++ b/net/minecraft/world/entity/ai/goal/BegGoal.java @@ -27,8 +27,43 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.wolf.getGoalCtx().result() instanceof Player target)) return false; + if (target == null) return false; @@ -390,11 +317,11 @@ index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..9f2658f49640c4f5c2de0c2810b3042b + return null; + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public boolean canUse() { -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -402,7 +329,7 @@ index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..9f2658f49640c4f5c2de0c2810b3042b + findTargetAsync(); + return false; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); return this.player != null && this.playerHoldingInteresting(this.player); } @@ -411,16 +338,16 @@ index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..9f2658f49640c4f5c2de0c2810b3042b } - private boolean playerHoldingInteresting(Player player) { -+ private static boolean playerHoldingInteresting(Player player) { // Leaf start - Async Target Finding - static ++ private static boolean playerHoldingInteresting(Player player) { // Leaf start - Async target finding - static for (InteractionHand interactionHand : InteractionHand.values()) { ItemStack itemInHand = player.getItemInHand(interactionHand); - if (itemInHand.is(Items.BONE) || this.wolf.isFood(itemInHand)) { -+ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { // Leaf end - Async Target Finding ++ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { // Leaf end - Async target finding return true; } } diff --git a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java -index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..b3205c9f35687bc37124876198ec2d657ccaa96c 100644 +index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..892e22b80d8c98ea2954b4024ba434da5a1abffa 100644 --- a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java +++ b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java @@ -52,6 +52,13 @@ public class CatLieOnBedGoal extends MoveToBlockGoal { @@ -428,18 +355,18 @@ index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..b3205c9f35687bc37124876198ec2d65 @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { - return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); -+ return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); // Leaf - Async search block - diff on change ++ return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); // Leaf - Async target finding - diff on change } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.CatLie; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } diff --git a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java -index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..057090e3134048e75dbaefb703e8f2d35a172a3b 100644 +index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..79e71a41245028042b8ac5a56cd39bd0940c37f5 100644 --- a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java @@ -44,14 +44,21 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal { @@ -447,11 +374,11 @@ index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..057090e3134048e75dbaefb703e8f2d3 @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { - if (!level.isEmptyBlock(pos.above())) { -+ if (!level.isEmptyBlock(pos.above())) { // Leaf - Async search block - diff on change ++ if (!level.isEmptyBlock(pos.above())) { // Leaf - Async target finding - diff on change return false; } else { - BlockState blockState = level.getBlockState(pos); -+ BlockState blockState = level.getBlockState(pos); // Leaf - Async search block - diff on change ++ BlockState blockState = level.getBlockState(pos); // Leaf - Async target finding - diff on change return blockState.is(Blocks.CHEST) ? ChestBlockEntity.getOpenCount(level, pos) < 1 : blockState.is(Blocks.FURNACE) && blockState.getValue(FurnaceBlock.LIT) @@ -459,22 +386,22 @@ index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..057090e3134048e75dbaefb703e8f2d3 } } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.CatSit; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } diff --git a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java -index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..b5571b7deb1645d88493c9fae866fd3726666754 100644 +index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..dc2f0105b1f368e01bbbfc7c68a58e0012dd8736 100644 --- a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java @@ -23,8 +23,47 @@ public class FollowBoatGoal extends Goal { this.mob = mob; } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + private boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof Player target)) return false; + var serverLevel = getServerLevel(this.mob); @@ -500,10 +427,10 @@ index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..b5571b7deb1645d88493c9fae866fd37 + return null; + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding @Override public boolean canUse() { -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -514,7 +441,7 @@ index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..b5571b7deb1645d88493c9fae866fd37 + findTargetAsync(); + return false; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding List entitiesOfClass = this.mob.level().getEntitiesOfClass(AbstractBoat.class, this.mob.getBoundingBox().inflate(5.0)); boolean flag = false; @@ -523,19 +450,19 @@ index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..b5571b7deb1645d88493c9fae866fd37 } - return this.following != null && (Mth.abs(this.following.xxa) > 0.0F || Mth.abs(this.following.zza) > 0.0F) || flag; -+ return flag; // Leaf - Async Target Finding - move above ++ return flag; // Leaf - Async target finding - move above } @Override diff --git a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java -index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..c2baf746a0697559dc391b6f8c9303917e194836 100644 +index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..66f69163ed8d36dcd0dec5603e72a88e8812d3e5 100644 --- a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java @@ -38,6 +38,15 @@ public class FollowMobGoal extends Goal { @Override public boolean canUse() { -+ // Leaf start - Async Follow Mob Finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -543,7 +470,7 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..c2baf746a0697559dc391b6f8c930391 + getFollowingMobAsync(); + return false; + } -+ // Leaf end - Async Follow Mob Finding ++ // Leaf end - Async target finding List entitiesOfClass = this.mob.level().getEntitiesOfClass(Mob.class, this.mob.getBoundingBox().inflate(this.areaSize), this.followPredicate); if (!entitiesOfClass.isEmpty()) { for (Mob mob : entitiesOfClass) { @@ -551,7 +478,7 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..c2baf746a0697559dc391b6f8c930391 return false; } -+ // Leaf start - Async Follow Mob Finding ++ // Leaf start - Async target finding + private boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof Mob target)) return false; + var serverLevel = getServerLevel(this.mob); @@ -579,20 +506,20 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..c2baf746a0697559dc391b6f8c930391 + return null; + }; + } -+ // Leaf end - Async Follow Mob Finding ++ // Leaf end - Async target finding + @Override public boolean canContinueToUse() { return this.followingMob != null && !this.navigation.isDone() && this.mob.distanceToSqr(this.followingMob) > this.stopDistance * this.stopDistance; diff --git a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270169330ef 100644 +index 3093f03d4f298bf39fec8bad2b6c22518774aea8..4eec8eda6ea1698529e0392bc75c07be5980f5fc 100644 --- a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java @@ -19,11 +19,56 @@ public class FollowParentGoal extends Goal { this.speedModifier = speedModifier; } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.animal.getGoalCtx().result() instanceof Animal target)) return false; + var serverLevel = getServerLevel(animal); @@ -626,14 +553,14 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270 + return target; + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public boolean canUse() { if (this.animal.getAge() >= 0) { return false; } else { -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -641,7 +568,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270 + findTargetAsync(); + return false; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding List entitiesOfClass = this.animal .level() .getEntitiesOfClass((Class)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0)); @@ -649,12 +576,12 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270 if (animal == null) { return false; } else if (d < 9.0) { -+ // Leaf - Async Target Finding - diff on change ++ // Leaf - Async target finding - diff on change return false; } else { this.parent = animal; diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b9f8b0cc0 100644 +index 55f1c138039b80894f655d180192f5cb95e32778..1afc0a13ff7f64e453b272c1d94b7e4c80cc22dd 100644 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -25,12 +25,22 @@ public class GoalSelector { @@ -702,13 +629,13 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); // Paper end - Perf: optimize goal types if (!flag.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) { -@@ -83,7 +94,131 @@ public class GoalSelector { +@@ -83,7 +94,136 @@ public class GoalSelector { return true; } + // Leaf start - Async target finding + public final boolean poll() { -+ if (this.ctxGoals == null || ctx.wake != null) { ++ if (ctxState == -1 || this.ctxGoals == null || ctx.wake != null || ctxGoals.length == 0) { + return false; + } + if (ctxState == 0) { @@ -727,7 +654,12 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b + ctx.state = true; + } + -+ this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); ++ for (Goal.Flag flag : GOAL_FLAG_VALUES) { ++ var goal = this.lockedFlags.get(flag); ++ if (goal != null && !goal.isRunning()) { ++ this.lockedFlags.remove(flag); ++ } ++ } + + ctxIndex = 0; + ctx.state = true; @@ -775,7 +707,7 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b + if (!ctx.state) { + switch (goal.getGoal()) { + case net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal t -> t.poll(); -+ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); ++ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); + default -> {} + } + } @@ -820,12 +752,12 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b public void tick() { + // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ if (ctxState == -1) { -+ if (availableGoalsDirty || this.ctxGoals == null) { ++ if (this.ctxState == -1) { ++ if (this.availableGoalsDirty || this.ctxGoals == null) { + this.ctxGoals = this.availableGoals.toArray(new WrappedGoal[0]); -+ availableGoalsDirty = false; ++ this.availableGoalsDirty = false; + } -+ ctxState = 0; ++ this.ctxState = 0; + } + return; + } @@ -834,7 +766,7 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); -@@ -114,6 +249,18 @@ public class GoalSelector { +@@ -114,6 +254,24 @@ public class GoalSelector { } public void tickRunningGoals(boolean tickAllRunning) { @@ -846,6 +778,12 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b + availableGoalsDirty = false; + } + ctxState = tickAllRunning ? 2 : 3; ++ } else { ++ for (WrappedGoal wrappedGoal : java.util.Objects.requireNonNull(this.ctxGoals)) { ++ if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { ++ wrappedGoal.tick(); ++ } ++ } + } + return; + } @@ -854,14 +792,14 @@ index 55f1c138039b80894f655d180192f5cb95e32778..2046bc2b69e0e41f31b794d0bda7df4b if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { wrappedGoal.tick(); diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0bff40c42 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..83a0e5ffad44a628b60a6a2cc136559b0a0377f3 100644 --- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -20,20 +20,83 @@ public class LlamaFollowCaravanGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + private @javax.annotation.Nullable Llama poll() { + if (!(this.llama.getGoalCtx().result() instanceof Llama target)) return null; + var serverLevel = getServerLevel(this.llama); @@ -910,16 +848,16 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0 + return target; + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public boolean canUse() { if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur - Llama API // Purpur - Config to disable Llama caravans if (!this.llama.isLeashed() && !this.llama.inCaravan()) { -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + Llama llama = poll(); + double d = Double.MAX_VALUE; -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (llama == null) { + findTargetAsync(); + return false; @@ -927,20 +865,20 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0 + d = this.llama.distanceToSqr(llama); + } + } else { -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding List entities = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity1 -> { EntityType type = entity1.getType(); return type == EntityType.LLAMA || type == EntityType.TRADER_LLAMA; }); - Llama llama = null; - double d = Double.MAX_VALUE; -+ // Llama llama = null; // Leaf - Async Target Finding -+ // double d = Double.MAX_VALUE; // Leaf - Async Target Finding ++ // Llama llama = null; // Leaf - Async target finding ++ // double d = Double.MAX_VALUE; // Leaf - Async target finding for (Entity entity : entities) { Llama llama1 = (Llama)entity; - if (llama1.inCaravan() && !llama1.hasCaravanTail()) { -+ if (llama1.inCaravan() && !llama1.hasCaravanTail()) { // Leaf - Async Target Finding - diff on change ++ if (llama1.inCaravan() && !llama1.hasCaravanTail()) { // Leaf - Async target finding - diff on change double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { d = d1; @@ -949,7 +887,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0 for (Entity entityx : entities) { Llama llama1 = (Llama)entityx; - if (llama1.isLeashed() && !llama1.hasCaravanTail()) { -+ if (llama1.isLeashed() && !llama1.hasCaravanTail()) { // Leaf - Async Target Finding - diff on change ++ if (llama1.isLeashed() && !llama1.hasCaravanTail()) { // Leaf - Async target finding - diff on change double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { d = d1; @@ -957,19 +895,19 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0 } } } -+ } // Leaf - Async Target Finding ++ } // Leaf - Async target finding if (llama == null) { return false; diff --git a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -index 6463c3c9b08d6058f2843c225b08a40fc30a960b..98c2b4a298ada4b02afa55f991791d8696702181 100644 +index 6463c3c9b08d6058f2843c225b08a40fc30a960b..126bd98bc5980a2f1177bd7c74918b86a1f5bdce 100644 --- a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +++ b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java @@ -48,32 +48,79 @@ public class LookAtPlayerGoal extends Goal { @Override public boolean canUse() { -+ // Leaf start - Async look finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -1059,23 +997,23 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..98c2b4a298ada4b02afa55f991791d86 - } + }; } -+ // Leaf end - Async look finding ++ // Leaf end - Async target finding @Override public boolean canContinueToUse() { diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0b3c29eb3 100644 +index f15da598cb1d7872fafb8b173e5134b9667c9a9f..13b7129774a098c66990266e03877302f5cea0fe 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -41,8 +41,60 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -41,14 +41,73 @@ public abstract class MoveToBlockGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof BlockPos blockPos1)) return false; + if (!this.mob.level().hasChunkAt(blockPos1) -+ || !this.mob.isWithinHome(blockPos1) ++ || !this.mob.isWithinRestriction(blockPos1) + || !this.isValidTarget(this.mob.level(), blockPos1)) { + return false; + } @@ -1084,10 +1022,15 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + return true; + } + -+ protected void getBlockAsync() { ++ protected boolean findNearestBlockAsync() { ++ // Leaf start - Async target finding ++ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock) { ++ return findNearestBlock(); ++ } ++ // Leaf end - Async target finding + final var mob = this.mob; + final var ctx = mob.getGoalCtx(); -+ if (!ctx.state) return; ++ if (!ctx.state) return false; + final var serverLevel = getServerLevel(mob); + final TypeToCheck ty = this.typeToCheck(); + final net.minecraft.world.level.block.Block toRemove; @@ -1100,9 +1043,10 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + final int searchRange = this.searchRange; + final int verticalSearchRange = this.verticalSearchRange; + final BlockPos blockPos = mob.blockPosition(); -+ final float homeRadius = mob.getHomeRadius(); -+ final BlockPos homePosition = mob.getHomePosition(); -+ ctx.wake = () -> findNearestBlockAsync(ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, homeRadius, homePosition); ++ final float restrictRadius = mob.getRestrictRadius(); ++ final BlockPos restrictCenter = mob.getRestrictCenter(); ++ ctx.wake = () -> findNearestBlockAsync(ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); ++ return false; + } + + protected enum TypeToCheck { @@ -1115,37 +1059,32 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + Strider, + TurtleToWater, + TurtleLay, ++ Unknown, + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding + @Override public boolean canUse() { -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + if (poll()) { + return true; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding if (this.nextStartTick > 0) { this.nextStartTick--; return false; -@@ -109,6 +161,12 @@ public abstract class MoveToBlockGoal extends Goal { + } else { + this.nextStartTick = this.nextStartTick(this.mob); +- return this.findNearestBlock(); ++ return this.findNearestBlockAsync(); // Leaf - Async target finding + } } - protected boolean findNearestBlock() { -+ // Leaf start - Async search block -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock) { -+ getBlockAsync(); -+ return false; -+ } -+ // Leaf end - Async search block - int i = this.searchRange; - int i1 = this.verticalSearchRange; - BlockPos blockPos = this.mob.blockPosition(); -@@ -133,5 +191,105 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -133,5 +192,108 @@ public abstract class MoveToBlockGoal extends Goal { return false; } -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + protected static @javax.annotation.Nullable BlockPos findNearestBlockAsync( + final TypeToCheck ty, + @org.jetbrains.annotations.Nullable final net.minecraft.world.level.block.Block toRemove, @@ -1155,8 +1094,8 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + final int searchRange, + final int verticalSearchRange, + final BlockPos blockPos, -+ final float homeRadius, -+ final BlockPos homePosition ++ final float restrictRadius, ++ final BlockPos restrictCenter + ) { + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + for (int i2 = verticalSearchStart; i2 <= verticalSearchRange; i2 = i2 > 0 ? -i2 : 1 - i2) { @@ -1165,7 +1104,7 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { + mutableBlockPos.setWithOffset(blockPos, i4, i2 - 1, i5); + if (!serverLevel.hasChunkAt(mutableBlockPos)) continue; -+ if (isWithinHome(homeRadius, homePosition, mutableBlockPos) ++ if (isWithinRestriction(restrictRadius, restrictCenter, mutableBlockPos) + && isValidTargetAsync(ty, toRemove, mob, serverLevel, mutableBlockPos)) { + return mutableBlockPos.immutable(); + } @@ -1176,15 +1115,17 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + return null; + } + -+ private static boolean isWithinHome(float homeRadius, BlockPos homePosition, BlockPos pos) { -+ return homeRadius == -1.0F || homePosition.distSqr(pos) < homeRadius * homeRadius; ++ private static boolean isWithinRestriction(float restrictRadius, BlockPos restrictCenter, BlockPos pos) { ++ return restrictRadius == -1.0F || restrictCenter.distSqr(pos) < restrictRadius * restrictRadius; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding + protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); + -+ // Leaf start - Async search block -+ protected abstract TypeToCheck typeToCheck(); ++ // Leaf start - Async target finding ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.Unknown; ++ } + + private static boolean isValidTargetAsync( + TypeToCheck type, @@ -1242,20 +1183,21 @@ index f15da598cb1d7872fafb8b173e5134b9667c9a9f..7a870c4efcb1022790f9baf1bac0f3c0 + case TurtleLay -> { + return level.isEmptyBlock(pos.above()) && net.minecraft.world.level.block.TurtleEggBlock.isSand(level, pos); + } ++ case Unknown -> throw new IllegalStateException(); + case null -> throw new IllegalStateException(); + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding + } } diff --git a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java -index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..0f2841a9681997c0d4b915284f7f5b385b1e172c 100644 +index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..d0e3e030bc5eacc304c523afcabd193ea86080e6 100644 --- a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java +++ b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java @@ -19,10 +19,20 @@ public class OfferFlowerGoal extends Goal { @Override public boolean canUse() { -+ // Leaf start - Async offer flower finding ++ // Leaf start - Async target finding if (!this.golem.level().isBrightOutside()) { return false; - } else if (this.golem.getRandom().nextInt(8000) != 0) { @@ -1269,7 +1211,7 @@ index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..0f2841a9681997c0d4b915284f7f5b38 + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getVillagerAsync(); + return false; -+ // Leaf end - Async offer flower finding ++ // Leaf end - Async target finding } else { this.villager = getServerLevel(this.golem) .getNearestEntity( @@ -1278,7 +1220,7 @@ index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..0f2841a9681997c0d4b915284f7f5b38 } + -+ // Leaf start - Async look finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.golem.getGoalCtx().result() instanceof Villager target)) return false; + var serverLevel = getServerLevel(this.golem); @@ -1305,57 +1247,61 @@ index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..0f2841a9681997c0d4b915284f7f5b38 + z + ); + } -+ // Leaf end - Async look finding ++ // Leaf end - Async target finding + @Override public boolean canContinueToUse() { return this.tick > 0; diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -index 16ec032d84f128fc44a836843fafef303f52b699..767dff2aa4c1d71cc3c22104366605486a5004b9 100644 +index 16ec032d84f128fc44a836843fafef303f52b699..e3bb4c5850e25405a243aaf57e2e8b1c64381d1d 100644 --- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -@@ -37,7 +37,14 @@ public class RemoveBlockGoal extends MoveToBlockGoal { +@@ -37,10 +37,17 @@ public class RemoveBlockGoal extends MoveToBlockGoal { public boolean canUse() { if (!getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING, getServerLevel(this.removerMob).purpurConfig.zombieMobGriefingOverride)) { // Purpur - Add mobGriefing override to everything affected return false; - } else if (this.nextStartTick > 0) { + } -+ // Leaf start - async search block ++ // Leaf start - Async target finding + if (poll()) { + this.nextStartTick = reducedTickDelay(20); + return true; + } -+ // Leaf end - async search block ++ // Leaf end - Async target finding + if (this.nextStartTick > 0) { this.nextStartTick--; return false; - } else if (this.findNearestBlock()) { +- } else if (this.findNearestBlock()) { ++ } else if (this.findNearestBlockAsync()) { // Leaf - Async target finding + this.nextStartTick = reducedTickDelay(20); + return true; + } else { @@ -151,8 +158,15 @@ public class RemoveBlockGoal extends MoveToBlockGoal { protected boolean isValidTarget(LevelReader level, BlockPos pos) { ChunkAccess chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks return chunk != null - && chunk.getBlockState(pos).is(this.blockToRemove) -+ && chunk.getBlockState(pos).is(this.blockToRemove) // Leaf - Async search block - diff on change ++ && chunk.getBlockState(pos).is(this.blockToRemove) // Leaf - Async target finding - diff on change && chunk.getBlockState(pos.above()).isAir() && chunk.getBlockState(pos.above(2)).isAir(); } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.RemoveBlock; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index a805c9426630c2c46db9d0dd536f1d16769395d3..8842e87b652635db72bac07a9c4883f79c48e700 100644 +index a805c9426630c2c46db9d0dd536f1d16769395d3..66445cf3aa824a1294959eb3ae41a1807bb18f42 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -49,12 +49,51 @@ public class TemptGoal extends Goal { +@@ -49,14 +49,43 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } -+ // Leaf start - Async Tempt Finding ++ // Leaf start - Async target finding + private boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof Player target)) return false; + var serverLevel = getServerLevel(this.mob); @@ -1374,58 +1320,42 @@ index a805c9426630c2c46db9d0dd536f1d16769395d3..8842e87b652635db72bac07a9c4883f7 + .copy(); + ctx.wake = () -> serverLevel.getNearestPlayer(conditions, mob); + } -+ // Leaf end - Async Tempt Finding ++ // Leaf end - Async target finding @Override public boolean canUse() { if (this.calmDown > 0) { this.calmDown--; return false; } else { -+ // Leaf start - Async Tempt Finding +- this.player = getServerLevel(this.mob) +- .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ if (poll()) { -+ if (this.player != null) { -+ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); -+ if (event.isCancelled()) { -+ return false; -+ } -+ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); -+ } -+ if (this.player != null) { -+ return true; -+ } -+ } else { ++ if (!poll()) { + getNearestPlayerAsync(); + return false; + } ++ } else { ++ this.player = getServerLevel(this.mob) ++ .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + } -+ // Leaf end - Async Tempt Finding - this.player = getServerLevel(this.mob) - .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ // Leaf end - Async target finding // CraftBukkit start + if (this.player != null) { + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java -index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..6e752caa969891fa7f44b69ab58fca5434bb1aa9 100644 +index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..96ccafe979c5c15e26c12f6ca98ad1e81537bdce 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java -@@ -16,7 +16,7 @@ public class DefendVillageTargetGoal extends TargetGoal { - private final IronGolem golem; - @Nullable - private LivingEntity potentialTarget; -- private final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); -+ private static final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); // Leaf - Async Target Finding - static - - public DefendVillageTargetGoal(IronGolem golem) { - super(golem, false, true); -@@ -24,8 +24,49 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -24,13 +24,52 @@ public class DefendVillageTargetGoal extends TargetGoal { this.setFlags(EnumSet.of(Goal.Flag.TARGET)); } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + ServerLevel serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !target.isAlive() || !attackTargeting.test(serverLevel, golem, target)) return false; -+ if ((target instanceof Player player && (player.isSpectator() || player.isCreative()))) return false; + this.potentialTarget = target; + return true; + } @@ -1450,33 +1380,37 @@ index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..6e752caa969891fa7f44b69ab58fca54 + return null; + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public boolean canUse() { + // Leaf start - Async target finding -+ if (poll()) { -+ return true; -+ } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ this.findTargetAsync(); -+ return false; -+ } -+ // Leaf end - Async target finding ++ if (!poll()) { ++ this.findTargetAsync(); ++ return false; ++ } ++ } else { AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); ServerLevel serverLevel = getServerLevel(this.golem); List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); -@@ -42,7 +83,7 @@ public class DefendVillageTargetGoal extends TargetGoal { + List nearbyPlayers = serverLevel.getNearbyPlayers(this.attackTargeting, this.golem, aabb); + ++ // Async target finding - diff + for (LivingEntity livingEntity : nearbyEntities) { + Villager villager = (Villager)livingEntity; + +@@ -41,6 +80,8 @@ public class DefendVillageTargetGoal extends TargetGoal { + } } } ++ } ++ // Leaf end - Async target finding -- return this.potentialTarget != null && !(this.potentialTarget instanceof Player player1 && (player1.isSpectator() || player1.isCreative())); -+ return this.potentialTarget != null && !(this.potentialTarget instanceof Player player1 && (player1.isSpectator() || player1.isCreative())); // Leaf - Async target finding - diff on change + return this.potentialTarget != null && !(this.potentialTarget instanceof Player player1 && (player1.isSpectator() || player1.isCreative())); } - - @Override diff --git a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java -index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1ec99988b7 100644 +index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..412fc8e049ba3763314ec2a56dce378cb0e4cc5f 100644 --- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java @@ -73,6 +73,46 @@ public class HurtByTargetGoal extends TargetGoal { @@ -1484,7 +1418,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1e double followDistance = this.getFollowDistance(); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followDistance, 10.0, followDistance); + -+ // Leaf start - Async alert other ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var self = this.mob; + final var ctx = self.getGoalCtx(); @@ -1521,7 +1455,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1e + }; + return; + } -+ // Leaf end - Async alert other ++ // Leaf end - Async target finding + List entitiesOfClass = this.mob .level() @@ -1531,7 +1465,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1e mob = (Mob)var5.next(); if (this.mob != mob - && mob.getTarget() == null -+ && mob.getTarget() == null // Leaf - Async alert other - diff on change ++ && mob.getTarget() == null // Leaf - Async target finding - diff on change && (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner()) && !mob.isAlliedTo(this.mob.getLastHurtByMob())) { if (this.toIgnoreAlert == null) { @@ -1540,7 +1474,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1e boolean flag = false; - for (Class clazz : this.toIgnoreAlert) { -+ for (Class clazz : this.toIgnoreAlert) { // Leaf - Async alert other - diff on change ++ for (Class clazz : this.toIgnoreAlert) { // Leaf - Async target finding - diff on change if (mob.getClass() == clazz) { flag = true; break; @@ -1548,7 +1482,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1e } } -+ // Leaf start - Async alert other ++ // Leaf start - Async target finding + public void poll() { + if (!(this.mob.getGoalCtx().result() instanceof List toAlert)) return; + LivingEntity lastHurtByMob = this.mob.getLastHurtByMob(); @@ -1576,20 +1510,20 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1e + } + } + } -+ // Leaf end - Async alert other ++ // Leaf end - Async target finding + protected void alertOther(Mob mob, LivingEntity target) { mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY); // CraftBukkit - reason } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..211178240d9f58504ff822e459f3e1139ffda32d 100644 +index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..d44aec876818421eb8a54007e2bdabd7d354f22b 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -41,8 +41,43 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -41,12 +41,52 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + ServerLevel serverLevel = getServerLevel(this.mob); @@ -1598,7 +1532,12 @@ index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..211178240d9f58504ff822e459f3e113 + return true; + } + -+ private void findTargetAsync() { ++ protected void findTargetAsync() { ++ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ findTarget(); ++ return; ++ } ++ this.target = null; + final Mob mob = this.mob; + final var ctx = mob.getGoalCtx(); + if (!ctx.state) return; @@ -1617,7 +1556,7 @@ index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..211178240d9f58504ff822e459f3e113 + } + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public boolean canUse() { @@ -1629,27 +1568,24 @@ index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..211178240d9f58504ff822e459f3e113 if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -@@ -57,6 +92,15 @@ public class NearestAttackableTargetGoal extends TargetG +- this.findTarget(); ++ this.findTargetAsync(); // Async target finding + return this.target != null; + } + } +@@ -57,6 +97,7 @@ public class NearestAttackableTargetGoal extends TargetG protected void findTarget() { ServerLevel serverLevel = getServerLevel(this.mob); -+ -+ // Leaf start - Async Target Finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ this.findTargetAsync(); -+ this.target = null; -+ return; -+ } -+ // Leaf end - Async Target Finding + if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { this.target = serverLevel.getNearestEntity( this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73460593e5 100644 +index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..84c7b89e7c894c0f544cf0ffcf9dff3f6a5919cc 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -@@ -23,7 +23,15 @@ public class NearestHealableRaiderTargetGoal extends Nea +@@ -23,12 +23,20 @@ public class NearestHealableRaiderTargetGoal extends Nea @Override public boolean canUse() { @@ -1666,6 +1602,12 @@ index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73 return false; } else if (!((Raider)this.mob).hasActiveRaid()) { return false; + } else { +- this.findTarget(); ++ this.findTargetAsync(); // Leaf - Async target finding + return this.target != null; + } + } diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java index abf57494950f55bbd75f335f26736cb9e703c197..efd2418f56c36e7850edde483a2a4906dd622441 100644 --- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java @@ -1688,14 +1630,14 @@ index abf57494950f55bbd75f335f26736cb9e703c197..efd2418f56c36e7850edde483a2a4906 } } diff --git a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java -index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..270f0b8b33aed1c54edbdb8595ce7fcc7fca2dc4 100644 +index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..08a09dea0dd377adca22b4432cf8f57c3122ec02 100644 --- a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java @@ -37,6 +37,27 @@ public class ResetUniversalAngerTargetGoal extends G this.lastHurtByPlayerTimestamp = this.mob.getLastHurtByMobTimestamp(); this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); if (this.alertOthersOfSameType) { -+ // Leaf start - Async alert other ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var mob = this.mob; + final var ctx = mob.getGoalCtx(); @@ -1715,7 +1657,7 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..270f0b8b33aed1c54edbdb8595ce7fcc + }; + return; + } -+ // Leaf end - Async alert other ++ // Leaf end - Async target finding this.getNearbyMobsOfSameType() .stream() .filter(mob -> mob != this.mob) @@ -1723,7 +1665,7 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..270f0b8b33aed1c54edbdb8595ce7fcc super.start(); } -+ // Leaf start - Async alert other ++ // Leaf start - Async target finding + public void poll() { + if (!(this.mob.getGoalCtx().result() instanceof List toStop)) return; + for (var neutralMob : toStop) { @@ -1732,10 +1674,10 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..270f0b8b33aed1c54edbdb8595ce7fcc + } + } + } -+ // Leaf end - Async alert other ++ // Leaf end - Async target finding + private List getNearbyMobsOfSameType() { -+ // Leaf - Async alert other - diff on change ++ // Leaf - Async target finding - diff on change double attributeValue = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(attributeValue, 10.0, attributeValue); return this.mob.level().getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); @@ -1780,7 +1722,7 @@ index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..6fa0b8defbd1d06b3bf5d9b32ffd08f3 // Gale start - Petal - reduce line of sight cache lookups - merge sets int cached = this.seen.get(id); diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java -index 8964bb5098c0dc36741af3656af6bc0b5b463abe..ac35459445061f50302f70044e81a5967e03df5f 100644 +index 8964bb5098c0dc36741af3656af6bc0b5b463abe..a22ccaab0f4d6e3a69080b56f8042010403378cf 100644 --- a/net/minecraft/world/entity/animal/Fox.java +++ b/net/minecraft/world/entity/animal/Fox.java @@ -867,6 +867,11 @@ public class Fox extends Animal { @@ -1809,7 +1751,7 @@ index 8964bb5098c0dc36741af3656af6bc0b5b463abe..ac35459445061f50302f70044e81a596 protected boolean isValidTarget(LevelReader level, BlockPos pos) { BlockState blockState = level.getBlockState(pos); - return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); -+ return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); // Leaf - Async search block - diff on change ++ return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); // Leaf - Async target finding - diff on change } @Override @@ -1818,24 +1760,24 @@ index 8964bb5098c0dc36741af3656af6bc0b5b463abe..ac35459445061f50302f70044e81a596 super.start(); } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.FoxEat; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } class FoxFloatGoal extends FloatGoal { diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java -index 02bfa88568e635770675ea9173f2cf3ca21457fa..c7dc46c1584345463100264dc88bddb7d883575e 100644 +index 02bfa88568e635770675ea9173f2cf3ca21457fa..8b37cbae45916227b99c7dae4da5f0c0e3144619 100644 --- a/net/minecraft/world/entity/animal/Panda.java +++ b/net/minecraft/world/entity/animal/Panda.java @@ -989,9 +989,18 @@ public class Panda extends Animal { @Override public boolean canUse() { -+ // Leaf start - Async look finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -1846,12 +1788,12 @@ index 02bfa88568e635770675ea9173f2cf3ca21457fa..c7dc46c1584345463100264dc88bddb7 + getLookAsync(); + return false; + } -+ // Leaf end - Async look finding ++ // Leaf end - Async target finding if (this.lookAt == null) { ServerLevel serverLevel = getServerLevel(this.mob); if (this.lookAtType == Player.class) { diff --git a/net/minecraft/world/entity/animal/Rabbit.java b/net/minecraft/world/entity/animal/Rabbit.java -index 1af1b33702296f9aa74c33436ea2904c5e5ae43d..ad9a1baa24a7a4ac22bb741f743e5b32514e318a 100644 +index 1af1b33702296f9aa74c33436ea2904c5e5ae43d..da19a60713ef159b86f98e1e718a1776493cdec6 100644 --- a/net/minecraft/world/entity/animal/Rabbit.java +++ b/net/minecraft/world/entity/animal/Rabbit.java @@ -670,7 +670,13 @@ public class Rabbit extends Animal { @@ -1859,13 +1801,13 @@ index 1af1b33702296f9aa74c33436ea2904c5e5ae43d..ad9a1baa24a7a4ac22bb741f743e5b32 } - return super.canUse(); -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + if (this.wantsToRaid && !this.canRaid) { + return super.canUse(); + } else { + return false; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } @Override @@ -1874,7 +1816,7 @@ index 1af1b33702296f9aa74c33436ea2904c5e5ae43d..ad9a1baa24a7a4ac22bb741f743e5b32 protected boolean isValidTarget(LevelReader level, BlockPos pos) { BlockState blockState = level.getBlockState(pos); - if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { -+ if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { // Leaf - Async search block - diff on change ++ if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { // Leaf - Async target finding - diff on change blockState = level.getBlockState(pos.above()); if (blockState.getBlock() instanceof CarrotBlock && ((CarrotBlock)blockState.getBlock()).isMaxAge(blockState)) { this.canRaid = true; @@ -1883,17 +1825,17 @@ index 1af1b33702296f9aa74c33436ea2904c5e5ae43d..ad9a1baa24a7a4ac22bb741f743e5b32 return false; } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.RaidGarden; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } public static enum Variant implements StringRepresentable { diff --git a/net/minecraft/world/entity/animal/Turtle.java b/net/minecraft/world/entity/animal/Turtle.java -index 9bdc1f10e3b40672449ad166f130ecabdacc75d3..49e54711a5a57541cb603fa0bf9f5acd739453a6 100644 +index 9bdc1f10e3b40672449ad166f130ecabdacc75d3..98131b65eaaa5e20b8f4e8d022141140b70521ca 100644 --- a/net/minecraft/world/entity/animal/Turtle.java +++ b/net/minecraft/world/entity/animal/Turtle.java @@ -483,8 +483,15 @@ public class Turtle extends Animal { @@ -1901,15 +1843,15 @@ index 9bdc1f10e3b40672449ad166f130ecabdacc75d3..49e54711a5a57541cb603fa0bf9f5acd @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { - return level.getBlockState(pos).is(Blocks.WATER); -+ return level.getBlockState(pos).is(Blocks.WATER); // Leaf - Async search block - diff on change ++ return level.getBlockState(pos).is(Blocks.WATER); // Leaf - Async target finding - diff on change } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.TurtleToWater; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } static class TurtleLayEggGoal extends MoveToBlockGoal { @@ -1918,20 +1860,20 @@ index 9bdc1f10e3b40672449ad166f130ecabdacc75d3..49e54711a5a57541cb603fa0bf9f5acd @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { - return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); -+ return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); // Leaf - Async search block - diff on change ++ return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); // Leaf - Async target finding - diff on change + } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.TurtleLay; } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables diff --git a/net/minecraft/world/entity/animal/wolf/Wolf.java b/net/minecraft/world/entity/animal/wolf/Wolf.java -index 7e7cb9db1c84bdb173b444bec90663a93fb3b549..e64713d454b6b00827c609b36371d841adc30df0 100644 +index 7e7cb9db1c84bdb173b444bec90663a93fb3b549..96016902084a94a98c850579ec7714264a23b781 100644 --- a/net/minecraft/world/entity/animal/wolf/Wolf.java +++ b/net/minecraft/world/entity/animal/wolf/Wolf.java @@ -710,7 +710,7 @@ public class Wolf extends TamableAnimal implements NeutralMob { @@ -1939,12 +1881,12 @@ index 7e7cb9db1c84bdb173b444bec90663a93fb3b549..e64713d454b6b00827c609b36371d841 @Override public boolean isFood(ItemStack stack) { - return stack.is(ItemTags.WOLF_FOOD); -+ return stack.is(ItemTags.WOLF_FOOD); // Leaf - Async Target Finding - diff on change ++ return stack.is(ItemTags.WOLF_FOOD); // Leaf - Async target finding - diff on change } @Override diff --git a/net/minecraft/world/entity/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java -index 7ff380212ce5e56e0e58e5f52f8c75bda5061ef0..11b10d802b7a63cc294c66c70c9a99b48b601632 100644 +index 7ff380212ce5e56e0e58e5f52f8c75bda5061ef0..c21519490433dfb2da3435afe757df01747c98e5 100644 --- a/net/minecraft/world/entity/monster/Drowned.java +++ b/net/minecraft/world/entity/monster/Drowned.java @@ -392,7 +392,7 @@ public class Drowned extends Zombie implements RangedAttackMob { @@ -1952,7 +1894,7 @@ index 7ff380212ce5e56e0e58e5f52f8c75bda5061ef0..11b10d802b7a63cc294c66c70c9a99b4 protected boolean isValidTarget(LevelReader level, BlockPos pos) { BlockPos blockPos = pos.above(); - return level.isEmptyBlock(blockPos) && level.isEmptyBlock(blockPos.above()) && level.getBlockState(pos).entityCanStandOn(level, pos, this.drowned); -+ return level.isEmptyBlock(blockPos) && level.isEmptyBlock(blockPos.above()) && level.getBlockState(pos).entityCanStandOn(level, pos, this.drowned); // Leaf - Async search block - diff on change ++ return level.isEmptyBlock(blockPos) && level.isEmptyBlock(blockPos.above()) && level.getBlockState(pos).entityCanStandOn(level, pos, this.drowned); // Leaf - Async target finding - diff on change } @Override @@ -1961,24 +1903,24 @@ index 7ff380212ce5e56e0e58e5f52f8c75bda5061ef0..11b10d802b7a63cc294c66c70c9a99b4 super.stop(); } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.Drowned; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } static class DrownedGoToWaterGoal extends Goal { diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java -index 58887b2cc931892f96793edd7a7d1db22cb8686c..769f3a350ebe0133a2b45e13fe9cf9456d5d9748 100644 +index 58887b2cc931892f96793edd7a7d1db22cb8686c..573986e139321cd8389440ac2a9a50f806b4ed8c 100644 --- a/net/minecraft/world/entity/monster/EnderMan.java +++ b/net/minecraft/world/entity/monster/EnderMan.java @@ -588,10 +588,34 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (poll()) { + return true; @@ -1991,12 +1933,12 @@ index 58887b2cc931892f96793edd7a7d1db22cb8686c..769f3a350ebe0133a2b45e13fe9cf945 + ctx.wake = () -> level.getNearestPlayer(cond, enderman); + return false; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding this.pendingTarget = getServerLevel(this.enderman).getNearestPlayer(this.startAggroTargetConditions.range(this.getFollowDistance()), this.enderman); return this.pendingTarget != null; } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof Player player)) return false; + var serverLevel = getServerLevel(this.enderman); @@ -2004,13 +1946,13 @@ index 58887b2cc931892f96793edd7a7d1db22cb8686c..769f3a350ebe0133a2b45e13fe9cf945 + this.pendingTarget = player; + return true; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public void start() { this.aggroTime = this.adjustedTickDelay(5); diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java -index e1717b5c854aa81fdd7b7e715d7c3498d9f86072..3627e7479b4deea28e268245410ec4cd48f24e9e 100644 +index e1717b5c854aa81fdd7b7e715d7c3498d9f86072..727effd31644432f9da04ee4e3aaa41ce45d6a2e 100644 --- a/net/minecraft/world/entity/monster/Strider.java +++ b/net/minecraft/world/entity/monster/Strider.java @@ -551,8 +551,15 @@ public class Strider extends Animal implements ItemSteerable { @@ -2018,31 +1960,23 @@ index e1717b5c854aa81fdd7b7e715d7c3498d9f86072..3627e7479b4deea28e268245410ec4cd @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { - return level.getBlockState(pos).is(Blocks.LAVA) && level.getBlockState(pos.above()).isPathfindable(PathComputationType.LAND); -+ return level.getBlockState(pos).is(Blocks.LAVA) && level.getBlockState(pos.above()).isPathfindable(PathComputationType.LAND); // Leaf - Async search block - diff on change ++ return level.getBlockState(pos).is(Blocks.LAVA) && level.getBlockState(pos.above()).isPathfindable(PathComputationType.LAND); // Leaf - Async target finding - diff on change } + -+ // Leaf start - Async search block ++ // Leaf start - Async target finding + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.Strider; + } -+ // Leaf end - Async search block ++ // Leaf end - Async target finding } static class StriderPathNavigation extends GroundPathNavigation { diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 22afb73cd31c15ed7c68c403f044910d93889d99..f407daf04f2e498dfc058a7bc8b062f80cdccdc7 100644 +index 22afb73cd31c15ed7c68c403f044910d93889d99..44b9fb5fbbce0fab5b09425078c31058b8576579 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java -@@ -1532,6 +1532,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl - } - // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - do not bother with condition work / make configurable - // Paper end - rewrite chunk system -+ ((ServerLevel) this).leafMidTickTasks(); // Leaf - Async target finding - } - } - this.blockEntityTickers.removeMarkedEntries(); // SparklyPaper - optimize block entity removals -@@ -1793,9 +1794,11 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1793,9 +1793,11 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl @Override public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { @@ -2050,9 +1984,9 @@ index 22afb73cd31c15ed7c68c403f044910d93889d99..f407daf04f2e498dfc058a7bc8b062f8 - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) - List list = Lists.newArrayList(); + // Leaf start - Async target finding -+ //if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ // ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) -+ //List list = Lists.newArrayList(); // Leaf - Async target finding - unused ++ // if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ // List list = Lists.newArrayList(); // Leaf - Async target finding - unused + // Leaf end - Async target finding // Paper start - rewrite chunk system diff --git a/leaf-server/minecraft-patches/features/0275-Fix-crash-during-parsing-unknown-command-message.patch b/leaf-server/minecraft-patches/features/0274-Fix-crash-during-parsing-unknown-command-message.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0275-Fix-crash-during-parsing-unknown-command-message.patch rename to leaf-server/minecraft-patches/features/0274-Fix-crash-during-parsing-unknown-command-message.patch diff --git a/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch new file mode 100644 index 00000000..1b14456a --- /dev/null +++ b/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 6 Jun 2025 20:46:10 +0900 +Subject: [PATCH] optimize random tick + + +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 46e171ca454253c32e22c0c18587e9a7ba19f331..36e592ff42eba050829f9c4d055c3d49a78ba813 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -633,6 +633,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } finally { + list.clear(); + } ++ this.level.randomTickSystem.tick(this.level); // Leaf - optimize random tick + + this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations + if (_boolean) { +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 27da552e2542153a58d6177f592cf30d858c41a9..a180612fea46ec9f7dcfe1781e985dd2e98ed513 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1114,6 +1114,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking + ++ public org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { + final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting + ChunkPos pos = chunk.getPos(); +@@ -1129,7 +1130,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } // Paper - Option to disable ice and snow + + if (randomTickSpeed > 0) { +- this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking ++ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) randomTickSystem.tickChunk(this.simpleRandom, chunk, randomTickSpeed); // Leaf - optimize random tick ++ else this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking // Leaf - optimize random tick + } + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab627b0128 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -152,6 +152,48 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + } + // Gale end - Airplane - optimize random calls in chunk ticking - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively + ++ // Leaf start - optimize random tick ++ private boolean leaf$tickingBlocksDirty = true; ++ private int leaf$tickingBlocksCount; ++ private int leaf$firstTickingSectionIndex = -1; ++ public final int leaf$tickingBlocksCount() { ++ if (!leaf$tickingBlocksDirty) { ++ return leaf$tickingBlocksCount; ++ } ++ leaf$tickingBlocksDirty = false; ++ int sum = 0; ++ leaf$firstTickingSectionIndex = -1; ++ for (int i = 0; i < sections.length; i++) { ++ LevelChunkSection section = sections[i]; ++ int size = section.moonrise$getTickingBlockList().size(); ++ if (size != 0 && leaf$firstTickingSectionIndex == -1) { ++ leaf$firstTickingSectionIndex = i; ++ } ++ sum += size; ++ } ++ leaf$tickingBlocksCount = sum; ++ return sum; ++ } ++ public final java.util.OptionalLong leaf$getTickingPos(int idx) { ++ if (leaf$firstTickingSectionIndex != -1) { ++ for (int i = leaf$firstTickingSectionIndex; i < sections.length; i++) { ++ LevelChunkSection section = sections[i]; ++ var l = section.moonrise$getTickingBlockList(); ++ int size = l.size(); ++ if (idx < size) { ++ short loc = l.getRaw(idx); ++ int x = (loc & 15) | (chunkPos.x << 4); ++ int y = (loc >>> 8) | ((getMinSectionY() + i) << 4); ++ int z = ((loc >>> 4) & 15) | (chunkPos.z << 4); ++ return java.util.OptionalLong.of(BlockPos.asLong(x, y, z)); ++ } ++ idx -= size; ++ } ++ } ++ leaf$tickingBlocksDirty = true; ++ return java.util.OptionalLong.empty(); ++ } ++ // Leaf end - optimize random tick + public LevelChunk(Level level, ChunkPos pos) { + this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); + } +@@ -416,6 +458,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + if (blockState == state) { + return null; + } else { ++ // Leaf start - optimize random tick ++ if (blockState.isRandomlyTicking() != state.isRandomlyTicking()) { ++ leaf$tickingBlocksDirty = true; ++ } ++ // Leaf end - optimize random tick + Block block = state.getBlock(); + this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, state); + this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, state); diff --git a/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch index d7fb58da..2aa176bc 100644 --- a/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch @@ -100,7 +100,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194 - // Paper end - detailed watchdog information } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 12cf6dc9bac109a4feeeeede5f3762b20ea582bb..b450ac5e19d42765c739311de1566b7b0c11c88b 100644 +index 36e592ff42eba050829f9c4d055c3d49a78ba813..b32cb1c85a9f7a7a96a257c4546ee7e21cd91a6d 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -623,8 +623,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -117,10 +117,10 @@ index 12cf6dc9bac109a4feeeeede5f3762b20ea582bb..b450ac5e19d42765c739311de1566b7b for (LevelChunk levelChunk : list) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 117a8ef98c369bf0919e44fe823d6af0758816b5..47357e1d54563e30b4281c410a1ab70ccd672ad0 100644 +index a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd655cb0e90 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1526,13 +1526,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1518,13 +1518,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - log detailed entity tick information public void tickNonPassenger(Entity entity) { @@ -134,7 +134,7 @@ index 117a8ef98c369bf0919e44fe823d6af0758816b5..47357e1d54563e30b4281c410a1ab70c entity.setOldPosAndRot(); entity.tickCount++; entity.totalEntityAge++; // Paper - age-like counter for all entities -@@ -1545,13 +1539,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1537,13 +1531,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } @@ -149,7 +149,7 @@ index 117a8ef98c369bf0919e44fe823d6af0758816b5..47357e1d54563e30b4281c410a1ab70c private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 7573c566aace4b7661c5aed6d96653472bab81bb..84808bf3311067409aad99817d3f1df2c098d83c 100644 +index fc39af434b59054260d763b994fb50bcf92b4ff3..ddc2a7fbca414b8e3c044cfe7076d2cac182b63e 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -1166,16 +1166,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index 780e2678..371f54e5 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -1,6 +1,5 @@ package org.dreeam.leaf.async.ai; -import it.unimi.dsi.fastutil.ints.IntArrayList; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; @@ -10,85 +9,54 @@ import org.dreeam.leaf.config.modules.async.AsyncTargetFinding; import org.dreeam.leaf.util.queue.SpscIntQueue; import java.util.OptionalInt; -import java.util.concurrent.locks.LockSupport; public class AsyncGoalExecutor { protected static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal"); protected final SpscIntQueue queue; - protected final SpscIntQueue wake; - protected final IntArrayList submit; private final ServerLevel world; - private long midTickCount = 0L; - public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel world) { + public AsyncGoalExecutor(ServerLevel world) { this.world = world; this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize); - this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize); - this.submit = new IntArrayList(); } - boolean wake(int id) { - Entity entity = this.world.getEntities().get(id); - if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) { - return false; - } - mob.goalSelector.ctx.wake(); - mob.targetSelector.ctx.wake(); - return true; - } - - public final void submit(int entityId) { - this.submit.add(entityId); - } - - public final void tick() { - batchSubmit(); + boolean wakeAll() { + boolean success = false; while (true) { - OptionalInt result = this.wake.recv(); + OptionalInt result = queue.recv(); if (result.isEmpty()) { break; } int id = result.getAsInt(); - if (poll(id) && !this.queue.send(id)) { - do { - wake(id); - } while (poll(id)); - } + success = true; + wake(id); } + return success; } - private void batchSubmit() { - if (submit.isEmpty()) { + public void tickMob(Mob mob) { + if (!poll(mob)) { return; } - int[] raw = submit.elements(); - int size = submit.size(); - for (int i = 0; i < size; i++) { - int id = raw[i]; - if (poll(id) && !this.queue.send(id)) { - do { - wake(id); - } while (poll(id)); - } + int entityId = mob.getId(); + if (!this.queue.send(entityId)) { + do { + wake(entityId); + } while (poll(mob)); } - this.submit.clear(); } - public final void midTick() { - if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) { - batchSubmit(); - } - - midTickCount += 1; - } - - private boolean poll(int id) { + private void wake(int id) { Entity entity = this.world.getEntities().get(id); if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) { - return false; + return; } + mob.goalSelector.ctx.wake(); + mob.targetSelector.ctx.wake(); + } + private boolean poll(Mob mob) { try { mob.tickingTarget = true; boolean a = mob.targetSelector.poll(); @@ -97,8 +65,7 @@ public class AsyncGoalExecutor { return a || b; } catch (Exception e) { LOGGER.error("Exception while polling", e); - // retry - return true; + return false; } } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java index e989adbf..4012e4cf 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -22,26 +22,11 @@ public class AsyncGoalThread extends Thread { while (RUNNING) { boolean retry = false; for (ServerLevel level : server.getAllLevels()) { - var exec = level.asyncGoalExecutor; - while (true) { - OptionalInt result = exec.queue.recv(); - if (result.isEmpty()) { - break; - } - int id = result.getAsInt(); - retry = true; - if (exec.wake(id)) { - while (!exec.wake.send(id)) { - Thread.onSpinWait(); - } - } - } - - Thread.yield(); + retry |= level.asyncGoalExecutor.wakeAll(); } if (!retry) { - LockSupport.parkNanos(10_000L); + LockSupport.parkNanos(1_000_000L); } } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java index 604140f4..e525e72f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java @@ -7,6 +7,7 @@ import net.minecraft.Util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dreeam.leaf.config.modules.misc.SentryDSN; +import org.dreeam.leaf.config.modules.opt.FastBiomeManagerSeedObfuscation; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.bukkit.Bukkit; @@ -243,6 +244,7 @@ public class LeafConfig { List extraHidden = existing != null ? new ArrayList<>(Arrays.asList(existing.split(","))) : new ArrayList<>(); extraHidden.add(SentryDSN.sentryDsnConfigPath); // Hide Sentry DSN key + extraHidden.add(FastBiomeManagerSeedObfuscation.seedObfKeyPath); // Hide FastBiomeManagerSeedObfuscation key return extraHidden; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index ec1a8a7b..101b9eaf 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -16,7 +16,6 @@ public class AsyncTargetFinding extends ConfigModules { public static boolean searchBlock = true; public static boolean searchEntity = true; public static int queueSize = 4096; - public static long threshold = 10L; private static boolean asyncTargetFindingInitialized; @Override @@ -36,21 +35,17 @@ public class AsyncTargetFinding extends ConfigModules { enabled = config.getBoolean(getBasePath() + ".enabled", enabled); // Disable if parallel world ticking is enabled, as they are incompatible. if (enabled && SparklyPaperParallelWorldTicking.enabled) { - LeafConfig.LOGGER.warn("Async Target Finding is incompatible with Parallel World Ticking. Disabling Async Target Finding automatically."); + LeafConfig.LOGGER.warn("Async target finding is incompatible with Parallel World Ticking. Disabling Async target finding automatically."); enabled = false; } alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true); searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); queueSize = config.getInt(getBasePath() + ".queue-size", 0); - threshold = config.getLong(getBasePath() + ".threshold", 0); if (queueSize <= 0) { queueSize = 4096; } - if (threshold == 0L) { - threshold = 10L; - } if (!enabled) { alertOther = false; searchEntity = false; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastBiomeManagerSeedObfuscation.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastBiomeManagerSeedObfuscation.java index 0189e746..c49a688c 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastBiomeManagerSeedObfuscation.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastBiomeManagerSeedObfuscation.java @@ -15,6 +15,7 @@ public class FastBiomeManagerSeedObfuscation extends ConfigModules { @Experimental public static boolean enabled = false; public static long seedObfuscationKey = ThreadLocalRandom.current().nextLong(); + public static String seedObfKeyPath; @Override public void onLoaded() { @@ -26,7 +27,7 @@ public class FastBiomeManagerSeedObfuscation extends ConfigModules { """ **实验性功能** 将原版 BiomeManager 的 SHA-256 种子混淆换成 XXHash.""")); - seedObfuscationKey = config.getLong(getBasePath() + ".seed-obfuscation-key", seedObfuscationKey, + seedObfuscationKey = config.getLong(seedObfKeyPath = getBasePath() + ".seed-obfuscation-key", seedObfuscationKey, config.pickStringRegionBased( "Seed obfuscation key for XXHash.", "XXHash 的混淆种子.")); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java new file mode 100644 index 00000000..525bcac7 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java @@ -0,0 +1,20 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; + +public class OptimizeRandomTick extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".optimise-random-tick"; + } + + @Experimental + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java b/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java index ee6d5dc5..301a66ee 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java @@ -56,11 +56,7 @@ public class FasterRandomSource implements BitRandomSource { @Override public final int next(int bits) { - if (useDirectImpl) { - return (int) ((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits)); - } - - return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits)); + return (int) ((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits)); } public static class FasterRandomSourcePositionalRandomFactory implements PositionalRandomFactory { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java new file mode 100644 index 00000000..23967287 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java @@ -0,0 +1,168 @@ +package org.dreeam.leaf.world; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.material.FluidState; + +import java.util.OptionalLong; + +public final class RandomTickSystem { + private static final long SCALE = 0x100000L; + private static final long CHUNK_BLOCKS = 4096L; + + /// reduce unnecessary sampling and block counting + private static final long TICK_MASK = 0b11L; + private static final long TICK_MUL = 4L; + private static final int BITS_STEP = 2; + private static final int BITS_MAX = 60; + + private final LongArrayList queue = new LongArrayList(); + private final LongArrayList samples = new LongArrayList(); + private final LongArrayList weights = new LongArrayList(); + private long weightsSum = 0L; + + private int bits = 60; + private long cacheRandom = 0L; + + public void tick(ServerLevel world) { + if (weights.isEmpty() || samples.isEmpty()) { + return; + } + + final var random = world.simpleRandom; + final long chosen; + if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) { + chosen = weightsSum / SCALE + 1L; + } else { + chosen = weightsSum / SCALE; + } + if (chosen == 0L) { + return; + } + + final long spoke = weightsSum / chosen; + if (spoke == 0L) { + return; + } + + final long[] weightsRaw = weights.elements(); + final long[] samplesRaw = samples.elements(); + + long accumulated = weightsRaw[0]; + long current = boundedNextLong(random, spoke); + int i = 0; + while (current < weightsSum) { + while (accumulated < current) { + i += 1; + accumulated += weightsRaw[i]; + } + queue.add(samplesRaw[i]); + current += spoke; + } + while (queue.size() < chosen) { + queue.add(samplesRaw[i]); + } + + long[] queueRaw = queue.elements(); + int j = 0; + int k; + for (k = queue.size() - 3; j < k; j += 4) { + final long packed1 = queueRaw[j]; + final long packed2 = queueRaw[j + 1]; + final long packed3 = queueRaw[j + 2]; + final long packed4 = queueRaw[j + 3]; + final LevelChunk chunk1 = getChunk(world, packed1); + final LevelChunk chunk2 = packed1 != packed2 ? getChunk(world, packed2) : chunk1; + final LevelChunk chunk3 = packed2 != packed3 ? getChunk(world, packed3) : chunk2; + final LevelChunk chunk4 = packed3 != packed4 ? getChunk(world, packed4) : chunk3; + if (chunk1 != null) tickBlock(world, chunk1, random); + if (chunk2 != null) tickBlock(world, chunk2, random); + if (chunk3 != null) tickBlock(world, chunk3, random); + if (chunk4 != null) tickBlock(world, chunk4, random); + } + for (k = queue.size(); j < k; j++) { + final LevelChunk chunk = getChunk(world, queueRaw[j]); + if (chunk != null) tickBlock(world, chunk, random); + } + + weightsSum = 0L; + queue.clear(); + weights.clear(); + samples.clear(); + } + + private static LevelChunk getChunk(ServerLevel world, long packed) { + return world.chunkSource.getChunkAtIfLoadedImmediately((int) packed, (int) (packed >> 32)); + } + + private static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) { + int count = chunk.leaf$tickingBlocksCount(); + if (count == 0) { + return; + } + OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(count)); + if (optionalPos.isEmpty()) { + return; + } + BlockPos pos = BlockPos.of(optionalPos.getAsLong()); + BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); + state.randomTick(world, pos, random); + + final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + if (doubleTickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { + fluidState.randomTick(world, pos, random); + } + } + } + + public void tickChunk( + RandomSource random, + LevelChunk chunk, + long tickSpeed + ) { + if (this.bits == BITS_MAX) { + this.bits = 0; + this.cacheRandom = random.nextLong(); + } else { + this.bits += BITS_STEP; + } + if ((this.cacheRandom & (TICK_MASK << bits)) == 0L) { + long count = chunk.leaf$tickingBlocksCount(); + if (count != 0L) { + long weight = (TICK_MUL * tickSpeed * count * SCALE) / CHUNK_BLOCKS; + samples.add(chunk.getPos().longKey); + weights.add(weight); + weightsSum += weight; + } + } + } + + /** + * @param rng a random number generator to be used as a + * source of pseudorandom {@code long} values + * @param bound the upper bound (exclusive); must be greater than zero + * + * @return a pseudorandomly chosen {@code long} value + * + * @see java.util.random.RandomGenerator#nextLong(long) nextLong(bound) + */ + public static long boundedNextLong(RandomSource rng, long bound) { + final long m = bound - 1; + long r = rng.nextLong(); + if ((bound & m) == 0L) { + r &= m; + } else { + for (long u = r >>> 1; + u + m - (r = u % bound) < 0L; + u = rng.nextLong() >>> 1) + ; + } + return r; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java b/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java index f4f3c7be..7e34b2e1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java @@ -20,8 +20,6 @@ public class OptimizedPoweredRails { private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS; - private static int RAIL_POWER_LIMIT = 8; - private static final Object2BooleanOpenHashMap CHECKED_POS_POOL = new Object2BooleanOpenHashMap<>(); private static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) { @@ -36,14 +34,6 @@ public class OptimizedPoweredRails { ); } - public static int getRailPowerLimit() { - return RAIL_POWER_LIMIT; - } - - public static void setRailPowerLimit(int powerLimit) { - RAIL_POWER_LIMIT = powerLimit; - } - public static void updateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) { boolean shouldBePowered = level.hasNeighborSignal(pos) || findPoweredRailSignalFaster(self, level, pos, state, true, 0, CHECKED_POS_POOL) || @@ -97,7 +87,7 @@ public class OptimizedPoweredRails { private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level, BlockPos pos, BlockState state, boolean searchForward, int distance, Object2BooleanOpenHashMap checkedPos) { - if (distance >= RAIL_POWER_LIMIT - 1) return false; + if (distance >= level.purpurConfig.railActivationRange) return false; int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); @@ -203,7 +193,8 @@ public class OptimizedPoweredRails { private static void setRailPositionsPower(PoweredRailBlock self, Level level, BlockPos pos, Object2BooleanOpenHashMap checkedPos, int[] count, int i, Direction dir) { - for (int z = 1; z < RAIL_POWER_LIMIT; z++) { + final int railPowerLimit = level.purpurConfig.railActivationRange; + for (int z = 1; z < railPowerLimit; z++) { BlockPos newPos = pos.relative(dir, z); BlockState state = level.getBlockState(newPos); if (checkedPos.containsKey(newPos)) { @@ -229,7 +220,8 @@ public class OptimizedPoweredRails { int[] count, int i, Direction dir) { Object2BooleanOpenHashMap checkedPos = CHECKED_POS_POOL; checkedPos.clear(); - for (int z = 1; z < RAIL_POWER_LIMIT; z++) { + final int railPowerLimit = level.purpurConfig.railActivationRange; + for (int z = 1; z < railPowerLimit; z++) { BlockPos newPos = pos.relative(dir, z); BlockState state = level.getBlockState(newPos); if (!state.is(self) || !state.getValue(POWERED) || level.hasNeighborSignal(newPos) || diff --git a/public/image/RainYun.jpg b/public/image/RainYun.jpg new file mode 100644 index 00000000..38e45e58 Binary files /dev/null and b/public/image/RainYun.jpg differ diff --git a/public/readme/README_CN.md b/public/readme/README_CN.md index 0d0a16f5..0fd85368 100644 --- a/public/readme/README_CN.md +++ b/public/readme/README_CN.md @@ -123,7 +123,7 @@ Leaf 根据其上游项目及其他材料,采用多种开源许可证授权, ## 🔥 特别感谢 -Jianke Cloud Host +[![Jianke Cloud Host](../image/JiankeServer.jpg)](https://cloud.swordsman.com.cn/?i8ab42c) \ 剑客云 | cloud of swordsman 如果你想找一个低价高性能、低延迟的云服务商,剑客云是个不错的选择!你可以在 [这里](https://cloud.swordsman.com.cn/?i8ab42c) 注册。 @@ -131,7 +131,15 @@ Leaf 根据其上游项目及其他材料,采用多种开源许可证授权, If you want to find a cheaper, high performance, stable, lower latency host, then cloud of swordsman is a good choice! Registers and purchases in [here](https://cloud.swordsman.com.cn/?i8ab42c). --- -![YourKit](https://www.yourkit.com/images/yklogo.png) +[![雨云](../image/RainYun.jpg)](https://www.rainyun.com/NzE2NTc1_) \ +雨云 | RainYun + +国际多线路选择,配套云存储 — 购买服务后七天内不满意可以申请退订,强大的技术支持团队和高在线率客服。雨云云服务器,用稳定和性价比,助力您快速上云。点击前往 [雨云](https://www.rainyun.com/NzE2NTc1_)。 + +Global multi-line routing with cloud storage. Refund available within 7 days. Reliable uptime and expert support. RainYun — stable, cost-effective, and ready for fast cloud deployment. Visit [RainYun]([雨云](https://www.rainyun.com/NzE2NTc1_)) + +--- +![YourKit](https://www.yourkit.com/images/yklogo.png) \ YourKit 通过创新和智能的工具支持开源项目,用于监控和分析 Java 和 .NET 应用程序。 YourKit 是 [YourKit Java Profiler](https://www.yourkit.com/java/profiler/)、 diff --git a/scripts/upstreamCommit.sh b/scripts/upstreamCommit.sh index 4fd006df..0f6ea255 100755 --- a/scripts/upstreamCommit.sh +++ b/scripts/upstreamCommit.sh @@ -53,7 +53,7 @@ logsuffix="" # Paper updates if [ -n "$paperHash" ]; then newHash=$(git diff gradle.properties | awk '/^+paperCommit =/{print $NF}') - paper=$(getCommits "PaperMC/Paper" "$paperHash" $(echo $newHash | grep . -q && echo $newHash || echo "HEAD")) + paper=$(getCommits "PaperMC/Paper" "$paperHash" $(echo $newHash | grep . -q && echo $newHash || echo "main")) # Update this on every version update # Updates found if [ -n "$paper" ]; then @@ -64,7 +64,7 @@ fi # Purpur updates if [ -n "$purpurHash" ]; then - purpur=$(getCommits "PurpurMC/Purpur" "$purpurHash" "HEAD") + purpur=$(getCommits "PurpurMC/Purpur" "$purpurHash" "ver/1.21.6") # Update this on every version update # Updates found if [ -n "$purpur" ]; then