Files
PlazmaBukkitMC/patches/server/0001-Pufferfish-Server-Changes.patch
Alpha 922c33171c Rewrite Configuration (#60)
* work

* work

* more work

* complete configuration base

* More work

* more work

* Configurable cave lava sea level, Fix MC-237017

* more more more work

* Updated Upstream (Paper, Pufferfish, Purpur)

* Rework, again

* Cleanup workflows, fix build

* typo

* more work

* more work

- Updated Upstream (Paper)
- [CI-Skip] Update Upstream CacheData (Pufferfish, Purpur)
- Update Dependencies (Paperweight)
- Fix build
- Update Wrapper

* more works

* more more works

* Fix build

* Fix test

* Implemented FixMySpawnR, Added more config optimize

* Fix build
2023-12-09 11:22:36 +09:00

3399 lines
161 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kevin Raneri <kevin.raneri@gmail.com>
Date: Sat, 25 Nov 2023 11:51:41 +0000
Subject: [PATCH] Pufferfish Server Changes
Original: Kevin Raneri <kevin.raneri@gmail.com>
Copyright (C) 2023 Pufferfish Studios LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/build.gradle.kts b/build.gradle.kts
index 64479f0a892d6847f987d844efe282a6080d607b..9525f76103136dfc900c70f97416864115f75ed5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,8 +13,12 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) {
val alsoShade: Configuration by configurations.creating
dependencies {
- implementation(project(":paper-api"))
- implementation(project(":paper-mojangapi"))
+ implementation(project(":pufferfish-api")) // Pufferfish // Paper
+ // Pufferfish start
+ implementation("io.papermc.paper:paper-mojangapi:1.19.2-R0.1-SNAPSHOT") {
+ exclude("io.papermc.paper", "paper-api")
+ }
+ // Pufferfish end
// Paper start
implementation("org.jline:jline-terminal-jansi:3.21.0")
implementation("net.minecrell:terminalconsoleappender:1.3.0")
@@ -51,6 +55,13 @@ dependencies {
runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3")
runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3")
+ // Pufferfish start
+ implementation("org.yaml:snakeyaml:1.32")
+ implementation ("com.github.carleslc.Simple-YAML:Simple-Yaml:1.8.4") {
+ exclude(group="org.yaml", module="snakeyaml")
+ }
+ // Pufferfish end
+
testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testImplementation("org.hamcrest:hamcrest:2.2")
@@ -58,6 +69,14 @@ dependencies {
}
val craftbukkitPackageVersion = "1_20_R2" // Paper
+
+// Pufferfish Start
+tasks.withType<JavaCompile> {
+ val compilerArgs = options.compilerArgs
+ compilerArgs.add("--add-modules=jdk.incubator.vector")
+}
+// Pufferfish End
+
tasks.jar {
archiveClassifier.set("dev")
@@ -70,7 +89,7 @@ tasks.jar {
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
"Implementation-Title" to "CraftBukkit",
- "Implementation-Version" to "git-Paper-$implementationVersion",
+ "Implementation-Version" to "git-Pufferfish-$implementationVersion", // Pufferfish
"Implementation-Vendor" to date, // Paper
"Specification-Title" to "Bukkit",
"Specification-Version" to project.version,
@@ -210,7 +229,5 @@ val runtimeClasspathForRunDev = sourceSets.main.flatMap { src ->
}
tasks.registerRunTask("runDev") {
description = "Spin up a non-relocated Mojang-mapped test server"
- classpath(tasks.filterProjectDir.flatMap { it.outputJar })
- classpath(runtimeClasspathForRunDev)
- jvmArgs("-DPaper.isRunDev=true")
+ classpath(sourceSets.main.map { it.runtimeClasspath })
}
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
index a2f71a6d1a9e98133dff6cd0f625da9435a8af14..ff940e43ca35094bbcae6c7d471d3c4aeb7c1727 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -242,7 +242,8 @@ public class TimingsExport extends Thread {
parent.put("config", createObject(
pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Pufferfish
+ pair("pufferfish", mapAsJSON(gg.pufferfish.pufferfish.PufferfishConfig.getConfigCopy(), null)) // Pufferfish
));
new TimingsExport(listeners, parent, history).start();
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
index 4b002e8b75d117b726b0de274a76d3596fce015b..692c962193cf9fcc6801fc93f3220bdc673d527b 100644
--- a/src/main/java/com/destroystokyo/paper/Metrics.java
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
@@ -593,7 +593,7 @@ public class Metrics {
boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
// Only start Metrics, if it's enabled in the config
if (config.getBoolean("enabled", true)) {
- Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger());
+ Metrics metrics = new Metrics("Pufferfish", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish
metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
String minecraftVersion = Bukkit.getVersion();
@@ -607,11 +607,11 @@ public class Metrics {
final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion();
if (implVersion != null) {
final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1);
- paperVersion = "git-Paper-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash);
+ paperVersion = "git-Pufferfish-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Pufferfish
} else {
paperVersion = "unknown";
}
- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> paperVersion));
+ metrics.addCustomChart(new Metrics.SimplePie("pufferfish_version", () -> paperVersion)); // Pufferfish
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
index 41b9405d6759d865e0d14dd4f95163e9690e967d..091b1ae822e1c0517e59572e7a9bda11e998c0ee 100644
--- a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
@@ -26,7 +26,7 @@ public abstract class AreaMap<E> {
// we use linked for better iteration.
// map of: coordinate to set of objects in coordinate
- protected final Long2ObjectOpenHashMap<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f);
+ protected Long2ObjectOpenHashMap<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f); // Pufferfish - not actually final
protected final PooledLinkedHashSets<E> pooledHashSets;
protected final ChangeCallback<E> addCallback;
@@ -160,7 +160,8 @@ public abstract class AreaMap<E> {
protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getEmptySetFor(final E object);
// expensive op, only for debug
- protected void validate(final E object, final int viewDistance) {
+ protected void validate0(final E object, final int viewDistance) { // Pufferfish - rename this thing just in case it gets used I'd rather a compile time error.
+ if (true) throw new UnsupportedOperationException(); // Pufferfish - not going to put in the effort to fix this if it doesn't ever get used.
int entiesGot = 0;
int expectedEntries = (2 * viewDistance + 1);
expectedEntries *= expectedEntries;
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
index 46954db7ecd35ac4018fdf476df7c8020d7ce6c8..1ad890a244bdf6df48a8db68cb43450e08c788a6 100644
--- a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
@@ -5,7 +5,7 @@ import net.minecraft.server.level.ServerPlayer;
/**
* @author Spottedleaf
*/
-public final class PlayerAreaMap extends AreaMap<ServerPlayer> {
+public class PlayerAreaMap extends AreaMap<ServerPlayer> { // Pufferfish - not actually final
public PlayerAreaMap() {
super();
diff --git a/src/main/java/gg/airplane/structs/FluidDirectionCache.java b/src/main/java/gg/airplane/structs/FluidDirectionCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa8467b9dda1f7707e41f50ac7b3e9d7343723ec
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/FluidDirectionCache.java
@@ -0,0 +1,136 @@
+package gg.airplane.structs;
+
+import it.unimi.dsi.fastutil.HashCommon;
+
+/**
+ * This is a replacement for the cache used in FluidTypeFlowing.
+ * The requirements for the previous cache were:
+ * - Store 200 entries
+ * - Look for the flag in the cache
+ * - If it exists, move to front of cache
+ * - If it doesn't exist, remove last entry in cache and insert in front
+ *
+ * This class accomplishes something similar, however has a few different
+ * requirements put into place to make this more optimize:
+ *
+ * - maxDistance is the most amount of entries to be checked, instead
+ * of having to check the entire list.
+ * - In combination with that, entries are all tracked by age and how
+ * frequently they're used. This enables us to remove old entries,
+ * without constantly shifting any around.
+ *
+ * Usage of the previous map would have to reset the head every single usage,
+ * shifting the entire map. Here, nothing happens except an increment when
+ * the cache is hit, and when it needs to replace an old element only a single
+ * element is modified.
+ */
+public class FluidDirectionCache<T> {
+
+ private static class FluidDirectionEntry<T> {
+ private final T data;
+ private final boolean flag;
+ private int uses = 0;
+ private int age = 0;
+
+ private FluidDirectionEntry(T data, boolean flag) {
+ this.data = data;
+ this.flag = flag;
+ }
+
+ public int getValue() {
+ return this.uses - (this.age >> 1); // age isn't as important as uses
+ }
+
+ public void incrementUses() {
+ this.uses = this.uses + 1 & Integer.MAX_VALUE;
+ }
+
+ public void incrementAge() {
+ this.age = this.age + 1 & Integer.MAX_VALUE;
+ }
+ }
+
+ private final FluidDirectionEntry[] entries;
+ private final int mask;
+ private final int maxDistance; // the most amount of entries to check for a value
+
+ public FluidDirectionCache(int size) {
+ int arraySize = HashCommon.nextPowerOfTwo(size);
+ this.entries = new FluidDirectionEntry[arraySize];
+ this.mask = arraySize - 1;
+ this.maxDistance = Math.min(arraySize, 4);
+ }
+
+ public Boolean getValue(T data) {
+ FluidDirectionEntry curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) {
+ return null;
+ } else if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return curr.flag;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return curr.flag;
+ } else if (++checked >= this.maxDistance) {
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ public void putValue(T data, boolean flag) {
+ FluidDirectionEntry<T> curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) {
+ this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add
+ return;
+ } else if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data.equals(curr.data)) {
+ curr.incrementUses();
+ return;
+ } else if (++checked >= this.maxDistance) {
+ this.forceAdd(data, flag);
+ return;
+ }
+ }
+
+ this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add
+ }
+
+ private void forceAdd(T data, boolean flag) {
+ int expectedPos = HashCommon.mix(data.hashCode()) & this.mask;
+
+ int toRemovePos = expectedPos;
+ FluidDirectionEntry entryToRemove = this.entries[toRemovePos];
+
+ for (int i = expectedPos + 1; i < expectedPos + this.maxDistance; i++) {
+ int pos = i & this.mask;
+ FluidDirectionEntry entry = this.entries[pos];
+ if (entry.getValue() < entryToRemove.getValue()) {
+ toRemovePos = pos;
+ entryToRemove = entry;
+ }
+
+ entry.incrementAge(); // use this as a mechanism to age the other entries
+ }
+
+ // remove the least used/oldest entry
+ this.entries[toRemovePos] = new FluidDirectionEntry(data, flag);
+ }
+}
diff --git a/src/main/java/gg/airplane/structs/ItemListWithBitset.java b/src/main/java/gg/airplane/structs/ItemListWithBitset.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b7a4ee47f4445d7f2ac91d3a73ae113edbdddb2
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/ItemListWithBitset.java
@@ -0,0 +1,114 @@
+package gg.airplane.structs;
+
+import net.minecraft.core.NonNullList;
+import net.minecraft.world.item.ItemStack;
+import org.apache.commons.lang.Validate;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ItemListWithBitset extends AbstractList<ItemStack> {
+ public static ItemListWithBitset fromList(List<ItemStack> list) {
+ if (list instanceof ItemListWithBitset ours) {
+ return ours;
+ }
+ return new ItemListWithBitset(list);
+ }
+
+ private static ItemStack[] createArray(int size) {
+ ItemStack[] array = new ItemStack[size];
+ Arrays.fill(array, ItemStack.EMPTY);
+ return array;
+ }
+
+ private final ItemStack[] items;
+
+ private long bitSet = 0;
+ private final long allBits;
+
+ private static class OurNonNullList extends NonNullList<ItemStack> {
+ protected OurNonNullList(List<ItemStack> delegate) {
+ super(delegate, ItemStack.EMPTY);
+ }
+ }
+
+ public final NonNullList<ItemStack> nonNullList = new OurNonNullList(this);
+
+ private ItemListWithBitset(List<ItemStack> list) {
+ this(list.size());
+
+ for (int i = 0; i < list.size(); i++) {
+ this.set(i, list.get(i));
+ }
+ }
+
+ public ItemListWithBitset(int size) {
+ Validate.isTrue(size < Long.BYTES * 8, "size is too large");
+
+ this.items = createArray(size);
+ this.allBits = ((1L << size) - 1);
+ }
+
+ public boolean isCompletelyEmpty() {
+ return this.bitSet == 0;
+ }
+
+ public boolean hasFullStacks() {
+ return (this.bitSet & this.allBits) == allBits;
+ }
+
+ @Override
+ public ItemStack set(int index, @NotNull ItemStack itemStack) {
+ ItemStack existing = this.items[index];
+
+ this.items[index] = itemStack;
+
+ if (itemStack == ItemStack.EMPTY) {
+ this.bitSet &= ~(1L << index);
+ } else {
+ this.bitSet |= 1L << index;
+ }
+
+ return existing;
+ }
+
+ @NotNull
+ @Override
+ public ItemStack get(int var0) {
+ return this.items[var0];
+ }
+
+ @Override
+ public int size() {
+ return this.items.length;
+ }
+
+ @Override
+ public void clear() {
+ Arrays.fill(this.items, ItemStack.EMPTY);
+ }
+
+ // these are unsupported for block inventories which have a static size
+ @Override
+ public void add(int var0, ItemStack var1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ItemStack remove(int var0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return "ItemListWithBitset{" +
+ "items=" + Arrays.toString(items) +
+ ", bitSet=" + Long.toString(bitSet, 2) +
+ ", allBits=" + Long.toString(allBits, 2) +
+ ", size=" + this.items.length +
+ '}';
+ }
+}
diff --git a/src/main/java/gg/airplane/structs/Long2FloatAgingCache.java b/src/main/java/gg/airplane/structs/Long2FloatAgingCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7f297ebb569f7c1f205e967ca485be70013a714
--- /dev/null
+++ b/src/main/java/gg/airplane/structs/Long2FloatAgingCache.java
@@ -0,0 +1,119 @@
+package gg.airplane.structs;
+
+import it.unimi.dsi.fastutil.HashCommon;
+
+/**
+ * A replacement for the cache used in Biome.
+ */
+public class Long2FloatAgingCache {
+
+ private static class AgingEntry {
+ private long data;
+ private float value;
+ private int uses = 0;
+ private int age = 0;
+
+ private AgingEntry(long data, float value) {
+ this.data = data;
+ this.value = value;
+ }
+
+ public void replace(long data, float flag) {
+ this.data = data;
+ this.value = flag;
+ }
+
+ public int getValue() {
+ return this.uses - (this.age >> 1); // age isn't as important as uses
+ }
+
+ public void incrementUses() {
+ this.uses = this.uses + 1 & Integer.MAX_VALUE;
+ }
+
+ public void incrementAge() {
+ this.age = this.age + 1 & Integer.MAX_VALUE;
+ }
+ }
+
+ private final AgingEntry[] entries;
+ private final int mask;
+ private final int maxDistance; // the most amount of entries to check for a value
+
+ public Long2FloatAgingCache(int size) {
+ int arraySize = HashCommon.nextPowerOfTwo(size);
+ this.entries = new AgingEntry[arraySize];
+ this.mask = arraySize - 1;
+ this.maxDistance = Math.min(arraySize, 4);
+ }
+
+ public float getValue(long data) {
+ AgingEntry curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(HashCommon.long2int(data)) & this.mask]) == null) {
+ return Float.NaN;
+ } else if (data == curr.data) {
+ curr.incrementUses();
+ return curr.value;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data == curr.data) {
+ curr.incrementUses();
+ return curr.value;
+ } else if (++checked >= this.maxDistance) {
+ break;
+ }
+ }
+
+ return Float.NaN;
+ }
+
+ public void putValue(long data, float value) {
+ AgingEntry curr;
+ int pos;
+
+ if ((curr = this.entries[pos = HashCommon.mix(HashCommon.long2int(data)) & this.mask]) == null) {
+ this.entries[pos] = new AgingEntry(data, value); // add
+ return;
+ } else if (data == curr.data) {
+ curr.incrementUses();
+ return;
+ }
+
+ int checked = 1; // start at 1 because we already checked the first spot above
+
+ while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) {
+ if (data == curr.data) {
+ curr.incrementUses();
+ return;
+ } else if (++checked >= this.maxDistance) {
+ this.forceAdd(data, value);
+ return;
+ }
+ }
+
+ this.entries[pos] = new AgingEntry(data, value); // add
+ }
+
+ private void forceAdd(long data, float value) {
+ int expectedPos = HashCommon.mix(HashCommon.long2int(data)) & this.mask;
+ AgingEntry entryToRemove = this.entries[expectedPos];
+
+ for (int i = expectedPos + 1; i < expectedPos + this.maxDistance; i++) {
+ int pos = i & this.mask;
+ AgingEntry entry = this.entries[pos];
+ if (entry.getValue() < entryToRemove.getValue()) {
+ entryToRemove = entry;
+ }
+
+ entry.incrementAge(); // use this as a mechanism to age the other entries
+ }
+
+ // remove the least used/oldest entry
+ entryToRemove.replace(data, value);
+ }
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f74732f4ab6ea
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java
@@ -0,0 +1,68 @@
+package gg.pufferfish.pufferfish;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.md_5.bungee.api.ChatColor;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+public class PufferfishCommand extends Command {
+
+ public PufferfishCommand() {
+ super("pufferfish");
+ this.description = "Pufferfish related commands";
+ this.usageMessage = "/pufferfish [reload | version]";
+ this.setPermission("bukkit.command.pufferfish");
+ }
+
+ public static void init() {
+ MinecraftServer.getServer().server.getCommandMap().register("pufferfish", "Pufferfish", new PufferfishCommand());
+ }
+
+ @Override
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
+ if (args.length == 1) {
+ return Stream.of("reload", "version")
+ .filter(arg -> arg.startsWith(args[0].toLowerCase()))
+ .collect(Collectors.toList());
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
+ if (!testPermission(sender)) return true;
+ String prefix = ChatColor.of("#12fff6") + "" + ChatColor.BOLD + "Pufferfish » " + ChatColor.of("#e8f9f9");
+
+ if (args.length != 1) {
+ sender.sendMessage(prefix + "Usage: " + usageMessage);
+ args = new String[]{"version"};
+ }
+
+ if (args[0].equalsIgnoreCase("reload")) {
+ MinecraftServer console = MinecraftServer.getServer();
+ try {
+ PufferfishConfig.load();
+ } catch (IOException e) {
+ sender.sendMessage(Component.text("Failed to reload.", NamedTextColor.RED));
+ e.printStackTrace();
+ return true;
+ }
+ console.server.reloadCount++;
+
+ Command.broadcastCommandMessage(sender, prefix + "Pufferfish configuration has been reloaded.");
+ } else if (args[0].equalsIgnoreCase("version")) {
+ Command.broadcastCommandMessage(sender, prefix + "This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")");
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc66657cb4f978aa2df3ca1be6c683759952cc7a
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
@@ -0,0 +1,294 @@
+package gg.pufferfish.pufferfish;
+
+import gg.pufferfish.pufferfish.simd.SIMDDetection;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import net.minecraft.core.registries.BuiltInRegistries;
+import java.util.Locale;
+import java.util.Map;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.tags.TagKey;
+import org.apache.logging.log4j.Level;
+import org.bukkit.configuration.ConfigurationSection;
+import net.minecraft.world.entity.EntityType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import net.minecraft.server.MinecraftServer;
+import org.apache.logging.log4j.Level;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.MemoryConfiguration;
+import org.jetbrains.annotations.Nullable;
+import org.simpleyaml.configuration.comments.CommentType;
+import org.simpleyaml.configuration.file.YamlFile;
+import org.simpleyaml.exceptions.InvalidConfigurationException;
+
+public class PufferfishConfig {
+
+ private static final YamlFile config = new YamlFile();
+ private static int updates = 0;
+
+ private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) {
+ ConfigurationSection newSection = new MemoryConfiguration();
+ for (String key : section.getKeys(false)) {
+ if (section.isConfigurationSection(key)) {
+ newSection.set(key, convertToBukkit(section.getConfigurationSection(key)));
+ } else {
+ newSection.set(key, section.get(key));
+ }
+ }
+ return newSection;
+ }
+
+ public static ConfigurationSection getConfigCopy() {
+ return convertToBukkit(config);
+ }
+
+ public static int getUpdates() {
+ return updates;
+ }
+
+ public static void load() throws IOException {
+ File configFile = new File("pufferfish.yml");
+
+ if (configFile.exists()) {
+ try {
+ config.load(configFile);
+ } catch (InvalidConfigurationException e) {
+ throw new IOException(e);
+ }
+ }
+
+ getString("info.version", "1.0");
+ setComment("info",
+ "Pufferfish Configuration",
+ "Check out Pufferfish Host for maximum performance server hosting: https://pufferfish.host",
+ "Join our Discord for support: https://discord.gg/reZw4vQV9H",
+ "Download new builds at https://ci.pufferfish.host/job/Pufferfish");
+
+ for (Method method : PufferfishConfig.class.getDeclaredMethods()) {
+ if (Modifier.isStatic(method.getModifiers()) && Modifier.isPrivate(method.getModifiers()) && method.getParameterCount() == 0 &&
+ method.getReturnType() == Void.TYPE && !method.getName().startsWith("lambda")) {
+ method.setAccessible(true);
+ try {
+ method.invoke(null);
+ } catch (Throwable t) {
+ MinecraftServer.LOGGER.warn("Failed to load configuration option from " + method.getName(), t);
+ }
+ }
+ }
+
+ updates++;
+
+ config.save(configFile);
+
+ // Attempt to detect vectorization
+ try {
+ SIMDDetection.isEnabled = SIMDDetection.canEnable(PufferfishLogger.LOGGER);
+ SIMDDetection.versionLimited = SIMDDetection.getJavaVersion() != 17 && SIMDDetection.getJavaVersion() != 18 && SIMDDetection.getJavaVersion() != 19;
+ } catch (NoClassDefFoundError | Exception ignored) {
+ ignored.printStackTrace();
+ }
+
+ if (SIMDDetection.isEnabled) {
+ PufferfishLogger.LOGGER.info("SIMD operations detected as functional. Will replace some operations with faster versions.");
+ } else if (SIMDDetection.versionLimited) {
+ PufferfishLogger.LOGGER.warning("Will not enable SIMD! These optimizations are only safely supported on Java 17, Java 18, and Java 19.");
+ } else {
+ PufferfishLogger.LOGGER.warning("SIMD operations are available for your server, but are not configured!");
+ PufferfishLogger.LOGGER.warning("To enable additional optimizations, add \"--add-modules=jdk.incubator.vector\" to your startup flags, BEFORE the \"-jar\".");
+ PufferfishLogger.LOGGER.warning("If you have already added this flag, then SIMD operations are not supported on your JVM or CPU.");
+ PufferfishLogger.LOGGER.warning("Debug: Java: " + System.getProperty("java.version") + ", test run: " + SIMDDetection.testRun);
+ }
+ }
+
+ private static void setComment(String key, String... comment) {
+ if (config.contains(key)) {
+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK);
+ }
+ }
+
+ private static void ensureDefault(String key, Object defaultValue, String... comment) {
+ if (!config.contains(key)) {
+ config.set(key, defaultValue);
+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK);
+ }
+ }
+
+ private static boolean getBoolean(String key, boolean defaultValue, String... comment) {
+ return getBoolean(key, null, defaultValue, comment);
+ }
+
+ private static boolean getBoolean(String key, @Nullable String oldKey, boolean defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getBoolean(key, defaultValue);
+ }
+
+ private static int getInt(String key, int defaultValue, String... comment) {
+ return getInt(key, null, defaultValue, comment);
+ }
+
+ private static int getInt(String key, @Nullable String oldKey, int defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getInt(key, defaultValue);
+ }
+
+ private static double getDouble(String key, double defaultValue, String... comment) {
+ return getDouble(key, null, defaultValue, comment);
+ }
+
+ private static double getDouble(String key, @Nullable String oldKey, double defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getDouble(key, defaultValue);
+ }
+
+ private static String getString(String key, String defaultValue, String... comment) {
+ return getOldString(key, null, defaultValue, comment);
+ }
+
+ private static String getOldString(String key, @Nullable String oldKey, String defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getString(key, defaultValue);
+ }
+
+ private static List<String> getStringList(String key, List<String> defaultValue, String... comment) {
+ return getStringList(key, null, defaultValue, comment);
+ }
+
+ private static List<String> getStringList(String key, @Nullable String oldKey, List<String> defaultValue, String... comment) {
+ ensureDefault(key, defaultValue, comment);
+ return config.getStringList(key);
+ }
+
+ public static String sentryDsn;
+ private static void sentry() {
+ String sentryEnvironment = System.getenv("SENTRY_DSN");
+ String sentryConfig = getString("sentry-dsn", "", "Sentry DSN for improved error logging, leave blank to disable", "Obtain from https://sentry.io/");
+
+ sentryDsn = sentryEnvironment == null ? sentryConfig : sentryEnvironment;
+ if (sentryDsn != null && !sentryDsn.isBlank()) {
+ gg.pufferfish.pufferfish.sentry.SentryManager.init();
+ }
+ }
+
+ public static boolean enableBooks;
+ private static void books() {
+ enableBooks = getBoolean("enable-books", true,
+ "Whether or not books should be writeable.",
+ "Servers that anticipate being a target for duping may want to consider",
+ "disabling this option.",
+ "This can be overridden per-player with the permission pufferfish.usebooks");
+ }
+
+ public static boolean enableSuffocationOptimization;
+ private static void suffocationOptimization() {
+ enableSuffocationOptimization = getBoolean("enable-suffocation-optimization", true,
+ "Optimizes the suffocation check by selectively skipping",
+ "the check in a way that still appears vanilla. This should",
+ "be left enabled on most servers, but is provided as a",
+ "configuration option if the vanilla deviation is undesirable.");
+ }
+
+ public static boolean enableAsyncMobSpawning;
+ public static boolean asyncMobSpawningInitialized;
+ private static void asyncMobSpawning() {
+ boolean temp = getBoolean("enable-async-mob-spawning", true,
+ "Whether or not asynchronous mob spawning should be enabled.",
+ "On servers with many entities, this can improve performance by up to 15%. You must have",
+ "paper's per-player-mob-spawns setting set to true for this to work.",
+ "One quick note - this does not actually spawn mobs async (that would be very unsafe).",
+ "This just offloads some expensive calculations that are required for mob spawning.");
+
+ // This prevents us from changing the value during a reload.
+ if (!asyncMobSpawningInitialized) {
+ asyncMobSpawningInitialized = true;
+ enableAsyncMobSpawning = temp;
+ }
+ }
+
+ public static int maxProjectileLoadsPerTick;
+ public static int maxProjectileLoadsPerProjectile;
+ private static void projectileLoading() {
+ maxProjectileLoadsPerTick = getInt("projectile.max-loads-per-tick", 10, "Controls how many chunks are allowed", "to be sync loaded by projectiles in a tick.");
+ maxProjectileLoadsPerProjectile = getInt("projectile.max-loads-per-projectile", 10, "Controls how many chunks a projectile", "can load in its lifetime before it gets", "automatically removed.");
+
+ setComment("projectile", "Optimizes projectile settings");
+ }
+
+
+ public static boolean dearEnabled;
+ public static int startDistance;
+ public static int startDistanceSquared;
+ public static int maximumActivationPrio;
+ public static int activationDistanceMod;
+
+ private static void dynamicActivationOfBrains() throws IOException {
+ dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", true);
+ startDistance = getInt("dab.start-distance", "activation-range.start-distance", 12,
+ "This value determines how far away an entity has to be",
+ "from the player to start being effected by DEAR.");
+ startDistanceSquared = startDistance * startDistance;
+ maximumActivationPrio = getInt("dab.max-tick-freq", "activation-range.max-tick-freq", 20,
+ "This value defines how often in ticks, the furthest entity",
+ "will get their pathfinders and behaviors ticked. 20 = 1s");
+ activationDistanceMod = getInt("dab.activation-dist-mod", "activation-range.activation-dist-mod", 8,
+ "This value defines how much distance modifies an entity's",
+ "tick frequency. freq = (distanceToPlayer^2) / (2^value)",
+ "If you want further away entities to tick less often, use 7.",
+ "If you want further away entities to tick more often, try 9.");
+
+ for (EntityType<?> entityType : BuiltInRegistries.ENTITY_TYPE) {
+ entityType.dabEnabled = true; // reset all, before setting the ones to true
+ }
+ getStringList("dab.blacklisted-entities", "activation-range.blacklisted-entities", Collections.emptyList(), "A list of entities to ignore for activation")
+ .forEach(name -> EntityType.byString(name).ifPresentOrElse(entityType -> {
+ entityType.dabEnabled = false;
+ }, () -> MinecraftServer.LOGGER.warn("Unknown entity \"" + name + "\"")));
+
+ setComment("dab", "Optimizes entity brains when", "they're far away from the player");
+ }
+
+ public static Map<String, Integer> projectileTimeouts;
+ private static void projectileTimeouts() {
+ // Set some defaults
+ getInt("entity_timeouts.SNOWBALL", -1);
+ getInt("entity_timeouts.LLAMA_SPIT", -1);
+ setComment("entity_timeouts",
+ "These values define a entity's maximum lifespan. If an",
+ "entity is in this list and it has survived for longer than",
+ "that number of ticks, then it will be removed. Setting a value to",
+ "-1 disables this feature.");
+
+ for (EntityType<?> entityType : BuiltInRegistries.ENTITY_TYPE) {
+ String type = EntityType.getKey(entityType).getPath().toUpperCase(Locale.ROOT);
+ entityType.ttl = config.getInt("entity_timeouts." + type, -1);
+ }
+ }
+
+ public static boolean throttleInactiveGoalSelectorTick;
+ private static void inactiveGoalSelectorThrottle() {
+ throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", true,
+ "Throttles the AI goal selector in entity inactive ticks.",
+ "This can improve performance by a few percent, but has minor gameplay implications.");
+ }
+
+ public static boolean allowEndCrystalRespawn;
+ private static void allowEndCrystalRespawn() {
+ allowEndCrystalRespawn = getBoolean("allow-end-crystal-respawn", true,
+ "Allows end crystals to respawn the ender dragon.",
+ "On servers that expect end crystal fights in the end dimension, disabling this",
+ "will prevent the server from performing an expensive search to attempt respawning",
+ "the ender dragon whenever a player places an end crystal.");
+ }
+
+
+ public static boolean disableMethodProfiler;
+ public static boolean disableOutOfOrderChat;
+ private static void miscSettings() {
+ disableMethodProfiler = getBoolean("misc.disable-method-profiler", true);
+ disableOutOfOrderChat = getBoolean("misc.disable-out-of-order-chat", false);
+ setComment("misc", "Settings for things that don't belong elsewhere");
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java
new file mode 100644
index 0000000000000000000000000000000000000000..53f2df00c6809618a9ee3d2ea72e85e8052fbcf1
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java
@@ -0,0 +1,16 @@
+package gg.pufferfish.pufferfish;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
+
+public class PufferfishLogger extends Logger {
+ public static final PufferfishLogger LOGGER = new PufferfishLogger();
+
+ private PufferfishLogger() {
+ super("Pufferfish", null);
+
+ setParent(Bukkit.getLogger());
+ setLevel(Level.ALL);
+ }
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..893d8c0946ef71a0561221dd76bffff0dc940d56
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java
@@ -0,0 +1,136 @@
+package gg.pufferfish.pufferfish;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+import com.destroystokyo.paper.VersionHistoryManager;
+import com.destroystokyo.paper.util.VersionFetcher;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.JoinConfiguration;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.bukkit.craftbukkit.CraftServer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PufferfishVersionFetcher implements VersionFetcher {
+
+ private static final Logger LOGGER = Logger.getLogger("PufferfishVersionFetcher");
+ private static final HttpClient client = HttpClient.newHttpClient();
+
+ private static final URI JENKINS_URI = URI.create("https://ci.pufferfish.host/job/Pufferfish-1.20/lastSuccessfulBuild/buildNumber");
+ private static final String GITHUB_FORMAT = "https://api.github.com/repos/pufferfish-gg/Pufferfish/compare/ver/1.20...%s";
+
+ private static final HttpResponse.BodyHandler<JsonObject> JSON_OBJECT_BODY_HANDLER = responseInfo -> HttpResponse.BodySubscribers
+ .mapping(
+ HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8),
+ string -> new Gson().fromJson(string, JsonObject.class)
+ );
+
+ @Override
+ public long getCacheTime() {
+ return TimeUnit.MINUTES.toMillis(30);
+ }
+
+ @Override
+ public @NotNull Component getVersionMessage(final @NotNull String serverVersion) {
+ final String[] parts = CraftServer.class.getPackage().getImplementationVersion().split("-");
+ @NotNull Component component;
+
+ if (parts.length != 3) {
+ component = text("Unknown server version.", RED);
+ } else {
+ final String versionString = parts[2];
+
+ try {
+ component = this.fetchJenkinsVersion(Integer.parseInt(versionString));
+ } catch (NumberFormatException e) {
+ component = this.fetchGithubVersion(versionString.substring(1, versionString.length() - 1));
+ }
+ }
+
+ final @Nullable Component history = this.getHistory();
+ return history != null ? Component
+ .join(JoinConfiguration.noSeparators(), component, Component.newline(), this.getHistory()) : component;
+ }
+
+ private @NotNull Component fetchJenkinsVersion(final int versionNumber) {
+ final HttpRequest request = HttpRequest.newBuilder(JENKINS_URI).build();
+ try {
+ final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() != 200) {
+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED);
+ }
+
+ int latestVersionNumber;
+ try {
+ latestVersionNumber = Integer.parseInt(response.body());
+ } catch (NumberFormatException e) {
+ LOGGER.log(Level.WARNING, "Received invalid response from Jenkins \"" + response.body() + "\".");
+ return text("Received invalid response from server.", RED);
+ }
+
+ final int versionDiff = latestVersionNumber - versionNumber;
+ return this.getResponseMessage(versionDiff);
+ } catch (IOException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Failed to look up version from Jenkins", e);
+ return text("Failed to retrieve version from server.", RED);
+ }
+ }
+
+ // Based off code contributed by Techcable <Techcable@outlook.com> in Paper/GH-65
+ private @NotNull Component fetchGithubVersion(final @NotNull String hash) {
+ final URI uri = URI.create(String.format(GITHUB_FORMAT, hash));
+ final HttpRequest request = HttpRequest.newBuilder(uri).build();
+ try {
+ final HttpResponse<JsonObject> response = client.send(request, JSON_OBJECT_BODY_HANDLER);
+ if (response.statusCode() != 200) {
+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED);
+ }
+
+ final JsonObject obj = response.body();
+ final int versionDiff = obj.get("behind_by").getAsInt();
+
+ return this.getResponseMessage(versionDiff);
+ } catch (IOException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Failed to look up version from GitHub", e);
+ return text("Failed to retrieve version from server.", RED);
+ }
+ }
+
+ private @NotNull Component getResponseMessage(final int versionDiff) {
+ return switch (Math.max(-1, Math.min(1, versionDiff))) {
+ case -1 -> text("You are running an unsupported version of Pufferfish.", RED);
+ case 0 -> text("You are on the latest version!", GREEN);
+ default -> text("You are running " + versionDiff + " version" + (versionDiff == 1 ? "" : "s") + " beyond. " +
+ "Please update your server when possible to maintain stability, security, and receive the latest optimizations.",
+ RED);
+ };
+ }
+
+ private @Nullable Component getHistory() {
+ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
+ if (data == null) {
+ return null;
+ }
+
+ final String oldVersion = data.getOldVersion();
+ if (oldVersion == null) {
+ return null;
+ }
+
+ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
new file mode 100644
index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834733d0621
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
@@ -0,0 +1,135 @@
+package gg.pufferfish.pufferfish.sentry;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import io.sentry.Breadcrumb;
+import io.sentry.Sentry;
+import io.sentry.SentryEvent;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.Message;
+import io.sentry.protocol.User;
+import java.util.Map;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.filter.AbstractFilter;
+
+public class PufferfishSentryAppender extends AbstractAppender {
+
+ private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class);
+ private static final Gson GSON = new Gson();
+
+ public PufferfishSentryAppender() {
+ super("PufferfishSentryAdapter", new SentryFilter(), null);
+ }
+
+ @Override
+ public void append(LogEvent logEvent) {
+ if (logEvent.getThrown() != null && logEvent.getLevel().isMoreSpecificThan(Level.WARN)) {
+ try {
+ logException(logEvent);
+ } catch (Exception e) {
+ logger.warn("Failed to log event with sentry", e);
+ }
+ } else {
+ try {
+ logBreadcrumb(logEvent);
+ } catch (Exception e) {
+ logger.warn("Failed to log event with sentry", e);
+ }
+ }
+ }
+
+ private void logException(LogEvent e) {
+ SentryEvent event = new SentryEvent(e.getThrown());
+
+ Message sentryMessage = new Message();
+ sentryMessage.setMessage(e.getMessage().getFormattedMessage());
+
+ event.setThrowable(e.getThrown());
+ event.setLevel(getLevel(e.getLevel()));
+ event.setLogger(e.getLoggerName());
+ event.setTransaction(e.getLoggerName());
+ event.setExtra("thread_name", e.getThreadName());
+
+ boolean hasContext = e.getContextData() != null;
+
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) {
+ User user = new User();
+ user.setId(e.getContextData().getValue("pufferfishsentry_playerid"));
+ user.setUsername(e.getContextData().getValue("pufferfishsentry_playername"));
+ event.setUser(user);
+ }
+
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) {
+ event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname"));
+ event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion"));
+ event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname"));
+ }
+
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) {
+ Map<String, String> eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken<Map<String, String>>() {}.getType());
+ if (eventFields != null) {
+ event.setExtra("event", eventFields);
+ }
+ }
+
+ Sentry.captureEvent(event);
+ }
+
+ private void logBreadcrumb(LogEvent e) {
+ Breadcrumb breadcrumb = new Breadcrumb();
+
+ breadcrumb.setLevel(getLevel(e.getLevel()));
+ breadcrumb.setCategory(e.getLoggerName());
+ breadcrumb.setType(e.getLoggerName());
+ breadcrumb.setMessage(e.getMessage().getFormattedMessage());
+
+ Sentry.addBreadcrumb(breadcrumb);
+ }
+
+ private SentryLevel getLevel(Level level) {
+ switch (level.getStandardLevel()) {
+ case TRACE:
+ case DEBUG:
+ return SentryLevel.DEBUG;
+ case WARN:
+ return SentryLevel.WARNING;
+ case ERROR:
+ return SentryLevel.ERROR;
+ case FATAL:
+ return SentryLevel.FATAL;
+ case INFO:
+ default:
+ return SentryLevel.INFO;
+ }
+ }
+
+ private static class SentryFilter extends AbstractFilter {
+
+ @Override
+ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg,
+ Object... params) {
+ return this.filter(logger.getName());
+ }
+
+ @Override
+ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) {
+ return this.filter(logger.getName());
+ }
+
+ @Override
+ public Result filter(LogEvent event) {
+ return this.filter(event == null ? null : event.getLoggerName());
+ }
+
+ private Result filter(String loggerName) {
+ return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY
+ : Result.NEUTRAL;
+ }
+
+ }
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b29210ad0bbb4ada150f23357f0c80d331c996d
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java
@@ -0,0 +1,40 @@
+package gg.pufferfish.pufferfish.sentry;
+
+import gg.pufferfish.pufferfish.PufferfishConfig;
+import io.sentry.Sentry;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class SentryManager {
+
+ private static final Logger logger = LogManager.getLogger(SentryManager.class);
+
+ private SentryManager() {
+
+ }
+
+ private static boolean initialized = false;
+
+ public static synchronized void init() {
+ if (initialized) {
+ return;
+ }
+ try {
+ initialized = true;
+
+ Sentry.init(options -> {
+ options.setDsn(PufferfishConfig.sentryDsn);
+ options.setMaxBreadcrumbs(100);
+ });
+
+ PufferfishSentryAppender appender = new PufferfishSentryAppender();
+ appender.start();
+ ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender);
+ logger.info("Sentry logging started!");
+ } catch (Exception e) {
+ logger.warn("Failed to initialize sentry!", e);
+ initialized = false;
+ }
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e5323d5d9af25c8a85c4b34a6be76cfc54384cf
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java
@@ -0,0 +1,73 @@
+package gg.pufferfish.pufferfish.util;
+
+import com.google.common.collect.Queues;
+import gg.pufferfish.pufferfish.PufferfishLogger;
+import java.util.Queue;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+
+public class AsyncExecutor implements Runnable {
+
+ private final Queue<Runnable> jobs = Queues.newArrayDeque();
+ private final Lock mutex = new ReentrantLock();
+ private final Condition cond = mutex.newCondition();
+ private final Thread thread;
+ private volatile boolean killswitch = false;
+
+ public AsyncExecutor(String threadName) {
+ this.thread = new Thread(this, threadName);
+ }
+
+ public void start() {
+ thread.start();
+ }
+
+ public void kill() {
+ killswitch = true;
+ cond.signalAll();
+ }
+
+ public void submit(Runnable runnable) {
+ mutex.lock();
+ try {
+ jobs.offer(runnable);
+ cond.signalAll();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ @Override
+ public void run() {
+ while (!killswitch) {
+ try {
+ Runnable runnable = takeRunnable();
+ if (runnable != null) {
+ runnable.run();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (Exception e) {
+ PufferfishLogger.LOGGER.log(Level.SEVERE, e, () -> "Failed to execute async job for thread " + thread.getName());
+ }
+ }
+ }
+
+ private Runnable takeRunnable() throws InterruptedException {
+ mutex.lock();
+ try {
+ while (jobs.isEmpty() && !killswitch) {
+ cond.await();
+ }
+
+ if (jobs.isEmpty()) return null; // We've set killswitch
+
+ return jobs.remove();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/util/AsyncPlayerAreaMap.java b/src/main/java/gg/pufferfish/pufferfish/util/AsyncPlayerAreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdcb62d12164024a5f354d60cc863821a18d1b2a
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/util/AsyncPlayerAreaMap.java
@@ -0,0 +1,31 @@
+package gg.pufferfish.pufferfish.util;
+
+import com.destroystokyo.paper.util.misc.PlayerAreaMap;
+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
+import java.util.concurrent.ConcurrentHashMap;
+import net.minecraft.server.level.ServerPlayer;
+
+public final class AsyncPlayerAreaMap extends PlayerAreaMap {
+
+ public AsyncPlayerAreaMap() {
+ super();
+ this.areaMap = new Long2ObjectOpenHashMapWrapper<>(new ConcurrentHashMap<>(1024, 0.7f));
+ }
+
+ public AsyncPlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets) {
+ super(pooledHashSets);
+ this.areaMap = new Long2ObjectOpenHashMapWrapper<>(new ConcurrentHashMap<>(1024, 0.7f));
+ }
+
+ public AsyncPlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+ final ChangeCallback<ServerPlayer> removeCallback) {
+ this(pooledHashSets, addCallback, removeCallback, null);
+ }
+
+ public AsyncPlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+ final ChangeCallback<ServerPlayer> removeCallback, final ChangeSourceCallback<ServerPlayer> changeSourceCallback) {
+ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback);
+ this.areaMap = new Long2ObjectOpenHashMapWrapper<>(new ConcurrentHashMap<>(1024, 0.7f));
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1929840254a3e6d721816f4a20415bea1742580
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java
@@ -0,0 +1,20 @@
+package gg.pufferfish.pufferfish.util;
+
+import java.util.Iterator;
+import org.jetbrains.annotations.NotNull;
+
+public class IterableWrapper<T> implements Iterable<T> {
+
+ private final Iterator<T> iterator;
+
+ public IterableWrapper(Iterator<T> iterator) {
+ this.iterator = iterator;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<T> iterator() {
+ return iterator;
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..facd55463d44cb7e3d2ca6892982f5497b8dded1
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java
@@ -0,0 +1,40 @@
+package gg.pufferfish.pufferfish.util;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import java.util.Map;
+import org.jetbrains.annotations.Nullable;
+
+public class Long2ObjectOpenHashMapWrapper<V> extends Long2ObjectOpenHashMap<V> {
+
+ private final Map<Long, V> backingMap;
+
+ public Long2ObjectOpenHashMapWrapper(Map<Long, V> map) {
+ backingMap = map;
+ }
+
+ @Override
+ public V put(Long key, V value) {
+ return backingMap.put(key, value);
+ }
+
+ @Override
+ public V get(Object key) {
+ return backingMap.get(key);
+ }
+
+ @Override
+ public V remove(Object key) {
+ return backingMap.remove(key);
+ }
+
+ @Nullable
+ @Override
+ public V putIfAbsent(Long key, V value) {
+ return backingMap.putIfAbsent(key, value);
+ }
+
+ @Override
+ public int size() {
+ return backingMap.size();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
index 8240bb085b619f257f8c0a25775e0b15068e440f..6d9668d993bb922ae9d2b76a4d766903cc3f98a4 100644
--- a/src/main/java/io/papermc/paper/util/MCUtil.java
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
@@ -213,7 +213,7 @@ public final class MCUtil {
}
public static long getCoordinateKey(final Entity entity) {
- return ((long)(MCUtil.fastFloor(entity.getZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.getX()) >> 4) & 0xFFFFFFFFL);
+ return ((long)(entity.blockPosition.getZ() >> 4) << 32) | ((entity.blockPosition.getX() >> 4) & 0xFFFFFFFFL); // Pufferfish - eliminate double->long cast in hotpath
}
public static long getCoordinateKey(final ChunkPos pair) {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 8f31413c939cc2b0454ad3d9a1b618dbae449d00..58d076e2a8fa1cf56c4c8d15a502e85fcf48aa90 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -308,6 +308,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper start - lag compensation
public static final long SERVER_INIT = System.nanoTime();
// Paper end - lag compensation
+
+ public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
AtomicReference<S> atomicreference = new AtomicReference();
@@ -1697,7 +1699,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@DontObfuscate
public String getServerModName() {
- return "Paper"; // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
+ return "Pufferfish"; // Pufferfish - Pufferfish > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
}
public SystemReport fillSystemReport(SystemReport details) {
@@ -2272,6 +2274,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public ProfilerFiller getProfiler() {
+ if (gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE;
return this.profiler;
}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 1c9742ad81f04052d2c3bc18c7636f45b2fc5160..e5c955e7e99bc07199feabfe9f0301016ad38b84 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -221,6 +221,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
// Paper end
+ gg.pufferfish.pufferfish.PufferfishConfig.load(); // Pufferfish
+ gg.pufferfish.pufferfish.PufferfishCommand.init(); // Pufferfish
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
@@ -338,6 +340,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
DedicatedServer.LOGGER.info("JMX monitoring enabled");
}
+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish
return true;
}
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index caa73632aee15583c6b6ed12a668c8f49b794708..3abec84383a445d3ad0d3b5f613246b6ac7ee741 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -244,7 +244,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
// Paper start - optimise chunk tick iteration
public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new gg.pufferfish.pufferfish.util.AsyncPlayerAreaMap(this.pooledLinkedPlayerHashSets); // Pufferfish
// Paper end - optimise chunk tick iteration
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
@@ -1464,8 +1464,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return ChunkMap.this.level.getServer().getScaledTrackingDistance(initialDistance);
}
+ private static int getHighestRange(Entity parent, int highest) {
+ List<Entity> passengers = parent.getPassengers();
+
+ for (int i = 0, size = passengers.size(); i < size; i++) {
+ Entity entity = passengers.get(i);
+ int range = entity.getType().clientTrackingRange() * 16;
+ range = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, range); // Paper
+
+ if (range > highest) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic // Tuinity - not anymore!
+ highest = range;
+ }
+
+ highest = getHighestRange(entity, highest);
+ }
+
+ return highest;
+ }
+
private int getEffectiveRange() {
int i = this.range;
+ // Pufferfish start - remove iterators and streams
+ /*
Iterator iterator = this.entity.getIndirectPassengers().iterator();
while (iterator.hasNext()) {
@@ -1477,6 +1497,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
i = j;
}
}
+ */
+ i = getHighestRange(this.entity, i);
+ // Pufferfish end
return this.scaledRange(i);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 8c33a12ca879c46893150d6adfb8aa4d397c6b4c..5d26364c0f4ed03bd9994077683c93b9883e5327 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -75,6 +75,9 @@ public class ServerChunkCache extends ChunkSource {
final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<LevelChunk> loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f);
private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4];
+
+ public boolean firstRunSpawnCounts = true; // Pufferfish
+ public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs
private static int getChunkCacheKey(int x, int z) {
return x & 3 | ((z & 3) << 2);
@@ -521,6 +524,7 @@ public class ServerChunkCache extends ChunkSource {
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
gameprofilerfiller.push("pollingChunks");
+ this.level.resetIceAndSnowTick(); // Pufferfish - reset ice & snow tick random
int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && worlddata.getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
@@ -530,28 +534,35 @@ public class ServerChunkCache extends ChunkSource {
// Paper start - per player mob spawning
NaturalSpawner.SpawnState spawnercreature_d; // moved down
if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
- // re-set mob counts
- for (ServerPlayer player : this.level.players) {
- // Paper start - per player mob spawning backoff
- for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
- player.mobCounts[ii] = 0;
-
- int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm?
- if (newBackoff < 0) {
- newBackoff = 0;
+ // Pufferfish start - moved down when async processing
+ if (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) {
+ // re-set mob counts
+ for (ServerPlayer player : this.level.players) {
+ // Paper start - per player mob spawning backoff
+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
+ player.mobCounts[ii] = 0;
+
+ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm?
+ if (newBackoff < 0) {
+ newBackoff = 0;
+ }
+ player.mobBackoffCounts[ii] = newBackoff;
}
- player.mobBackoffCounts[ii] = newBackoff;
+ // Paper end - per player mob spawning backoff
}
- // Paper end - per player mob spawning backoff
+ lastSpawnState = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true);
}
- spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true);
+ // Pufferfish end
} else {
- spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ // Pufferfish start
+ lastSpawnState = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ _pufferfish_spawnCountsReady.set(true);
+ // Pufferfish end
}
// Paper end
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
- this.lastSpawnState = spawnercreature_d;
+ //this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously
gameprofilerfiller.popPush("filteringLoadedChunks");
// Paper - optimise chunk tick iteration
// Paper - optimise chunk tick iteration
@@ -644,8 +655,8 @@ public class ServerChunkCache extends ChunkSource {
// Paper end - optimise chunk tick iteration
if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { // Paper - optimise chunk tick iteration
chunk1.incrementInhabitedTime(j);
- if (spawn && flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ if (spawn && flag2 && (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning || _pufferfish_spawnCountsReady.get()) && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration // Pufferfish
+ NaturalSpawner.spawnForChunk(this.level, chunk1, lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1); // Pufferfish
}
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
@@ -691,6 +702,40 @@ public class ServerChunkCache extends ChunkSource {
gameprofilerfiller.pop();
this.chunkMap.tick();
}
+
+ // Pufferfish start - optimize mob spawning
+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) {
+ for (ServerPlayer player : this.level.players) {
+ // Paper start - per player mob spawning backoff
+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
+ player.mobCounts[ii] = 0;
+
+ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm?
+ if (newBackoff < 0) {
+ newBackoff = 0;
+ }
+ player.mobBackoffCounts[ii] = newBackoff;
+ }
+ // Paper end - per player mob spawning backoff
+ }
+ if (firstRunSpawnCounts) {
+ firstRunSpawnCounts = false;
+ _pufferfish_spawnCountsReady.set(true);
+ }
+ if (_pufferfish_spawnCountsReady.getAndSet(false)) {
+ net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> {
+ int mapped = distanceManager.getNaturalSpawnChunkCount();
+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Entity> objectiterator =
+ level.entityTickList.entities.iterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS);
+ gg.pufferfish.pufferfish.util.IterableWrapper<Entity> wrappedIterator =
+ new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator);
+ lastSpawnState = NaturalSpawner.createState(mapped, wrappedIterator, this::getFullChunk, null, true);
+ objectiterator.finishedIterating();
+ _pufferfish_spawnCountsReady.set(true);
+ });
+ }
+ }
+ // Pufferfish end
}
private void getFullChunk(long pos, Consumer<LevelChunk> chunkConsumer) {
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 35674f92a67f93382103c2766df4b678ba5c862f..d46e61640b241d32df05240dedd2c23f138725e6 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -177,7 +177,8 @@ public class ServerEntity {
long i1 = this.positionCodec.encodeZ(vec3d);
boolean flag6 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
- if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()&& !(io.papermc.paper.configuration.GlobalConfiguration.get().collisions.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync
+ if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround() && !(io.papermc.paper.configuration.GlobalConfiguration.get().collisions.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync
+ if (flag2 || flag3 || this.entity instanceof AbstractArrow) { // Pufferfish
if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
if (flag2) {
packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.onGround());
@@ -191,6 +192,7 @@ public class ServerEntity {
flag4 = true;
flag5 = true;
}
+ } // Pufferfish
} else {
this.wasOnGround = this.entity.onGround();
this.teleportDelay = 0;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index c88d5b9125f6ee43bf2be60fd1745d836f271b78..945783d090e44ebed1d4968c1d1ec0b68f6d494f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -878,6 +878,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
this.timings.entityTick.startTiming(); // Spigot
this.entityTickList.forEach((entity) -> {
+ entity.activatedPriorityReset = false; // Pufferfish - DAB
if (!entity.isRemoved()) {
if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
entity.discard();
@@ -897,7 +898,20 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
gameprofilerfiller.push("tick");
- this.guardEntityTick(this::tickNonPassenger, entity);
+ // Pufferfish start - copied from this.guardEntityTick
+ try {
+ this.tickNonPassenger(entity); // Pufferfish - changed
+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
+ // Paper start - Prevent tile entity and entity crashes
+ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
+ MinecraftServer.LOGGER.error(msg, throwable);
+ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable)));
+ entity.discard();
+ // Paper end
+ }
+ // Pufferfish end
gameprofilerfiller.pop();
}
}
@@ -962,9 +976,11 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
// Paper start - optimise random block ticking
private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
- private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong());
+ // private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(); // Pufferfish - moved to super
// Paper end
+ private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // Pufferfish
+
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
ChunkPos chunkcoordintpair = chunk.getPos();
boolean flag = this.isRaining();
@@ -975,7 +991,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
gameprofilerfiller.push("thunder");
final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
- if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - disable thunder
+ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && /*this.random.nextInt(this.spigotConfig.thunderChance) == 0 &&*/ chunk.shouldDoLightning(this.random)) { // Spigot // Paper - disable thunder // Pufferfish - replace random with shouldDoLightning
blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
if (this.isRainingAt(blockposition)) {
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 65bb221993147a558995b36fb835f7b82e0eb4bd..6cc9271ba058f4af759eae34e2f6e9f892b4f6da 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1119,6 +1119,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
@Override
public void handleEditBook(ServerboundEditBookPacket packet) {
+ if (!gg.pufferfish.pufferfish.PufferfishConfig.enableBooks && !this.player.getBukkitEntity().hasPermission("pufferfish.usebooks")) return; // Pufferfish
// Paper start
if (!this.cserver.isPrimaryThread()) {
List<String> pageList = packet.getPages();
@@ -2274,6 +2275,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
}
private boolean updateChatOrder(Instant timestamp) {
+ if (gg.pufferfish.pufferfish.PufferfishConfig.disableOutOfOrderChat) return true;
Instant instant1;
do {
diff --git a/src/main/java/net/minecraft/world/CompoundContainer.java b/src/main/java/net/minecraft/world/CompoundContainer.java
index 241fec02e6869c638d3a160819b32173a081467b..6a8f9e8f5bf108674c47018def28906e2d0a729c 100644
--- a/src/main/java/net/minecraft/world/CompoundContainer.java
+++ b/src/main/java/net/minecraft/world/CompoundContainer.java
@@ -1,5 +1,6 @@
package net.minecraft.world;
+import net.minecraft.core.Direction; // Pufferfish
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@@ -64,6 +65,23 @@ public class CompoundContainer implements Container {
this.container2 = second;
}
+ // Pufferfish start
+ @Override
+ public boolean hasEmptySlot(Direction enumdirection) {
+ return this.container1.hasEmptySlot(null) || this.container2.hasEmptySlot(null);
+ }
+
+ @Override
+ public boolean isCompletelyFull(Direction enumdirection) {
+ return this.container1.isCompletelyFull(null) && this.container2.isCompletelyFull(null);
+ }
+
+ @Override
+ public boolean isCompletelyEmpty(Direction enumdirection) {
+ return this.container1.isCompletelyEmpty(null) && this.container2.isCompletelyEmpty(null);
+ }
+ // Pufferfish end
+
@Override
public int getContainerSize() {
return this.container1.getContainerSize() + this.container2.getContainerSize();
diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java
index d6cbe98e67fdbf8db46338a88ab1356dd63b50a3..20dd3a63b2f955b05a75eb240e33ae4cf6aef28f 100644
--- a/src/main/java/net/minecraft/world/Container.java
+++ b/src/main/java/net/minecraft/world/Container.java
@@ -3,6 +3,8 @@ package net.minecraft.world;
import java.util.Set;
import java.util.function.Predicate;
import net.minecraft.core.BlockPos;
+
+import net.minecraft.core.Direction; // Pufferfish
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@@ -14,6 +16,63 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity;
// CraftBukkit end
public interface Container extends Clearable {
+ // Pufferfish start - allow the inventory to override and optimize these frequent calls
+ default boolean hasEmptySlot(@org.jetbrains.annotations.Nullable Direction enumdirection) { // there is a slot with 0 items in it
+ if (this instanceof WorldlyContainer worldlyContainer) {
+ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
+ if (this.getItem(i).isEmpty()) {
+ return true;
+ }
+ }
+ } else {
+ int size = this.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ if (this.getItem(i).isEmpty()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ default boolean isCompletelyFull(@org.jetbrains.annotations.Nullable Direction enumdirection) { // every stack is maxed
+ if (this instanceof WorldlyContainer worldlyContainer) {
+ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
+ ItemStack itemStack = this.getItem(i);
+ if (itemStack.getCount() < itemStack.getMaxStackSize()) {
+ return false;
+ }
+ }
+ } else {
+ int size = this.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ ItemStack itemStack = this.getItem(i);
+ if (itemStack.getCount() < itemStack.getMaxStackSize()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ default boolean isCompletelyEmpty(@org.jetbrains.annotations.Nullable Direction enumdirection) {
+ if (this instanceof WorldlyContainer worldlyContainer) {
+ for (int i : worldlyContainer.getSlotsForFace(enumdirection)) {
+ if (!this.getItem(i).isEmpty()) {
+ return false;
+ }
+ }
+ } else {
+ int size = this.getContainerSize();
+ for (int i = 0; i < size; i++) {
+ if (!this.getItem(i).isEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ // Pufferfish end
int LARGE_MAX_STACK_SIZE = 64;
int DEFAULT_DISTANCE_LIMIT = 8;
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index fece91254b10b59474056aa730fd420f90cd7bec..80c9a523e94704f73e833e2255af6b0bf55da454 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -306,7 +306,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
public double yo;
public double zo;
private Vec3 position;
- private BlockPos blockPosition;
+ public BlockPos blockPosition; // Pufferfish - private->public
private ChunkPos chunkPosition;
private Vec3 deltaMovement;
private float yRot;
@@ -434,6 +434,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return this.originWorld;
}
// Paper end
+ // Pufferfish start
+ public boolean activatedPriorityReset = false; // DAB
+ public int activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; // golf score
+ public final BlockPos.MutableBlockPos cachedBlockPos = new BlockPos.MutableBlockPos(); // used where needed
+ // Pufferfish end
+
public float getBukkitYaw() {
return this.yRot;
}
@@ -798,6 +804,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
public void tick() {
+ // Pufferfish start - entity TTL
+ if (type != EntityType.PLAYER && type.ttl >= 0 && this.tickCount >= type.ttl) {
+ discard();
+ return;
+ }
+ // Pufferfish end - entity TTL
this.baseTick();
}
@@ -4362,16 +4374,18 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
public boolean updateFluidHeightAndDoFluidPushing(TagKey<Fluid> tag, double speed) {
- if (this.touchingUnloadedChunk()) {
+ if (false && this.touchingUnloadedChunk()) { // Pufferfish - cost of a lookup here is the same cost as below, so skip
return false;
} else {
AABB axisalignedbb = this.getBoundingBox().deflate(0.001D);
- int i = Mth.floor(axisalignedbb.minX);
- int j = Mth.ceil(axisalignedbb.maxX);
- int k = Mth.floor(axisalignedbb.minY);
- int l = Mth.ceil(axisalignedbb.maxY);
- int i1 = Mth.floor(axisalignedbb.minZ);
- int j1 = Mth.ceil(axisalignedbb.maxZ);
+ // Pufferfish start - rename
+ int minBlockX = Mth.floor(axisalignedbb.minX);
+ int maxBlockX = Mth.ceil(axisalignedbb.maxX);
+ int minBlockY = Mth.floor(axisalignedbb.minY);
+ int maxBlockY = Mth.ceil(axisalignedbb.maxY);
+ int minBlockZ = Mth.floor(axisalignedbb.minZ);
+ int maxBlockZ = Mth.ceil(axisalignedbb.maxZ);
+ // Pufferfish end
double d1 = 0.0D;
boolean flag = this.isPushedByFluid();
boolean flag1 = false;
@@ -4379,14 +4393,61 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
int k1 = 0;
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- for (int l1 = i; l1 < j; ++l1) {
- for (int i2 = k; i2 < l; ++i2) {
- for (int j2 = i1; j2 < j1; ++j2) {
- blockposition_mutableblockposition.set(l1, i2, j2);
- FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
+ // Pufferfish start - based off CollisionUtil.getCollisionsForBlocksOrWorldBorder
+ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this.level());
+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxSection(this.level());
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return false;
+ }
+
+ int minYIterate = Math.max(minBlock, minBlockY);
+ int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ int minChunkX = minBlockX >> 4;
+ int maxChunkX = maxBlockX >> 4;
+
+ int minChunkZ = minBlockZ >> 4;
+ int maxChunkZ = maxBlockZ >> 4;
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 16; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 16; // coordinate in chunk
+
+ net.minecraft.world.level.chunk.ChunkAccess chunk = this.level().getChunkIfLoadedImmediately(currChunkX, currChunkZ);
+ if (chunk == null) {
+ return false; // if we're touching an unloaded chunk then it's false
+ }
+
+ net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
+
+ for (int currY = minYIterate; currY < maxYIterate; ++currY) {
+ net.minecraft.world.level.chunk.LevelChunkSection section = sections[(currY >> 4) - minSection];
+
+ if (section == null || section.hasOnlyAir() || section.fluidStateCount == 0) { // if no fluids, nothing in this section
+ // empty
+ // skip to next section
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
+ continue;
+ }
+
+ net.minecraft.world.level.chunk.PalettedContainer<BlockState> blocks = section.states;
+
+ for (int currZ = minZ; currZ < maxZ; ++currZ) {
+ for (int currX = minX; currX < maxX; ++currX) {
+ FluidState fluid = blocks.get(currX & 15, currY & 15, currZ & 15).getFluidState();
if (fluid.is(tag)) {
- double d2 = (double) ((float) i2 + fluid.getHeight(this.level(), blockposition_mutableblockposition));
+ blockposition_mutableblockposition.set((currChunkX << 4) + currX, currY, (currChunkZ << 4) + currZ);
+ double d2 = (double) ((float) currY + fluid.getHeight(this.level(), blockposition_mutableblockposition));
if (d2 >= axisalignedbb.minY) {
flag1 = true;
@@ -4408,9 +4469,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
// CraftBukkit end
}
}
+ }
+ }
}
}
}
+ // Pufferfish end
if (vec3d.length() > 0.0D) {
if (k1 > 0) {
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
index 73871f456a85bda1e51f54986d0e61fb629822e8..2561e74ffdf595a9b6ae13dcd738662c772db442 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -301,6 +301,8 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
private final boolean canSpawnFarFromPlayer;
private final int clientTrackingRange;
private final int updateInterval;
+ public boolean dabEnabled = false; // Pufferfish
+ public int ttl = -1; // Pufferfish
@Nullable
private String descriptionId;
@Nullable
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 15e1d8c09fad181406a6acb8b3f177cd5e6c0f52..3b9ee3324a084271862ed790e8fc0d469e877ec1 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -142,7 +142,6 @@ import org.bukkit.event.entity.EntityTeleportEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
// CraftBukkit end
-import co.aikar.timings.MinecraftTimings; // Paper
public abstract class LivingEntity extends Entity implements Attackable {
@@ -414,7 +413,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
boolean flag = this instanceof net.minecraft.world.entity.player.Player;
if (!this.level().isClientSide) {
- if (this.isInWall()) {
+ if (shouldCheckForSuffocation() && this.isInWall()) { // Pufferfish - optimize suffocation
this.hurt(this.damageSources().inWall(), 1.0F);
} else if (flag && !this.level().getWorldBorder().isWithinBounds(this.getBoundingBox())) {
double d0 = this.level().getWorldBorder().getDistanceToBorder(this) + this.level().getWorldBorder().getDamageSafeZone();
@@ -1409,6 +1408,19 @@ public abstract class LivingEntity extends Entity implements Attackable {
return this.getHealth() <= 0.0F;
}
+ // Pufferfish start - optimize suffocation
+ public boolean couldPossiblyBeHurt(float amount) {
+ if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && amount <= this.lastHurt) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean shouldCheckForSuffocation() {
+ return !gg.pufferfish.pufferfish.PufferfishConfig.enableSuffocationOptimization || (tickCount % 10 == 0 && couldPossiblyBeHurt(1.0F));
+ }
+ // Pufferfish end
+
@Override
public boolean hurt(DamageSource source, float amount) {
if (this.isInvulnerableTo(source)) {
@@ -2004,6 +2016,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
return this.lastClimbablePos;
}
+
+ // Pufferfish start
+ private boolean cachedOnClimable = false;
+ private BlockPos lastClimbingPosition = null;
+
+ public boolean onClimableCached() {
+ if (!this.blockPosition().equals(this.lastClimbingPosition)) {
+ this.cachedOnClimable = this.onClimbable();
+ this.lastClimbingPosition = this.blockPosition();
+ }
+ return this.cachedOnClimable;
+ }
+ // Pufferfish end
+
public boolean onClimbable() {
if (this.isSpectator()) {
return false;
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 956d05e2ae59978ea9623ca0e167c0afe0b87306..944c22ea172796492a683d2f2bddfb0938d7a8c9 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -220,14 +220,16 @@ public abstract class Mob extends LivingEntity implements Targeting {
return this.lookControl;
}
+ int _pufferfish_inactiveTickDisableCounter = 0; // Pufferfish - throttle inactive goal selector ticking
// Paper start
@Override
public void inactiveTick() {
super.inactiveTick();
- if (this.goalSelector.inactiveTick()) {
+ boolean isThrottled = gg.pufferfish.pufferfish.PufferfishConfig.throttleInactiveGoalSelectorTick && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking
+ if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking
this.goalSelector.tick();
}
- if (this.targetSelector.inactiveTick()) {
+ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority
this.targetSelector.tick();
}
}
@@ -911,16 +913,20 @@ public abstract class Mob extends LivingEntity implements Targeting {
if (i % 2 != 0 && this.tickCount > 1) {
this.level().getProfiler().push("targetSelector");
+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.targetSelector.tickRunningGoals(false);
this.level().getProfiler().pop();
this.level().getProfiler().push("goalSelector");
+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.goalSelector.tickRunningGoals(false);
this.level().getProfiler().pop();
} else {
this.level().getProfiler().push("targetSelector");
+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.targetSelector.tick();
this.level().getProfiler().pop();
this.level().getProfiler().push("goalSelector");
+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.goalSelector.tick();
this.level().getProfiler().pop();
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
index 7204b973c3ad9239e82355513f6d538107102e48..3087f8359b098682a345399c85395de8a15b6eed 100644
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -23,9 +23,11 @@ public class AttributeMap {
private final Map<Attribute, AttributeInstance> attributes = Maps.newHashMap();
private final Set<AttributeInstance> dirtyAttributes = Sets.newHashSet();
private final AttributeSupplier supplier;
+ private final java.util.function.Function<Attribute, AttributeInstance> createInstance; // Pufferfish
public AttributeMap(AttributeSupplier defaultAttributes) {
this.supplier = defaultAttributes;
+ this.createInstance = attribute -> this.supplier.createInstance(this::onAttributeModified, attribute); // Pufferfish
}
private void onAttributeModified(AttributeInstance instance) {
@@ -45,11 +47,10 @@ public class AttributeMap {
}).collect(Collectors.toList());
}
+
@Nullable
public AttributeInstance getInstance(Attribute attribute) {
- return this.attributes.computeIfAbsent(attribute, (attributex) -> {
- return this.supplier.createInstance(this::onAttributeModified, attributex);
- });
+ return this.attributes.computeIfAbsent(attribute, this.createInstance); // Pufferfish - cache lambda, as for some reason java allocates it anyways
}
@Nullable
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java
index 646d9a121d908a2fc3e4e302484dd5cd1bfc6804..e546ecdccde352502e26a8668eaaafe048d6e282 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java
@@ -37,7 +37,11 @@ public class VillagerPanicTrigger extends Behavior<Villager> {
@Override
protected void tick(ServerLevel world, Villager entity, long time) {
- if (time % 100L == 0L) {
+ // Pufferfish start
+ if (entity.nextGolemPanic < 0) entity.nextGolemPanic = time + 100;
+ if (--entity.nextGolemPanic < time) {
+ entity.nextGolemPanic = -1;
+ // Pufferfish end
entity.spawnGolemIfNeeded(world, time, 3);
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
index b738ee2d3801fadfd09313f05ae24593e56b0ec6..1635818fc4b1788c0d397085239df6dd75b210ab 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
@@ -53,9 +53,12 @@ public class GoalSelector {
}
// Paper start
- public boolean inactiveTick() {
+ public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start
+ if (inactive && !gg.pufferfish.pufferfish.PufferfishConfig.dearEnabled) tickRate = 4; // reset to Paper's
+ tickRate = Math.min(tickRate, this.newGoalRate);
this.curRate++;
- return this.curRate % this.newGoalRate == 0;
+ return this.curRate % tickRate == 0;
+ // Pufferfish end
}
public boolean hasTasks() {
for (WrappedGoal task : this.availableGoals) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index 34f319ad09276c6f68dde449c79351de0d7d86f5..a719af0b512d9ef243d0d54f3b744b1b1a5f2772 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
@@ -119,6 +119,7 @@ public abstract class MoveToBlockGoal extends Goal {
for(int m = 0; m <= l; m = m > 0 ? -m : 1 - m) {
for(int n = m < l && m > -l ? l : 0; n <= l; n = n > 0 ? -n : 1 - n) {
mutableBlockPos.setWithOffset(blockPos, m, k - 1, n);
+ if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Pufferfish - if this block isn't loaded, continue
if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) {
this.blockPos = mutableBlockPos;
setTargetPosition(mutableBlockPos.immutable()); // Paper
diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
index c157309ac78e7af084d3acb6e8b2bcd469a39d5e..ac5e5676b194a2a99e5cf53eb89c1152cac963b8 100644
--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -75,9 +75,18 @@ public class TargetingConditions {
}
if (this.range > 0.0D) {
- double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
- double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0D); // Paper
+ // Pufferfish start - check range before getting visibility
+ // d = invisibility percent, e = follow range adjusted for invisibility, f = distance
double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ());
+ double followRangeRaw = this.useFollowRange ? this.getFollowRange(baseEntity) : this.range;
+
+ if (f > followRangeRaw * followRangeRaw) { // the actual follow range will always be this value or smaller, so if the distance is larger then it never will return true after getting invis
+ return false;
+ }
+
+ double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D;
+ double e = Math.max((followRangeRaw) * d, 2.0D); // Paper
+ // Pufferfish end
if (f > e * e) {
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
index 5beaa849a250ea005733250ad3edfa8382224667..2c91fe46355c9a201507de5577f693ed4f5fb974 100644
--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
@@ -237,13 +237,22 @@ public class Bat extends AmbientCreature {
}
}
+ // Pufferfish start - only check for spooky season once an hour
+ private static boolean isSpookySeason = false;
+ private static final int ONE_HOUR = 20 * 60 * 60;
+ private static int lastSpookyCheck = -ONE_HOUR;
private static boolean isHalloween() {
+ if (net.minecraft.server.MinecraftServer.currentTick - lastSpookyCheck > ONE_HOUR) {
LocalDate localdate = LocalDate.now();
int i = localdate.get(ChronoField.DAY_OF_MONTH);
int j = localdate.get(ChronoField.MONTH_OF_YEAR);
- return j == 10 && i >= 20 || j == 11 && i <= 3;
+ isSpookySeason = j == 10 && i >= 20 || j == 11 && i <= 3;
+ lastSpookyCheck = net.minecraft.server.MinecraftServer.currentTick;
+ }
+ return isSpookySeason;
}
+ // Pufferfish end
@Override
protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
index 5ad5f22e5aa26445e5eb229958e7bf356bdd460e..d241ca4d0295f9fce39c11197bd435cfac7f6e54 100644
--- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
@@ -221,9 +221,11 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
return 0.4F;
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("allayBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel) this.level(), this);
this.level().getProfiler().pop();
this.level().getProfiler().push("allayActivityUpdate");
diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
index d5b97d4316390028f54aa9bb9fa52b0b003e32a0..b4793b88688bd568a428aa520e880f0038de45a7 100644
--- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
@@ -280,9 +280,11 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder<Axolo
return true;
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("axolotlBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel) this.level(), this);
this.level().getProfiler().pop();
this.level().getProfiler().push("axolotlActivityUpdate");
diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
index 5000ebc5f19dee72a86360062b822aecd60beb47..c8e3d47b3f2dc919cca8ad397095437f1da6c762 100644
--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
@@ -164,9 +164,11 @@ public class Frog extends Animal implements VariantHolder<FrogVariant> {
return true;
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("frogBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel)this.level(), this);
this.level().getProfiler().pop();
this.level().getProfiler().push("frogActivityUpdate");
diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
index 4aeab90e778629c355189dfe79c39c4b21f5f5ac..6ed4ac06c76b8d0d6e8db778cade15dbd1e3e5f5 100644
--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
@@ -77,9 +77,11 @@ public class Tadpole extends AbstractFish {
return SoundEvents.TADPOLE_FLOP;
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("tadpoleBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel) this.level(), this);
this.level().getProfiler().pop();
this.level().getProfiler().push("tadpoleActivityUpdate");
diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
index 111a244087e24f25ba8524a46a228da10cd9498a..ff12ba2b79cb2e7e0bfd0e3b58ff6cb9e770092b 100644
--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
@@ -191,9 +191,11 @@ public class Goat extends Animal {
return (Brain<Goat>) super.getBrain(); // CraftBukkit - decompile error
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("goatBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel) this.level(), this);
this.level().getProfiler().pop();
this.level().getProfiler().push("goatActivityUpdate");
diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
index c9a4feb4a52c0eb621b120e5b8c18d0a74dae0cd..256598e058db1fd34d36390e45ab9903768343cb 100644
--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -150,6 +150,13 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
this.bossEvent.setName(this.getDisplayName());
}
+ // Pufferfish start - optimize suffocation
+ @Override
+ public boolean shouldCheckForSuffocation() {
+ return true;
+ }
+ // Pufferfish end
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.WITHER_AMBIENT;
diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
index bd5996eef2d946e9d7765b6b315bc5951158810e..0d51f435f18f3f9d59a3241a0b7fa1c4af841b72 100644
--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
@@ -333,11 +333,17 @@ public class EnderMan extends Monster implements NeutralMob {
private boolean teleport(double x, double y, double z) {
BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(x, y, z);
- while (blockposition_mutableblockposition.getY() > this.level().getMinBuildHeight() && !this.level().getBlockState(blockposition_mutableblockposition).blocksMotion()) {
+ // Pufferfish start - single chunk lookup
+ net.minecraft.world.level.chunk.LevelChunk chunk = this.level().getChunkIfLoaded(blockposition_mutableblockposition);
+ if (chunk == null) {
+ return false;
+ }
+ // Pufferfish end
+ while (blockposition_mutableblockposition.getY() > this.level().getMinBuildHeight() && !chunk.getBlockState(blockposition_mutableblockposition).blocksMotion()) { // Pufferfish
blockposition_mutableblockposition.move(Direction.DOWN);
}
- BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition);
+ BlockState iblockdata = chunk.getBlockState(blockposition_mutableblockposition); // Pufferfish
boolean flag = iblockdata.blocksMotion();
boolean flag1 = iblockdata.getFluidState().is(FluidTags.WATER);
diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
index 4257f2282152aee09533c9a2e53018d3e49effa4..e703320717ff620a19ff76d1c10066117c9895d5 100644
--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
@@ -128,9 +128,11 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
return (Brain<Hoglin>) super.getBrain(); // Paper - decompile fix
}
+ private int behaviorTick; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("hoglinBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel)this.level(), this);
this.level().getProfiler().pop();
HoglinAi.updateActivity(this);
diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
index 6407ddef8442fce4f310ac4babf3e3de0dd5fc9a..cfdc1650783d6855e0d4f33ec68aab48dbee09f0 100644
--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
@@ -300,9 +300,11 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
return !this.cannotHunt;
}
+ private int behaviorTick; // Pufferfish
@Override
protected void customServerAiStep() {
this.level().getProfiler().push("piglinBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick((ServerLevel) this.level(), this);
this.level().getProfiler().pop();
PiglinAi.updateActivity(this);
diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
index b2bc3a832c310448046ccde37a04918aa6d63197..5e43912708f9074dee1bb351efa737a7e6796fc3 100644
--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
@@ -272,11 +272,13 @@ public class Warden extends Monster implements VibrationSystem {
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
ServerLevel worldserver = (ServerLevel) this.level();
worldserver.getProfiler().push("wardenBrain");
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(worldserver, this);
this.level().getProfiler().pop();
super.customServerAiStep();
diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
index cbe2a37f74f4fb2abd0b3297699e54335aaed64f..2460768aaa7b8e6d183c03c1f0f2ccd6cb61a16f 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
@@ -142,6 +142,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
return holder.is(PoiTypes.MEETING);
});
+ public long nextGolemPanic = -1; // Pufferfish
+
public Villager(EntityType<? extends Villager> entityType, Level world) {
this(entityType, world, VillagerType.PLAINS);
}
@@ -245,6 +247,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
// Spigot End
+ private int behaviorTick = 0; // Pufferfish
@Override
@Deprecated // Paper
protected void customServerAiStep() {
@@ -254,7 +257,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
protected void customServerAiStep(final boolean inactive) {
// Paper end
this.level().getProfiler().push("villagerBrain");
- if (!inactive) this.getBrain().tick((ServerLevel) this.level(), this); // Paper
+ // Pufferfish start
+ if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) {
+ this.getBrain().tick((ServerLevel) this.level(), this); // Paper
+ }
+ // Pufferfish end
this.level().getProfiler().pop();
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java
index 96d664c28738d6090f7067761c2978dd1aa0fd0e..b1c24a02b87aca7b180a6efbce177f2300db49c1 100644
--- a/src/main/java/net/minecraft/world/entity/player/Inventory.java
+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java
@@ -687,6 +687,8 @@ public class Inventory implements Container, Nameable {
}
public boolean contains(ItemStack stack) {
+ // Pufferfish start - don't allocate iterators
+ /*
Iterator iterator = this.compartments.iterator();
while (iterator.hasNext()) {
@@ -701,6 +703,18 @@ public class Inventory implements Container, Nameable {
}
}
}
+ */
+ for (int i = 0; i < this.compartments.size(); i++) {
+ List<ItemStack> list = this.compartments.get(i);
+ for (int j = 0; j < list.size(); j++) {
+ ItemStack itemstack1 = list.get(j);
+
+ if (!itemstack1.isEmpty() && ItemStack.isSameItemSameTags(itemstack1, stack)) {
+ return true;
+ }
+ }
+ }
+ // Pufferfish end
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
index a90317100d32974e481e14476843f66997a2cf3a..cd0629581bae5f805842157af36c2d838e01bee3 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
@@ -44,6 +44,36 @@ public abstract class Projectile extends Entity implements TraceableEntity {
super(type, world);
}
+ // Pufferfish start
+ private static int loadedThisTick = 0;
+ private static int loadedTick;
+
+ private int loadedLifetime = 0;
+ @Override
+ public void setPos(double x, double y, double z) {
+ int currentTick = net.minecraft.server.MinecraftServer.currentTick;
+ if (loadedTick != currentTick) {
+ loadedTick = currentTick;
+ loadedThisTick = 0;
+ }
+ int previousX = Mth.floor(this.getX()) >> 4, previousZ = Mth.floor(this.getZ()) >> 4;
+ int newX = Mth.floor(x) >> 4, newZ = Mth.floor(z) >> 4;
+ if (previousX != newX || previousZ != newZ) {
+ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfLoadedMainThread(newX, newZ) != null;
+ if (!isLoaded) {
+ if (Projectile.loadedThisTick > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerTick) {
+ if (++this.loadedLifetime > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerProjectile) {
+ this.discard();
+ }
+ return;
+ }
+ Projectile.loadedThisTick++;
+ }
+ }
+ super.setPos(x, y, z);
+ }
+ // Pufferfish end
+
public void setOwner(@Nullable Entity entity) {
if (entity != null) {
this.ownerUUID = entity.getUUID();
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
index 00187fbbeddfc17e1b6887f8bf0f50da23938470..f64edfdb03f99624daf1e05b5dc86d845c3018b6 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
@@ -27,7 +27,10 @@ import org.bukkit.inventory.InventoryHolder;
public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
+ // Pufferfish start
private NonNullList<ItemStack> itemStacks;
+ private gg.airplane.structs.ItemListWithBitset itemStacksOptimized;
+ // Pufferfish end
@Nullable
public ResourceLocation lootTable;
public long lootTableSeed;
@@ -89,12 +92,18 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
protected AbstractMinecartContainer(EntityType<?> type, Level world) {
super(type, world);
- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
+ // Pufferfish start
+ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513
+ this.itemStacks = this.itemStacksOptimized.nonNullList;
+ // Pufferfish end
}
protected AbstractMinecartContainer(EntityType<?> type, double x, double y, double z, Level world) {
super(type, world, x, y, z);
- this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
+ // Pufferfish start
+ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513
+ this.itemStacks = this.itemStacksOptimized.nonNullList;
+ // Pufferfish end
}
@Override
@@ -156,6 +165,10 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
protected void readAdditionalSaveData(CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
this.lootableData.loadNbt(nbt); // Paper
+ // Pufferfish start
+ this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
+ this.itemStacks = this.itemStacksOptimized.nonNullList;
+ // Pufferfish end
this.readChestVehicleSaveData(nbt);
}
diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java
index 5a19875cbc603acea95193d969d2e1dc1e0bfd78..3688e9f8c6c6d1239095e3a87060ccca90386d0c 100644
--- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java
+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java
@@ -55,7 +55,7 @@ public class EndCrystalItem extends Item {
world.gameEvent((Entity) context.getPlayer(), GameEvent.ENTITY_PLACE, blockposition1);
EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight();
- if (enderdragonbattle != null) {
+ if (enderdragonbattle != null && gg.pufferfish.pufferfish.PufferfishConfig.allowEndCrystalRespawn) { // Pufferfish
enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - pass placed end crystal position to pre-check proximity to portal
}
}
diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
index 38f7d1ece27ec1a3deda21fb6a6f0e788c8ed718..252fc22844682c0f67dc02a87478e01e49b6430d 100644
--- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
+++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
@@ -26,8 +26,13 @@ public class ShapelessRecipe extends io.papermc.paper.inventory.recipe.RecipeBoo
final CraftingBookCategory category;
final ItemStack result;
final NonNullList<Ingredient> ingredients;
+ private final boolean isBukkit; // Pufferfish
+ // Pufferfish start
public ShapelessRecipe(String group, CraftingBookCategory category, ItemStack result, NonNullList<Ingredient> ingredients) {
+ this(group, category, result, ingredients, false);
+ }
+ public ShapelessRecipe(String group, CraftingBookCategory category, ItemStack result, NonNullList<Ingredient> ingredients, boolean isBukkit) { this.isBukkit = isBukkit; // Pufferfish end
this.group = group;
this.category = category;
this.result = result;
@@ -77,6 +82,28 @@ public class ShapelessRecipe extends io.papermc.paper.inventory.recipe.RecipeBoo
}
public boolean matches(CraftingContainer inventory, Level world) {
+ // Pufferfish start
+ if (!this.isBukkit) {
+ java.util.List<Ingredient> ingredients = com.google.common.collect.Lists.newArrayList(this.ingredients.toArray(new Ingredient[0]));
+
+ inventory: for (int index = 0; index < inventory.getContainerSize(); index++) {
+ ItemStack itemStack = inventory.getItem(index);
+
+ if (!itemStack.isEmpty()) {
+ for (int i = 0; i < ingredients.size(); i++) {
+ if (ingredients.get(i).test(itemStack)) {
+ ingredients.remove(i);
+ continue inventory;
+ }
+ }
+ return false;
+ }
+ }
+
+ return ingredients.isEmpty();
+ }
+ // Pufferfish end
+
StackedContents autorecipestackmanager = new StackedContents();
autorecipestackmanager.initialize(this); // Paper - better exact choice recipes
int i = 0;
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 2354a0e5d15e9be633d9fe3a1a9feefe7b9b7782..99e1c645871be28d130319b65700a1b8db093de4 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -210,6 +210,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public abstract ResourceKey<LevelStem> getTypeKey();
+ protected final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(java.util.concurrent.ThreadLocalRandom.current().nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - move thread unsafe random initialization // Pufferfish - getter
+
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper
@@ -1308,13 +1310,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
try {
tickConsumer.accept(entity);
MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
- } catch (Throwable throwable) {
+ } catch (Throwable throwable) { // Pufferfish - diff on change ServerLevel.tick
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
MinecraftServer.LOGGER.error(msg, throwable);
getCraftServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable)));
- entity.discard();
+ entity.discard(); // Pufferfish - diff on change ServerLevel.tick
// Paper end
}
}
@@ -1779,6 +1781,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
public ProfilerFiller getProfiler() {
+ if (gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Pufferfish
return (ProfilerFiller) this.profiler.get();
}
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 3cdddda9c0618e95288b81b975d499c8dd30c05f..9c2d62feff1816f5729060c6192269a5b2d34153 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -429,12 +429,12 @@ public final class NaturalSpawner {
}
}
- private static BlockPos getRandomPosWithin(Level world, LevelChunk chunk) {
+ private static BlockPos getRandomPosWithin(ServerLevel world, LevelChunk chunk) { // Pufferfish - accept serverlevel
ChunkPos chunkcoordintpair = chunk.getPos();
- int i = chunkcoordintpair.getMinBlockX() + world.random.nextInt(16);
- int j = chunkcoordintpair.getMinBlockZ() + world.random.nextInt(16);
+ int i = chunkcoordintpair.getMinBlockX() + world.getThreadUnsafeRandom().nextInt(16); // Pufferfish - use thread unsafe random
+ int j = chunkcoordintpair.getMinBlockZ() + world.getThreadUnsafeRandom().nextInt(16); // Pufferfish
int k = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i, j) + 1;
- int l = Mth.randomBetweenInclusive(world.random, world.getMinBuildHeight(), k);
+ int l = Mth.randomBetweenInclusive(world.getThreadUnsafeRandom(), world.getMinBuildHeight(), k); // Pufferfish
return new BlockPos(i, l, j);
}
diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java
index 65012a12e1430956ef55ced56773e6354ac26444..ed439b7e94646141c93a7dd3704d1cdeb5c27e16 100644
--- a/src/main/java/net/minecraft/world/level/biome/Biome.java
+++ b/src/main/java/net/minecraft/world/level/biome/Biome.java
@@ -66,14 +66,20 @@ public final class Biome {
private final BiomeGenerationSettings generationSettings;
private final MobSpawnSettings mobSettings;
private final BiomeSpecialEffects specialEffects;
- private final ThreadLocal<Long2FloatLinkedOpenHashMap> temperatureCache = ThreadLocal.withInitial(() -> {
+ // Pufferfish start - use our cache
+ private final ThreadLocal<gg.airplane.structs.Long2FloatAgingCache> temperatureCache = ThreadLocal.withInitial(() -> {
return Util.make(() -> {
+ /*
Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = new Long2FloatLinkedOpenHashMap(1024, 0.25F) {
protected void rehash(int i) {
}
};
long2FloatLinkedOpenHashMap.defaultReturnValue(Float.NaN);
return long2FloatLinkedOpenHashMap;
+
+ */
+ return new gg.airplane.structs.Long2FloatAgingCache(TEMPERATURE_CACHE_SIZE);
+ // Pufferfish end
});
});
@@ -118,17 +124,15 @@ public final class Biome {
@Deprecated
public float getTemperature(BlockPos blockPos) {
long l = blockPos.asLong();
- Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = this.temperatureCache.get();
- float f = long2FloatLinkedOpenHashMap.get(l);
+ // Pufferfish start
+ gg.airplane.structs.Long2FloatAgingCache cache = this.temperatureCache.get();
+ float f = cache.getValue(l);
if (!Float.isNaN(f)) {
return f;
} else {
float g = this.getHeightAdjustedTemperature(blockPos);
- if (long2FloatLinkedOpenHashMap.size() == 1024) {
- long2FloatLinkedOpenHashMap.removeFirstFloat();
- }
-
- long2FloatLinkedOpenHashMap.put(l, g);
+ cache.putValue(l, g);
+ // Pufferfish end
return g;
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
index a71414397bd45ee7bcacfeef0041d80dfa25f114..d66806565770cb03a21794f99e5c4b0f3040b26a 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
@@ -31,7 +31,10 @@ import org.bukkit.entity.HumanEntity;
public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity {
private static final int EVENT_SET_OPEN_COUNT = 1;
+ // Pufferfish start
private NonNullList<ItemStack> items;
+ private gg.airplane.structs.ItemListWithBitset optimizedItems;
+ // Pufferfish end
public final ContainerOpenersCounter openersCounter;
private final ChestLidController chestLidController;
@@ -65,9 +68,13 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
}
// CraftBukkit end
+ private final boolean isNative = getClass().equals(ChestBlockEntity.class); // Pufferfish
protected ChestBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
- this.items = NonNullList.withSize(27, ItemStack.EMPTY);
+ // Pufferfish start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(27);
+ this.items = this.optimizedItems.nonNullList;
+ // Pufferfish end
this.openersCounter = new ContainerOpenersCounter() {
@Override
protected void onOpen(Level world, BlockPos pos, BlockState state) {
@@ -98,6 +105,23 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
this.chestLidController = new ChestLidController();
}
+ // Pufferfish start
+ @Override
+ public boolean hasEmptySlot(Direction enumdirection) {
+ return isNative ? !this.optimizedItems.hasFullStacks() : super.hasEmptySlot(enumdirection);
+ }
+
+ @Override
+ public boolean isCompletelyFull(Direction enumdirection) {
+ return isNative ? this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection) : super.isCompletelyFull(enumdirection);
+ }
+
+ @Override
+ public boolean isCompletelyEmpty(Direction enumdirection) {
+ return isNative && this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection);
+ }
+ // Pufferfish end
+
public ChestBlockEntity(BlockPos pos, BlockState state) {
this(BlockEntityType.CHEST, pos, state);
}
@@ -115,7 +139,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
- this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
+ // Pufferfish start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
+ this.items = this.optimizedItems.nonNullList;
+ // Pufferfish end
if (!this.tryLoadLootTable(nbt)) {
ContainerHelper.loadAllItems(nbt, this.items);
}
@@ -187,7 +214,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
@Override
protected void setItems(NonNullList<ItemStack> list) {
- this.items = list;
+ // Pufferfish start
+ this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list);
+ this.items = this.optimizedItems.nonNullList;
+ // Pufferfish end
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
index d4dcf7fe26474ae07374e7761d823bc5c8b54f97..1d13fabb3f34023b4fbb1be9ad02ebc606645531 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -47,7 +47,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public static final int MOVE_ITEM_SPEED = 8;
public static final int HOPPER_CONTAINER_SIZE = 5;
+ // Pufferfish start
private NonNullList<ItemStack> items;
+ private gg.airplane.structs.ItemListWithBitset optimizedItems; // Pufferfish
+ // Pufferfish end
private int cooldownTime;
private long tickedGameTime;
@@ -83,14 +86,37 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
public HopperBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.HOPPER, pos, state);
- this.items = NonNullList.withSize(5, ItemStack.EMPTY);
+ // Pufferfish start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(5);
+ this.items = this.optimizedItems.nonNullList;
+ // Pufferfish end
this.cooldownTime = -1;
}
+ // Pufferfish start
+ @Override
+ public boolean hasEmptySlot(Direction enumdirection) {
+ return !this.optimizedItems.hasFullStacks();
+ }
+
+ @Override
+ public boolean isCompletelyFull(Direction enumdirection) {
+ return this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection);
+ }
+
+ @Override
+ public boolean isCompletelyEmpty(Direction enumdirection) {
+ return this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection);
+ }
+ // Pufferfish end
+
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
- this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
+ // Pufferfish start
+ this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize());
+ this.items = this.optimizedItems.nonNullList;
+ // Pufferfish end
if (!this.tryLoadLootTable(nbt)) {
ContainerHelper.loadAllItems(nbt, this.items);
}
@@ -491,6 +517,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
private static boolean isFullContainer(Container inventory, Direction direction) {
+ if (true) return inventory.isCompletelyFull(direction); // Pufferfish - use bitsets
// Paper start - optimize hoppers
if (inventory instanceof WorldlyContainer worldlyContainer) {
for (final int slot : worldlyContainer.getSlotsForFace(direction)) {
@@ -513,7 +540,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
}
private static boolean isEmptyContainer(Container inv, Direction facing) {
- return allMatch(inv, facing, IS_EMPTY_TEST);
+ // Paper start
+ // Pufferfish start - use bitsets
+ //return allMatch(inv, facing, IS_EMPTY_TEST);
+ return inv.isCompletelyEmpty(facing);
+ // Pufferfish end
}
public static boolean suckInItems(Level world, Hopper hopper) {
@@ -713,7 +744,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
if (HopperBlockEntity.canPlaceItemInContainer(to, stack, slot, side)) {
boolean flag = false;
- boolean flag1 = to.isEmpty();
+ boolean flag1 = to.isCompletelyEmpty(side); // Pufferfish
if (itemstack1.isEmpty()) {
// Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
@@ -908,7 +939,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@Override
protected void setItems(NonNullList<ItemStack> list) {
- this.items = list;
+ // Pufferfish start
+ this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list);
+ this.items = this.optimizedItems.nonNullList;
+ // Pufferfish end
}
public static void entityInside(Level world, BlockPos pos, BlockState state, Entity entity, HopperBlockEntity blockEntity) {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
index 3e638f12956e57548f76c7e2403ba370f7baa249..02364a148b347e3669275553004391e31d77c0b5 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
@@ -96,12 +96,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
public boolean isEmpty() {
this.unpackLootTable((Player)null);
// Paper start
- for (final ItemStack itemStack : this.getItems()) {
- if (!itemStack.isEmpty()) {
- return false;
- }
- }
- return true;
+ return this.isCompletelyEmpty(null); // Pufferfish - use super
// Paper end
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index fa170cc1ce7011d201295b89718292d696c7fc24..7fd68d4aba72b15b2e21e5c88b44e677b794fe57 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -86,6 +86,18 @@ public class LevelChunk extends ChunkAccess {
private final LevelChunkTicks<Fluid> fluidTicks;
public volatile FullChunkStatus chunkStatus = FullChunkStatus.INACCESSIBLE; // Paper - rewrite chunk system
+ // Pufferfish start - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively
+ private int lightningTick;
+ // shouldDoLightning compiles down to 29 bytes, which with the default of 35 byte inlining should guarantee an inline
+ public final boolean shouldDoLightning(net.minecraft.util.RandomSource random) {
+ if (this.lightningTick-- <= 0) {
+ this.lightningTick = random.nextInt(this.level.spigotConfig.thunderChance) << 1;
+ return true;
+ }
+ return false;
+ }
+ // Pufferfish end
+
public LevelChunk(Level world, ChunkPos pos) {
this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null);
}
@@ -109,6 +121,8 @@ public class LevelChunk extends ChunkAccess {
this.postLoad = entityLoader;
this.blockTicks = blockTickScheduler;
this.fluidTicks = fluidTickScheduler;
+
+ this.lightningTick = this.level.getThreadUnsafeRandom().nextInt(100000) << 1; // Pufferfish - initialize lightning tick
}
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index f0de72afad4bb571153436399386a6a8a70582a6..45b7527341fcb6d24f35318cedb522646b5ee1c2 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -25,6 +25,7 @@ public class LevelChunkSection {
public final PalettedContainer<BlockState> states;
// CraftBukkit start - read/write
private PalettedContainer<Holder<Biome>> biomes;
+ public short fluidStateCount; // Pufferfish
public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
// Paper start - optimise collisions
private int specialCollidingBlocks;
@@ -102,6 +103,7 @@ public class LevelChunkSection {
if (!fluid.isEmpty()) {
--this.tickingFluidCount;
+ --this.fluidStateCount; // Pufferfish
}
if (!state.isAir()) {
@@ -116,6 +118,7 @@ public class LevelChunkSection {
if (!fluid1.isEmpty()) {
++this.tickingFluidCount;
+ ++this.fluidStateCount; // Pufferfish
}
this.updateBlockCallback(x, y, z, iblockdata1, state); // Paper - optimise collisions
@@ -161,6 +164,7 @@ public class LevelChunkSection {
if (fluid.isRandomlyTicking()) {
this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
}
+ this.fluidStateCount++; // Pufferfish
}
// Paper start - optimise collisions
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
index 4cdfc433df67afcd455422e9baf56f167dd712ae..57fcf3910f45ce371ac2e237b277b1034caaac4e 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -8,7 +8,7 @@ import javax.annotation.Nullable;
import net.minecraft.world.entity.Entity;
public class EntityTickList {
- private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking?
+ public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? // Pufferfish - private->public
private void ensureActiveIsNotIterated() {
// Paper - replace with better logic, do not delay removals
diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
index e21f4c5aff3a8e97101f6efc1349fbecf326b5ea..c55f51e6db55f9fa66f53eef0e7a56af5f81d742 100644
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
@@ -45,6 +45,8 @@ public abstract class FlowingFluid extends Fluid {
public static final BooleanProperty FALLING = BlockStateProperties.FALLING;
public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL_FLOWING;
private static final int CACHE_SIZE = 200;
+ // Pufferfish start - use our own cache
+ /*
private static final ThreadLocal<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> {
Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2bytelinkedopenhashmap = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(200) {
protected void rehash(int i) {}
@@ -53,6 +55,14 @@ public abstract class FlowingFluid extends Fluid {
object2bytelinkedopenhashmap.defaultReturnValue((byte) 127);
return object2bytelinkedopenhashmap;
});
+ */
+
+ private static final ThreadLocal<gg.airplane.structs.FluidDirectionCache<Block.BlockStatePairKey>> localFluidDirectionCache = ThreadLocal.withInitial(() -> {
+ // Pufferfish todo - mess with this number for performance
+ // with 2048 it seems very infrequent on a small world that it has to remove old entries
+ return new gg.airplane.structs.FluidDirectionCache<>(2048);
+ });
+ // Pufferfish end
private final Map<FluidState, VoxelShape> shapes = Maps.newIdentityHashMap();
public FlowingFluid() {}
@@ -252,6 +262,8 @@ public abstract class FlowingFluid extends Fluid {
return false;
}
// Paper end - optimise collisions
+ // Pufferfish start - modify to use our cache
+ /*
Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap;
if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
@@ -259,9 +271,16 @@ public abstract class FlowingFluid extends Fluid {
} else {
object2bytelinkedopenhashmap = null;
}
+ */
+ gg.airplane.structs.FluidDirectionCache<Block.BlockStatePairKey> cache = null;
+
+ if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
+ cache = localFluidDirectionCache.get();
+ }
Block.BlockStatePairKey block_a;
+ /*
if (object2bytelinkedopenhashmap != null) {
block_a = new Block.BlockStatePairKey(state, fromState, face);
byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(block_a);
@@ -272,11 +291,22 @@ public abstract class FlowingFluid extends Fluid {
} else {
block_a = null;
}
+ */
+ if (cache != null) {
+ block_a = new Block.BlockStatePairKey(state, fromState, face);
+ Boolean flag = cache.getValue(block_a);
+ if (flag != null) {
+ return flag;
+ }
+ } else {
+ block_a = null;
+ }
VoxelShape voxelshape = state.getCollisionShape(world, pos);
VoxelShape voxelshape1 = fromState.getCollisionShape(world, fromPos);
boolean flag = !Shapes.mergedFaceOccludes(voxelshape, voxelshape1, face);
+ /*
if (object2bytelinkedopenhashmap != null) {
if (object2bytelinkedopenhashmap.size() == 200) {
object2bytelinkedopenhashmap.removeLastByte();
@@ -284,6 +314,11 @@ public abstract class FlowingFluid extends Fluid {
object2bytelinkedopenhashmap.putAndMoveToFirst(block_a, (byte) (flag ? 1 : 0));
}
+ */
+ if (cache != null) {
+ cache.putValue(block_a, flag);
+ }
+ // Pufferfish end
return flag;
}
diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootParams.java b/src/main/java/net/minecraft/world/level/storage/loot/LootParams.java
index e43d07ccdd36f0c9f5b8e9c74cf0d87e17eec66a..8e441f7c2b2d911a0c0111aaa231fc6adae08730 100644
--- a/src/main/java/net/minecraft/world/level/storage/loot/LootParams.java
+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootParams.java
@@ -21,8 +21,10 @@ public class LootParams {
public LootParams(ServerLevel world, Map<LootContextParam<?>, Object> parameters, Map<ResourceLocation, LootParams.DynamicDrop> dynamicDrops, float luck) {
this.level = world;
- this.params = parameters;
- this.dynamicDrops = dynamicDrops;
+ // Pufferfish start - use unmodifiable maps instead of immutable ones to skip the copy
+ this.params = java.util.Collections.unmodifiableMap(parameters);
+ this.dynamicDrops = java.util.Collections.unmodifiableMap(dynamicDrops);
+ // Pufferfish end
this.luck = luck;
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java b/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java
index ebe65474a4a05ff1637d7f37ebcfe690af59def5..42142c512b12e5b269c19f1e821c50e7496a5f25 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/EntityCollisionContext.java
@@ -19,47 +19,66 @@ public class EntityCollisionContext implements CollisionContext {
return defaultValue;
}
};
- private final boolean descending;
- private final double entityBottom;
- private final ItemStack heldItem;
- private final Predicate<FluidState> canStandOnFluid;
+ // Pufferfish start - remove these and pray no plugin uses them
+ // private final boolean descending;
+ // private final double entityBottom;
+ // private final ItemStack heldItem;
+ // private final Predicate<FluidState> canStandOnFluid;
+ // Pufferfish end
@Nullable
private final Entity entity;
protected EntityCollisionContext(boolean descending, double minY, ItemStack heldItem, Predicate<FluidState> walkOnFluidPredicate, @Nullable Entity entity) {
- this.descending = descending;
- this.entityBottom = minY;
- this.heldItem = heldItem;
- this.canStandOnFluid = walkOnFluidPredicate;
+ // Pufferfish start - remove these
+ // this.descending = descending;
+ // this.entityBottom = minY;
+ // this.heldItem = heldItem;
+ // this.canStandOnFluid = walkOnFluidPredicate;
+ // Pufferfish end
this.entity = entity;
}
/** @deprecated */
@Deprecated
protected EntityCollisionContext(Entity entity) {
- this(entity.isDescending(), entity.getY(), entity instanceof LivingEntity ? ((LivingEntity)entity).getMainHandItem() : ItemStack.EMPTY, entity instanceof LivingEntity ? ((LivingEntity)entity)::canStandOnFluid : (fluidState) -> {
- return false;
- }, entity);
+ // Pufferfish start - remove this
+ // this(entity.isDescending(), entity.getY(), entity instanceof LivingEntity ? ((LivingEntity)entity).getMainHandItem() : ItemStack.EMPTY, entity instanceof LivingEntity ? ((LivingEntity)entity)::canStandOnFluid : (fluidState) -> {
+ // return false;
+ // }, entity);
+ // Pufferfish end
+ this.entity = entity;
}
@Override
public boolean isHoldingItem(Item item) {
- return this.heldItem.is(item);
+ // Pufferfish start
+ Entity entity = this.entity;
+ if (entity instanceof LivingEntity livingEntity) {
+ return livingEntity.getMainHandItem().is(item);
+ }
+ return ItemStack.EMPTY.is(item);
+ // Pufferfish end
}
@Override
public boolean canStandOnFluid(FluidState stateAbove, FluidState state) {
- return this.canStandOnFluid.test(state) && !stateAbove.getType().isSame(state.getType());
+ // Pufferfish start
+ Entity entity = this.entity;
+ if (entity instanceof LivingEntity livingEntity) {
+ return livingEntity.canStandOnFluid(state) && !stateAbove.getType().isSame(state.getType());
+ }
+ return false;
+ // Pufferfish end
}
@Override
public boolean isDescending() {
- return this.descending;
+ return this.entity != null && this.entity.isDescending(); // Pufferfish
}
@Override
public boolean isAbove(VoxelShape shape, BlockPos pos, boolean defaultValue) {
- return this.entityBottom > (double)pos.getY() + shape.max(Direction.Axis.Y) - (double)1.0E-5F;
+ return (this.entity == null ? -Double.MAX_VALUE : entity.getY()) > (double)pos.getY() + shape.max(Direction.Axis.Y) - (double)1.0E-5F; // Pufferfish
}
@Nullable
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 356107688a5d40d1c462b164f61af82f4dfd3926..2ecb8bfd98c141e9f5e7f4e441c8df91e1b9fbc2 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -269,7 +269,7 @@ import javax.annotation.Nullable; // Paper
import javax.annotation.Nonnull; // Paper
public final class CraftServer implements Server {
- private final String serverName = "Paper"; // Paper
+ private final String serverName = "Pufferfish"; // Paper // Pufferfish
private final String serverVersion;
private final String bukkitVersion = Versioning.getBukkitVersion();
private final Logger logger = Logger.getLogger("Minecraft");
@@ -1124,6 +1124,11 @@ public final class CraftServer implements Server {
plugin.getPluginMeta().getDisplayName(),
"This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies."
));
+ getLogger().log(Level.SEVERE, String.format("%s Stacktrace", worker.getThread().getName()));
+ StackTraceElement[] stackTrace = worker.getThread().getStackTrace();
+ for (StackTraceElement element : stackTrace) {
+ getLogger().log(Level.SEVERE, " " + element.toString());
+ }
}
}
// Paper end
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
index 96d772eb02f79f8c478f5e6f065e387aa7665b18..c5ce412f321b8b4f31cc042893659e213b081f29 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
@@ -45,6 +45,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe
data.set(i, this.toNMS(ingred.get(i), true));
}
- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftNamespacedKey.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.ShapelessRecipe(this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data)));
+ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftNamespacedKey.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.ShapelessRecipe(this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data, true))); // Pufferfish
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 548c77592a3520e8053483644eba805079a14f1a..397c10f64db3a4d7296fe18585b56851bc3a1f01 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -485,7 +485,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
@Override
public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
- return new com.destroystokyo.paper.PaperVersionFetcher();
+ return new gg.pufferfish.pufferfish.PufferfishVersionFetcher(); // Pufferfish
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
index 774556a62eb240da42e84db4502e2ed43495be17..80553face9c70c2a3d897681e7761df85b22d464 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
@@ -11,7 +11,7 @@ public final class Versioning {
public static String getBukkitVersion() {
String result = "Unknown-Version";
- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties");
+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/gg.pufferfish.pufferfish/pufferfish-api/pom.properties"); // Pufferfish
Properties properties = new Properties();
if (stream != null) {
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 59103744ac6beeb12719fdefcda54eeff498229e..c0333ba8e57cd284bb8ab15181da6b39d55872f9 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -38,6 +38,10 @@ import co.aikar.timings.MinecraftTimings;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
+// Pufferfish start
+import net.minecraft.world.phys.Vec3;
+import java.util.List;
+// Pufferfish end
public class ActivationRange
{
@@ -222,6 +226,25 @@ public class ActivationRange
}
// Paper end - configurable marker ticking
ActivationRange.activateEntity(entity);
+
+ // Pufferfish start
+ if (gg.pufferfish.pufferfish.PufferfishConfig.dearEnabled && entity.getType().dabEnabled) {
+ if (!entity.activatedPriorityReset) {
+ entity.activatedPriorityReset = true;
+ entity.activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio;
+ }
+ Vec3 playerVec = player.position();
+ Vec3 entityVec = entity.position();
+ double diffX = playerVec.x - entityVec.x, diffY = playerVec.y - entityVec.y, diffZ = playerVec.z - entityVec.z;
+ int squaredDistance = (int) (diffX * diffX + diffY * diffY + diffZ * diffZ);
+ entity.activatedPriority = squaredDistance > gg.pufferfish.pufferfish.PufferfishConfig.startDistanceSquared ?
+ Math.max(1, Math.min(squaredDistance >> gg.pufferfish.pufferfish.PufferfishConfig.activationDistanceMod, entity.activatedPriority)) :
+ 1;
+ } else {
+ entity.activatedPriority = 1;
+ }
+ // Pufferfish end
+
}
// Paper end
}
@@ -238,12 +261,12 @@ public class ActivationRange
if ( MinecraftServer.currentTick > entity.activatedTick )
{
if ( entity.defaultActivationState )
- {
+ { // Pufferfish - diff on change
entity.activatedTick = MinecraftServer.currentTick;
return;
}
if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) )
- {
+ { // Pufferfish - diff on change
entity.activatedTick = MinecraftServer.currentTick;
}
}
@@ -297,7 +320,7 @@ public class ActivationRange
if ( entity instanceof LivingEntity )
{
LivingEntity living = (LivingEntity) entity;
- if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper
+ if ( living.onClimableCached() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper // Pufferfish - use cached
{
return 1; // Paper
}