mirror of
https://github.com/PurpurMC/Purpur.git
synced 2026-02-17 16:37:43 +01:00
Upstream has released updates that appear to apply and compile correctly Pufferfish Changes: pufferfish-gg/Pufferfish@e1ad783 Updated Upstream (Paper) pufferfish-gg/Pufferfish@d3d30cb Fix #53 pufferfish-gg/Pufferfish@53295bd Remove busy wait in async execution utility pufferfish-gg/Pufferfish@5e6e1ad Updated Upstream (Paper)
3667 lines
176 KiB
Diff
3667 lines
176 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Kevin Raneri <kevin.raneri@gmail.com>
|
|
Date: Wed, 3 Feb 2021 23:02:38 -0600
|
|
Subject: [PATCH] Pufferfish Server Changes
|
|
|
|
Pufferfish
|
|
Copyright (C) 2022 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 b0e4f11e8af4b909a56bb5576d05ef0537fb25f7..9d4fc7b32e4b3f232748b7f6808c33f989649f5c 100644
|
|
--- a/build.gradle.kts
|
|
+++ b/build.gradle.kts
|
|
@@ -7,8 +7,12 @@ plugins {
|
|
}
|
|
|
|
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")
|
|
@@ -42,6 +46,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 ("me.carleslc.Simple-YAML:Simple-Yaml:1.8.2") {
|
|
+ exclude(group="org.yaml", module="snakeyaml")
|
|
+ }
|
|
+ // Pufferfish end
|
|
+
|
|
testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
|
|
testImplementation("junit:junit:4.13.2")
|
|
testImplementation("org.hamcrest:hamcrest-library:1.3")
|
|
@@ -50,6 +61,14 @@ dependencies {
|
|
}
|
|
|
|
val craftbukkitPackageVersion = "1_19_R1" // Paper
|
|
+
|
|
+// Pufferfish Start
|
|
+tasks.withType<JavaCompile> {
|
|
+ val compilerArgs = options.compilerArgs
|
|
+ compilerArgs.add("--add-modules=jdk.incubator.vector")
|
|
+}
|
|
+// Pufferfish End
|
|
+
|
|
tasks.jar {
|
|
archiveClassifier.set("dev")
|
|
|
|
@@ -62,7 +81,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,
|
|
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index 06bff37e4c1fddd3be6343049a66787c63fb420c..2cc44fbf8e5bd436b6d4e19f6c06b351e750cb31 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -241,7 +241,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 7b1843e16745ca8db2244e17490d291401f22679..061716934ba0a1f01e4d85d664034f72b3c7a765 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();
|
|
@@ -603,7 +603,7 @@ public class Metrics {
|
|
|
|
metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
|
|
metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
|
|
- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"));
|
|
+ metrics.addCustomChart(new Metrics.SimplePie("pufferfish_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"));
|
|
|
|
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..ed9c8e882739c02d0d04129d251e4c726b422c07
|
|
--- /dev/null
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
|
|
@@ -0,0 +1,283 @@
|
|
+package gg.pufferfish.pufferfish;
|
|
+
|
|
+import gg.pufferfish.pufferfish.simd.SIMDDetection;
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.util.Collections;
|
|
+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.core.Registry;
|
|
+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;
|
|
+ } catch (NoClassDefFoundError | Exception ignored) {}
|
|
+
|
|
+ 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 and Java 18.");
|
|
+ } 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 : Registry.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 : Registry.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() {
|
|
+ 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 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..e877921370f6009a4bd204d9b17d2d58834b8822
|
|
--- /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.19/lastSuccessfulBuild/buildNumber");
|
|
+ private static final String GITHUB_FORMAT = "https://api.github.com/repos/pufferfish-gg/Pufferfish/compare/ver/1.19...%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/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
index 63ec2ebb71aa0e0dbb64bbce7cd3c9494e9ce2e7..d03551e81e3ef37935cb1d963aba3df316f48ef5 100644
|
|
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
@@ -7,6 +7,7 @@ import net.kyori.adventure.text.Component;
|
|
import net.kyori.adventure.text.format.NamedTextColor;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
|
+import org.bukkit.Bukkit; // Pufferfish
|
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
|
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
|
@@ -16,6 +17,7 @@ import org.spongepowered.configurate.objectmapping.meta.Setting;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
+import java.util.logging.Level; // Pufferfish
|
|
|
|
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
|
public class GlobalConfiguration extends ConfigurationPart {
|
|
@@ -51,6 +53,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|
|
|
public class Timings extends ConfigurationPart.Post {
|
|
public boolean enabled = true;
|
|
+ public boolean reallyEnabled = false;
|
|
public boolean verbose = true;
|
|
public String url = "https://timings.aikar.co/";
|
|
public boolean serverNamePrivacy = false;
|
|
@@ -64,6 +67,14 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|
|
|
@Override
|
|
public void postProcess() {
|
|
+ // Pufferfish start
|
|
+ if (enabled && !reallyEnabled) {
|
|
+ Bukkit.getLogger().log(Level.WARNING, "[Pufferfish] To improve performance, timings have been disabled by default");
|
|
+ Bukkit.getLogger().log(Level.WARNING, "[Pufferfish] You can still use timings by using /timings on, but they will not start on server startup unless you set timings.really-enabled to true in paper.yml");
|
|
+ Bukkit.getLogger().log(Level.WARNING, "[Pufferfish] If you would like to disable this message, either set timings.really-enabled to true or timings.enabled to false.");
|
|
+ }
|
|
+ enabled = reallyEnabled;
|
|
+ // Pufferfish end
|
|
MinecraftTimings.processConfig(this);
|
|
}
|
|
}
|
|
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
index dacb00c7cb2702ae8e9c6be61ca08e41bd6009e4..b5d4c53bf1046fa52da5398491258b94f1e0fcd0 100644
|
|
--- a/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
@@ -209,7 +209,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 6dc6c3bccb4ba34268a87b0754c87eb1e0df4135..3cadf20891888b56ac70798d581d6a044a98c0a3 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -298,6 +298,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
public volatile Thread shutdownThread; // Paper
|
|
public volatile boolean abnormalExit = false; // Paper
|
|
public boolean isIteratingOverLevels = false; // Paper
|
|
+
|
|
+ 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();
|
|
@@ -1654,7 +1656,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) {
|
|
@@ -2233,6 +2235,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 ff3eced0e20c39b825586897ee2fed01dd471d88..5c54a5da7fb50cd97799c5fa280a24d5c6117244 100644
|
|
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
@@ -226,6 +226,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 55d8ced734a408c990c6c4fbc81707bcb1f27daa..65327b58bced011efb117e546fd6657092b76035 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -334,7 +334,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
|
|
this.regionManagers.add(this.dataRegionManager);
|
|
// Paper end
|
|
- this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
|
|
+ this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new gg.pufferfish.pufferfish.util.AsyncPlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper // Pufferfish
|
|
// Paper start - use distance map to optimise entity tracker
|
|
this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
|
|
this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length];
|
|
@@ -1589,8 +1589,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()) {
|
|
@@ -1602,6 +1622,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 4ff563d903633f181e1268daa77f250cfec204a0..014255019ce3f22e0c8cf8f0a775669f909d18f4 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);
|
|
@@ -702,6 +705,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 = level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
|
|
|
|
@@ -711,18 +715,25 @@ public class ServerChunkCache extends ChunkSource {
|
|
// Paper start - per player mob spawning
|
|
NaturalSpawner.SpawnState spawnercreature_d; // moved down
|
|
if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't count mobs when animals and monsters are disabled
|
|
- // re-set mob counts
|
|
- for (ServerPlayer player : this.level.players) {
|
|
- Arrays.fill(player.mobCounts, 0);
|
|
+ // Pufferfish start - moved down when async processing
|
|
+ if (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) {
|
|
+ // re-set mob counts
|
|
+ for (ServerPlayer player : this.level.players) {
|
|
+ Arrays.fill(player.mobCounts, 0);
|
|
+ }
|
|
+ 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.chunkMap.playerMobDistanceMap == null ? new LocalMobCapCalculator(this.chunkMap) : null, false);
|
|
+ // Pufferfish start - this is only implemented for per-player mob spawning so this makes everything work if this setting is disabled.
|
|
+ lastSpawnState = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, this.chunkMap.playerMobDistanceMap == null ? 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 - moved down
|
|
this.level.timings.chunkTicks.startTiming(); // Paper
|
|
@@ -760,8 +771,8 @@ public class ServerChunkCache extends ChunkSource {
|
|
|
|
if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - the chunk is known ticking
|
|
chunk1.incrementInhabitedTime(j);
|
|
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
|
|
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
|
|
+ if (flag2 && (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning || _pufferfish_spawnCountsReady.get()) && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
|
|
+ NaturalSpawner.spawnForChunk(this.level, chunk1, lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1); // Pufferfish
|
|
}
|
|
|
|
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - the chunk is known ticking
|
|
@@ -823,6 +834,30 @@ public class ServerChunkCache extends ChunkSource {
|
|
}
|
|
// Paper end - controlled flush for entity tracker packets
|
|
}
|
|
+
|
|
+ // Pufferfish start - optimize mob spawning
|
|
+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) {
|
|
+ for (ServerPlayer player : this.level.players) {
|
|
+ Arrays.fill(player.mobCounts, 0);
|
|
+ }
|
|
+ if (firstRunSpawnCounts) {
|
|
+ firstRunSpawnCounts = false;
|
|
+ _pufferfish_spawnCountsReady.set(true);
|
|
+ }
|
|
+ if (chunkMap.playerMobDistanceMap != null && _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 2358bb1788cfb902bac9b3b7588954af2d2cd823..163f14b4e1ca99d75e5d8e14190f7b91cb58e8f3 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
@@ -166,6 +166,7 @@ public class ServerEntity {
|
|
boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
|
|
|
|
if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround() && !(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.isOnGround());
|
|
@@ -175,6 +176,7 @@ public class ServerEntity {
|
|
} else {
|
|
packet1 = new ClientboundMoveEntityPacket.PosRot(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), (byte) i, (byte) j, this.entity.isOnGround());
|
|
}
|
|
+ } // Pufferfish
|
|
} else {
|
|
this.wasOnGround = this.entity.isOnGround();
|
|
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 feb0a6d6a90850977b393440881c472c317a9323..fde970ada330e3e2fd481c7687c8499129bd929c 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -708,6 +708,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
|
|
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();
|
|
@@ -727,7 +728,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();
|
|
}
|
|
}
|
|
@@ -794,9 +808,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();
|
|
@@ -807,7 +823,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)) {
|
|
DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
|
|
@@ -831,7 +847,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
}
|
|
|
|
gameprofilerfiller.popPush("iceandsnow");
|
|
- if (!this.paperConfig().environment.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow
|
|
+ if (!this.paperConfig().environment.disableIceAndSnow && (this.currentIceAndSnowTick++ & 15) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking // Pufferfish - optimize further random ticking
|
|
// Paper start - optimise chunk ticking
|
|
this.getRandomBlockPosition(j, 0, k, 15, blockposition);
|
|
int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1;
|
|
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
index d31a345edfffe39f127073fc3aec8b3489bae79c..187791ff8197be015856fdfa9159fb26558f95cf 100644
|
|
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
|
|
@@ -1216,6 +1216,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
|
|
@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();
|
|
@@ -2364,6 +2365,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
|
|
}
|
|
|
|
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 540bc9500c35c0db719b00aa26f6fb3a1b08ed9f..806cb760822a99316b08ad95ff8922df717050e2 100644
|
|
--- a/src/main/java/net/minecraft/world/Container.java
|
|
+++ b/src/main/java/net/minecraft/world/Container.java
|
|
@@ -2,6 +2,8 @@ package net.minecraft.world;
|
|
|
|
import java.util.Set;
|
|
import java.util.function.Predicate;
|
|
+
|
|
+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;
|
|
@@ -10,6 +12,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;
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
index 6fa47becd0f83ac4273ef3a10c314aa27b08184b..249305f0fc4f525fe2d109ecc96900ce3680b1b1 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
@@ -291,7 +291,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;
|
|
@@ -413,6 +413,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;
|
|
}
|
|
@@ -487,17 +493,36 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
|
this.isLegacyTrackingEntity = isLegacyTrackingEntity;
|
|
}
|
|
|
|
+ private org.spigotmc.TrackingRange.TrackingRangeType getFurthestEntity(Entity entity, net.minecraft.server.level.ChunkMap chunkMap, org.spigotmc.TrackingRange.TrackingRangeType type, int range) {
|
|
+ List<Entity> passengers = entity.getPassengers();
|
|
+ for (int i = 0, size = passengers.size(); i < size; i++) {
|
|
+ Entity passenger = passengers.get(i);
|
|
+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
|
|
+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
|
|
+ if (passengerRange > range) {
|
|
+ type = passengerType;
|
|
+ range = passengerRange;
|
|
+ }
|
|
+
|
|
+ type = this.getFurthestEntity(passenger, chunkMap, type, range);
|
|
+ }
|
|
+
|
|
+ return type;
|
|
+ }
|
|
+
|
|
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInTrackRange() {
|
|
// determine highest range of passengers
|
|
if (this.passengers.isEmpty()) {
|
|
return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()]
|
|
.getObjectsInRange(MCUtil.getCoordinateKey(this));
|
|
}
|
|
- Iterable<Entity> passengers = this.getIndirectPassengers();
|
|
+ //Iterable<Entity> passengers = this.getIndirectPassengers(); // Pufferfish
|
|
net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap;
|
|
org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType;
|
|
int range = chunkMap.getEntityTrackerRange(type.ordinal());
|
|
|
|
+ // Pufferfish start - use getFurthestEntity to skip getIndirectPassengers
|
|
+ /*
|
|
for (Entity passenger : passengers) {
|
|
org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
|
|
int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
|
|
@@ -506,6 +531,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
|
range = passengerRange;
|
|
}
|
|
}
|
|
+ */
|
|
+ type = this.getFurthestEntity(this, chunkMap, type, range);
|
|
+ // Pufferfish end
|
|
|
|
return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this));
|
|
}
|
|
@@ -787,6 +815,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
|
// CraftBukkit end
|
|
|
|
public void baseTick() {
|
|
+ // Pufferfish start - entity TTL
|
|
+ if (type != EntityType.PLAYER && type.ttl >= 0 && this.tickCount >= type.ttl) {
|
|
+ remove(RemovalReason.DISCARDED);
|
|
+ return;
|
|
+ }
|
|
+ // Pufferfish end - entity TTL
|
|
this.level.getProfiler().push("entityBaseTick");
|
|
if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Update last hurt when ticking
|
|
this.feetBlockState = null;
|
|
@@ -4019,16 +4053,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;
|
|
@@ -4036,14 +4072,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;
|
|
@@ -4065,9 +4148,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 72516335570d7137a62ec8667a6e8f06f024692f..b44322c337bcded94c60e1761a3891013ae1dd1a 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
|
|
@@ -305,6 +305,8 @@ public class EntityType<T extends Entity> implements EntityTypeTest<Entity, T> {
|
|
return Registry.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(id));
|
|
}
|
|
|
|
+ public boolean dabEnabled = false; // Pufferfish
|
|
+ public int ttl = -1; // Pufferfish
|
|
// Paper start - add id
|
|
public final String id;
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
index 3bfa0c6a0d82ed980b3289051892a6d1745ebb69..84caca55518464ac4fdf7dfe766bcaf00a9da227 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 {
|
|
|
|
@@ -398,8 +397,7 @@ public abstract class LivingEntity extends Entity {
|
|
|
|
if (this.isAlive()) {
|
|
boolean flag = this instanceof net.minecraft.world.entity.player.Player;
|
|
-
|
|
- if (this.isInWall()) {
|
|
+ if ((!gg.pufferfish.pufferfish.PufferfishConfig.enableSuffocationOptimization || (tickCount % 10 == 0 && couldPossiblyBeHurt(1.0F))) && this.isInWall()) { // Pufferfish - optimize suffocation
|
|
this.hurt(DamageSource.IN_WALL, 1.0F);
|
|
} else if (flag && !this.level.getWorldBorder().isWithinBounds(this.getBoundingBox())) {
|
|
double d0 = this.level.getWorldBorder().getDistanceToBorder(this) + this.level.getWorldBorder().getDamageSafeZone();
|
|
@@ -1313,6 +1311,15 @@ public abstract class LivingEntity extends Entity {
|
|
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;
|
|
+ }
|
|
+ // Pufferfish end
|
|
+
|
|
@Override
|
|
public boolean hurt(DamageSource source, float amount) {
|
|
if (this.isInvulnerableTo(source)) {
|
|
@@ -1906,6 +1913,20 @@ public abstract class LivingEntity extends Entity {
|
|
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;
|
|
@@ -3607,7 +3628,10 @@ public abstract class LivingEntity extends Entity {
|
|
Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ());
|
|
|
|
// Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists
|
|
- return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr
|
|
+ // Pufferfish start
|
|
+ //return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr
|
|
+ return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.rayTraceDirect(vec3d, vec3d1, net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.BlockHitResult.Type.MISS;
|
|
+ // Pufferfish end
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
|
|
index cf40ebd06c52a7a00e6f704a29ae9d2b5186d35a..26fabc96d3c3ac4ea35d094c686975b19c428d7f 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Mob.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
|
|
@@ -210,14 +210,16 @@ public abstract class Mob extends LivingEntity {
|
|
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();
|
|
}
|
|
}
|
|
@@ -860,16 +862,20 @@ public abstract class Mob extends LivingEntity {
|
|
|
|
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 692524a69d43dcf52ae1b0f7f593fc53f3878137..59ca3a0a70c68263495ae9972215b76554d3fb83 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
|
|
@@ -22,9 +22,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) {
|
|
@@ -44,11 +46,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
|
|
}
|
|
|
|
public boolean hasAttribute(Attribute attribute) {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
|
index 43243537b765a2d270be6de3f053fea77ff67d18..bf3b8ccb3e031e0ad24cd51e28ea8cbd4f8a8030 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
|
@@ -72,6 +72,7 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
|
|
@Override
|
|
protected void start(ServerLevel world, PathfinderMob entity, long time) {
|
|
this.nextScheduledStart = time + 20L + (long)world.getRandom().nextInt(20);
|
|
+ if (entity.getNavigation().isStuck()) this.nextScheduledStart += 200L; // Pufferfish - wait an additional 10s to check again if they're stuck
|
|
PoiManager poiManager = world.getPoiManager();
|
|
this.batchCache.long2ObjectEntrySet().removeIf((entry) -> {
|
|
return !entry.getValue().isStillValid(time);
|
|
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 42d466f7f162943886078eba3db18f2dfc2d7bee..6c0dda1ce018ec6bb2ebb97147045fffae0a89d5 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 serverLevel, Villager villager, long l) {
|
|
- if (l % 100L == 0L) {
|
|
+ // Pufferfish start
|
|
+ if (villager.nextGolemPanic < 0) villager.nextGolemPanic = l + 100;
|
|
+ if (--villager.nextGolemPanic < l) {
|
|
+ villager.nextGolemPanic = -1;
|
|
+ // Pufferfish end
|
|
villager.spawnGolemIfNeeded(serverLevel, l, 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 1805aacd982dae8d971cfad0ead23c161badb095..bc3defa2c2ca0971ce3d9642a38380973a82de46 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
|
|
@@ -50,9 +50,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 26bf383caea68834c654b25653ced9017f1b1b22..615eb55e24d365d994fbfe9d45d2be387fd5d561 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 a7575b5ef56af6f53448d391abb4956e130148ca..e752c83df50fb9b670ecea2abc95426c2a009b6f 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 def01d221f36d71640bf4ef982a984909aacc6da..bfea05018d9515fa7a9a8c04d4e2a63e0f3cd0d4 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
|
|
@@ -253,13 +253,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 1dcd0c494681b7665b6b86dbe20375afd8d2fad8..f889b352d5618c1955e21273da79604a33e30c06 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
|
|
@@ -228,9 +228,11 @@ public class Allay extends PathfinderMob implements InventoryCarrier {
|
|
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 e7467fec2cab37c1a9933d1096724e3732913590..1c93872f9fcc6f6a19dee8565726501d0e580d44 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
|
|
@@ -283,9 +283,11 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable {
|
|
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 bb6063ae7f4438916306ce876057f7488537b444..74cb2741a5cd66bdac3a22de938ae82968705a56 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
|
|
@@ -174,9 +174,11 @@ public class Frog extends Animal {
|
|
return this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6D && this.isInWaterOrBubble();
|
|
}
|
|
|
|
+ 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 3dd4e6d622a6daafa00ae971edd88a147e34beef..32cca29fd622d18030931e1f330791491e7f9fa0 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
|
|
@@ -76,9 +76,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 a4690391df0aa26abea1ad92e6143eb19c5d1143..3c3453638e117609190787efbf5d87fc20dcf8fd 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
|
|
@@ -163,9 +163,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/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
|
|
index fcc5444a1268931a0fd2df1e6bbbc17cfd5a61e0..16a55f94bda9f959548772c8916b4dc3eb045d47 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
|
|
@@ -252,10 +252,16 @@ public class ItemEntity extends Entity {
|
|
if (entityitem.isMergable()) {
|
|
// Paper Start - Fix items merging through walls
|
|
if (this.level.paperConfig().fixes.fixItemsMergingThroughWalls) {
|
|
+ // Pufferfish start - skip the allocations
|
|
+ /*
|
|
net.minecraft.world.level.ClipContext rayTrace = new net.minecraft.world.level.ClipContext(this.position(), entityitem.position(),
|
|
net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, this);
|
|
net.minecraft.world.phys.BlockHitResult rayTraceResult = level.clip(rayTrace);
|
|
if (rayTraceResult.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) continue;
|
|
+ */
|
|
+ if (level.rayTraceDirect(this.position(), entityitem.position(), net.minecraft.world.phys.shapes.CollisionContext.of(this)) ==
|
|
+ net.minecraft.world.phys.HitResult.Type.BLOCK) continue;
|
|
+ // Pufferfish end
|
|
}
|
|
// Paper End
|
|
this.tryToMerge(entityitem);
|
|
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 f22e615dba31619c97bf58930da060476a52facf..f5bb64f9f683cf21e772035e9be100ed2ddf8bc6 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
|
|
@@ -317,11 +317,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).getMaterial().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).getMaterial().blocksMotion()) { // Pufferfish
|
|
blockposition_mutableblockposition.move(Direction.DOWN);
|
|
}
|
|
|
|
- BlockState iblockdata = this.level.getBlockState(blockposition_mutableblockposition);
|
|
+ BlockState iblockdata = chunk.getBlockState(blockposition_mutableblockposition); // Pufferfish
|
|
boolean flag = iblockdata.getMaterial().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 45741410a13cffe3419e34b5607b048bbcf1c3ff..5d487f1613b1fc5807283c20e5cc23a432d08f42 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
|
|
@@ -126,9 +126,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 abeb7285dc0d1e6687feaae8be2dbde1d61b7f11..23547da37a4178a32775285a1d697c369a7ffcd2 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
|
|
@@ -289,9 +289,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 27bd70dc30c8472e5a80f3273f9233a0392f831d..b3158b1a7772f1254dd081ff6d3278a92192e896 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
|
|
@@ -270,11 +270,13 @@ public class Warden extends Monster implements VibrationListener.VibrationListen
|
|
|
|
}
|
|
|
|
+ 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 10b45ec24a5a0867106d1694312385ad1e267f43..93077e8c6b5a35adc6febb749d1d08be172402f1 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
|
|
@@ -140,6 +140,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);
|
|
}
|
|
@@ -243,11 +245,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
|
|
}
|
|
// Spigot End
|
|
|
|
+ private int behaviorTick = 0; // Pufferfish
|
|
@Override
|
|
protected void customServerAiStep() { mobTick(false); }
|
|
protected void mobTick(boolean inactive) {
|
|
this.level.getProfiler().push("villagerBrain");
|
|
- if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // Paper
|
|
+ // Pufferfish start
|
|
+ if (!inactive) {
|
|
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
|
|
+ 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 a1a625a8dacf4d2bbf75ddd90dce1b1be663c919..23e375c9a6955d03e0fc9be91ff0403251c8216c 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/player/Inventory.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java
|
|
@@ -681,6 +681,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()) {
|
|
@@ -695,6 +697,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() && itemstack1.sameItem(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 713c11d6547cb02ac4b6a02aec07a8ba68019f3f..bf828b9a36fc70bcf4c4d87d5db2d37aa384499c 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
|
|
@@ -42,6 +42,36 @@ public abstract class Projectile extends Entity {
|
|
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 start
|
|
+
|
|
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 b8fb7b5a347298ada16bc8b818edf1863e3f6040..43b4c4f9630bfa451d135139236ac6fce034ec15 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/crafting/ShapelessRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
|
|
index ffe5476d8ed15ee4384b679c341688787205ce59..9051559e78851257a56a998b4b882ebbcc394639 100644
|
|
--- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
|
|
+++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
|
|
@@ -25,8 +25,13 @@ public class ShapelessRecipe implements CraftingRecipe {
|
|
final String group;
|
|
final ItemStack result;
|
|
final NonNullList<Ingredient> ingredients;
|
|
+ private final boolean isBukkit; // Pufferfish
|
|
|
|
+ // Pufferfish start
|
|
public ShapelessRecipe(ResourceLocation id, String group, ItemStack output, NonNullList<Ingredient> input) {
|
|
+ this(id, group, output, input, false);
|
|
+ }
|
|
+ public ShapelessRecipe(ResourceLocation id, String group, ItemStack output, NonNullList<Ingredient> input, boolean isBukkit) { this.isBukkit = isBukkit; // Pufferfish end
|
|
this.id = id;
|
|
this.group = group;
|
|
this.result = output;
|
|
@@ -73,6 +78,28 @@ public class ShapelessRecipe implements CraftingRecipe {
|
|
}
|
|
|
|
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();
|
|
int i = 0;
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
|
|
index d1eefa6ef3e9abfe7af4d8310aa64465fa2d5463..0f4aa330e5b179bb706a31917c671f165e22b9cd 100644
|
|
--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
|
|
+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
|
|
@@ -73,6 +73,16 @@ public interface BlockGetter extends LevelHeightAccessor {
|
|
});
|
|
}
|
|
|
|
+ // Pufferfish start - broken down variant of below rayTraceBlock, used by World#rayTraceDirect
|
|
+ default net.minecraft.world.phys.BlockHitResult.Type rayTraceBlockDirect(Vec3 vec3d, Vec3 vec3d1, BlockPos blockposition, BlockState iblockdata, net.minecraft.world.phys.shapes.CollisionContext voxelshapecoll) {
|
|
+ if (iblockdata.isAir()) return null; // Tuinity - optimise air cases
|
|
+ VoxelShape voxelshape = ClipContext.Block.COLLIDER.get(iblockdata, this, blockposition, voxelshapecoll);
|
|
+ net.minecraft.world.phys.BlockHitResult movingobjectpositionblock = this.clipWithInteractionOverride(vec3d, vec3d1, blockposition, voxelshape, iblockdata);
|
|
+
|
|
+ return movingobjectpositionblock == null ? null : movingobjectpositionblock.getType();
|
|
+ }
|
|
+ // Pufferfish end
|
|
+
|
|
// CraftBukkit start - moved block handling into separate method for use by Block#rayTrace
|
|
default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) {
|
|
// Paper start - Prevent raytrace from loading chunks
|
|
diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java
|
|
index 2c500cfdd594e24b039d698bced1f9f9537722e3..9fa0758c0a8c74dec6cfe8a16585ace1cf45df96 100644
|
|
--- a/src/main/java/net/minecraft/world/level/GameRules.java
|
|
+++ b/src/main/java/net/minecraft/world/level/GameRules.java
|
|
@@ -91,6 +91,7 @@ public class GameRules {
|
|
public static final GameRules.Key<GameRules.BooleanValue> RULE_UNIVERSAL_ANGER = GameRules.register("universalAnger", GameRules.Category.MOBS, GameRules.BooleanValue.create(false));
|
|
public static final GameRules.Key<GameRules.IntegerValue> RULE_PLAYERS_SLEEPING_PERCENTAGE = GameRules.register("playersSleepingPercentage", GameRules.Category.PLAYER, GameRules.IntegerValue.create(100));
|
|
private final Map<GameRules.Key<?>, GameRules.Value<?>> rules;
|
|
+ private final GameRules.Value<?>[] gameruleArray;
|
|
|
|
private static <T extends GameRules.Value<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
|
|
GameRules.Key<T> gamerules_gamerulekey = new GameRules.Key<>(name, category);
|
|
@@ -109,17 +110,33 @@ public class GameRules {
|
|
}
|
|
|
|
public GameRules() {
|
|
- this.rules = (Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> {
|
|
+ // Pufferfish start - use this to ensure gameruleArray is initialized
|
|
+ this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> {
|
|
return ((GameRules.Type) entry.getValue()).createRule();
|
|
- }));
|
|
+ })));
|
|
+ // Pufferfish end
|
|
}
|
|
|
|
private GameRules(Map<GameRules.Key<?>, GameRules.Value<?>> rules) {
|
|
this.rules = rules;
|
|
+
|
|
+ // Pufferfish start
|
|
+ int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1;
|
|
+ GameRules.Value<?>[] values = new GameRules.Value[arraySize];
|
|
+
|
|
+ for (Entry<GameRules.Key<?>, GameRules.Value<?>> entry : rules.entrySet()) {
|
|
+ values[entry.getKey().gameRuleIndex] = entry.getValue();
|
|
+ }
|
|
+
|
|
+ this.gameruleArray = values;
|
|
+ // Pufferfish end
|
|
}
|
|
|
|
public <T extends GameRules.Value<T>> T getRule(GameRules.Key<T> key) {
|
|
- return (T) this.rules.get(key); // CraftBukkit - decompile error
|
|
+ // Pufferfish start
|
|
+ return key == null ? null : (T) this.gameruleArray[key.gameRuleIndex];
|
|
+ //return (T) this.rules.get(key); // CraftBukkit - decompile error
|
|
+ // Pufferfish end
|
|
}
|
|
|
|
public CompoundTag createTag() {
|
|
@@ -178,6 +195,10 @@ public class GameRules {
|
|
}
|
|
|
|
public static final class Key<T extends GameRules.Value<T>> {
|
|
+ // Pufferfish start
|
|
+ private static int lastGameRuleIndex = 0;
|
|
+ public final int gameRuleIndex = lastGameRuleIndex++;
|
|
+ // Pufferfish end
|
|
|
|
final String id;
|
|
private final GameRules.Category category;
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index 596fb8ee21ba8450db13a11890d241ef3974d81d..791411866cabc2c237f40d54250d5a34653ceaa0 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -271,6 +271,17 @@ 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
|
|
+
|
|
+ // Pufferfish start - ensure these get inlined
|
|
+ private final int minBuildHeight, minSection, height, maxBuildHeight, maxSection;
|
|
+ @Override public final int getMaxBuildHeight() { return this.maxBuildHeight; }
|
|
+ @Override public final int getMinSection() { return this.minSection; }
|
|
+ @Override public final int getMaxSection() { return this.maxSection; }
|
|
+ @Override public final int getMinBuildHeight() { return this.minBuildHeight; }
|
|
+ @Override public final int getHeight() { return this.height; }
|
|
+ // Pufferfish end
|
|
+
|
|
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, 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
|
|
@@ -293,6 +304,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
});
|
|
final DimensionType dimensionmanager = (DimensionType) holder.value();
|
|
|
|
+ // Pufferfish start
|
|
+ this.minBuildHeight = dimensionmanager.minY();
|
|
+ this.minSection = SectionPos.blockToSectionCoord(this.minBuildHeight);
|
|
+ this.height = dimensionmanager.height();
|
|
+ this.maxBuildHeight = this.minBuildHeight + this.height;
|
|
+ this.maxSection = SectionPos.blockToSectionCoord(this.maxBuildHeight - 1) + 1;
|
|
+ // Pufferfish end
|
|
this.dimension = resourcekey;
|
|
this.isClientSide = flag;
|
|
if (dimensionmanager.coordinateScale() != 1.0D) {
|
|
@@ -408,6 +426,91 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
return null;
|
|
}
|
|
|
|
+ // Pufferfish start - broken down method of raytracing for EntityLiving#hasLineOfSight, replaces IBlockAccess#rayTrace(RayTrace)
|
|
+ public net.minecraft.world.phys.BlockHitResult.Type rayTraceDirect(net.minecraft.world.phys.Vec3 vec3d, net.minecraft.world.phys.Vec3 vec3d1, net.minecraft.world.phys.shapes.CollisionContext voxelshapecoll) {
|
|
+ // most of this code comes from IBlockAccess#a(RayTrace, BiFunction, Function), but removes the needless functions
|
|
+ if (vec3d.equals(vec3d1)) {
|
|
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
|
|
+ }
|
|
+
|
|
+ double endX = Mth.lerp(-1.0E-7D, vec3d1.x, vec3d.x);
|
|
+ double endY = Mth.lerp(-1.0E-7D, vec3d1.y, vec3d.y);
|
|
+ double endZ = Mth.lerp(-1.0E-7D, vec3d1.z, vec3d.z);
|
|
+
|
|
+ double startX = Mth.lerp(-1.0E-7D, vec3d.x, vec3d1.x);
|
|
+ double startY = Mth.lerp(-1.0E-7D, vec3d.y, vec3d1.y);
|
|
+ double startZ = Mth.lerp(-1.0E-7D, vec3d.z, vec3d1.z);
|
|
+
|
|
+ int currentX = Mth.floor(startX);
|
|
+ int currentY = Mth.floor(startY);
|
|
+ int currentZ = Mth.floor(startZ);
|
|
+
|
|
+ BlockPos.MutableBlockPos currentBlock = new BlockPos.MutableBlockPos(currentX, currentY, currentZ);
|
|
+
|
|
+ LevelChunk chunk = this.getChunkIfLoaded(currentBlock);
|
|
+ if (chunk == null) {
|
|
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
|
|
+ }
|
|
+
|
|
+ net.minecraft.world.phys.BlockHitResult.Type initialCheck = this.rayTraceBlockDirect(vec3d, vec3d1, currentBlock, chunk.getBlockState(currentBlock), voxelshapecoll);
|
|
+
|
|
+ if (initialCheck != null) {
|
|
+ return initialCheck;
|
|
+ }
|
|
+
|
|
+ double diffX = endX - startX;
|
|
+ double diffY = endY - startY;
|
|
+ double diffZ = endZ - startZ;
|
|
+
|
|
+ int xDirection = Mth.sign(diffX);
|
|
+ int yDirection = Mth.sign(diffY);
|
|
+ int zDirection = Mth.sign(diffZ);
|
|
+
|
|
+ double normalizedX = xDirection == 0 ? Double.MAX_VALUE : (double) xDirection / diffX;
|
|
+ double normalizedY = yDirection == 0 ? Double.MAX_VALUE : (double) yDirection / diffY;
|
|
+ double normalizedZ = zDirection == 0 ? Double.MAX_VALUE : (double) zDirection / diffZ;
|
|
+
|
|
+ double normalizedXDirection = normalizedX * (xDirection > 0 ? 1.0D - Mth.frac(startX) : Mth.frac(startX));
|
|
+ double normalizedYDirection = normalizedY * (yDirection > 0 ? 1.0D - Mth.frac(startY) : Mth.frac(startY));
|
|
+ double normalizedZDirection = normalizedZ * (zDirection > 0 ? 1.0D - Mth.frac(startZ) : Mth.frac(startZ));
|
|
+
|
|
+ net.minecraft.world.phys.BlockHitResult.Type result;
|
|
+
|
|
+ do {
|
|
+ if (normalizedXDirection > 1.0D && normalizedYDirection > 1.0D && normalizedZDirection > 1.0D) {
|
|
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
|
|
+ }
|
|
+
|
|
+ if (normalizedXDirection < normalizedYDirection) {
|
|
+ if (normalizedXDirection < normalizedZDirection) {
|
|
+ currentX += xDirection;
|
|
+ normalizedXDirection += normalizedX;
|
|
+ } else {
|
|
+ currentZ += zDirection;
|
|
+ normalizedZDirection += normalizedZ;
|
|
+ }
|
|
+ } else if (normalizedYDirection < normalizedZDirection) {
|
|
+ currentY += yDirection;
|
|
+ normalizedYDirection += normalizedY;
|
|
+ } else {
|
|
+ currentZ += zDirection;
|
|
+ normalizedZDirection += normalizedZ;
|
|
+ }
|
|
+
|
|
+ currentBlock.set(currentX, currentY, currentZ);
|
|
+ if (chunk.getPos().x != currentBlock.getX() >> 4 || chunk.getPos().z != currentBlock.getZ() >> 4) {
|
|
+ chunk = this.getChunkIfLoaded(currentBlock);
|
|
+ if (chunk == null) {
|
|
+ return net.minecraft.world.phys.BlockHitResult.Type.MISS;
|
|
+ }
|
|
+ }
|
|
+ result = this.rayTraceBlockDirect(vec3d, vec3d1, currentBlock, chunk.getBlockState(currentBlock), voxelshapecoll);
|
|
+ } while (result == null);
|
|
+
|
|
+ return result;
|
|
+ }
|
|
+ // Pufferfish end
|
|
+
|
|
public boolean isInWorldBounds(BlockPos pos) {
|
|
return pos.isInsideBuildHeightAndWorldBoundsHorizontal(this); // Paper - use better/optimized check
|
|
}
|
|
@@ -896,13 +999,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
|
|
}
|
|
}
|
|
@@ -1388,6 +1491,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 7c7408b9686c8ff945c30892d45914c60f6c5e4a..a182b3c804cf56855e15290f826fdebdc289a552 100644
|
|
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
|
|
@@ -415,12 +415,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 ca259e278ad10347567c021376abca0287610432..021f7c44285b13a0904c09ea7210f2a5d10c7c61 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
|
|
});
|
|
});
|
|
|
|
@@ -114,17 +120,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 ccad692aba2ed77259f6814d88f01b91ed9d229b..698a0ac71cf9fb409d609a81cc2958121260a46d 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
|
|
@@ -43,7 +43,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;
|
|
|
|
@@ -79,14 +82,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);
|
|
}
|
|
@@ -158,7 +184,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
flag = HopperBlockEntity.ejectItems(world, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit
|
|
}
|
|
|
|
- if (!blockEntity.inventoryFull()) {
|
|
+ if (!blockEntity.optimizedItems.hasFullStacks() || !blockEntity.inventoryFull()) { // Pufferfish - use bitset first
|
|
flag |= booleansupplier.getAsBoolean();
|
|
}
|
|
|
|
@@ -197,7 +223,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
skipPushModeEventFire = skipHopperEvents;
|
|
boolean foundItem = false;
|
|
for (int i = 0; i < hopper.getContainerSize(); ++i) {
|
|
- ItemStack item = hopper.getItem(i);
|
|
+ ItemStack item = hopper.getItem(i); // Pufferfish
|
|
if (!item.isEmpty()) {
|
|
foundItem = true;
|
|
ItemStack origItemStack = item;
|
|
@@ -400,12 +426,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
|
|
}
|
|
|
|
private static boolean isFullContainer(Container inventory, Direction direction) {
|
|
- return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams
|
|
+ // Pufferfish start - use bitsets
|
|
+ //return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams
|
|
+ return inventory.isCompletelyFull(direction);
|
|
+ // Pufferfish end
|
|
}
|
|
|
|
private static boolean isEmptyContainer(Container inv, Direction facing) {
|
|
// Paper start
|
|
- return allMatch(inv, facing, IS_EMPTY_TEST);
|
|
+ // Pufferfish start - use bitsets
|
|
+ //return allMatch(inv, facing, IS_EMPTY_TEST);
|
|
+ return inv.isCompletelyEmpty(facing);
|
|
+ // Pufferfish end
|
|
}
|
|
private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
|
|
if (iinventory instanceof WorldlyContainer) {
|
|
@@ -582,7 +614,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
|
|
@@ -730,7 +762,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 d559f93a9a09bac414dd5d58afccad42c127f09b..13e749a3c40f0b2cc002f13675a9a56eedbefdac 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,13 +96,8 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
|
|
public boolean isEmpty() {
|
|
this.unpackLootTable((Player)null);
|
|
// Paper start
|
|
- for (ItemStack itemStack : this.getItems()) {
|
|
- if (!itemStack.isEmpty()) {
|
|
- return false;
|
|
- }
|
|
- }
|
|
+ return this.isCompletelyEmpty(null); // Pufferfish - use super
|
|
// Paper end
|
|
- return true;
|
|
}
|
|
|
|
@Override
|
|
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 5e54cf312160e537d2fe6e6fedc618160359330e..39684720c5ee2bb36b8d12cc10e05b7ab8d06172 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -88,6 +88,18 @@ public class LevelChunk extends ChunkAccess {
|
|
private final LevelChunkTicks<Block> blockTicks;
|
|
private final LevelChunkTicks<Fluid> fluidTicks;
|
|
|
|
+ // 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);
|
|
}
|
|
@@ -118,6 +130,7 @@ public class LevelChunk extends ChunkAccess {
|
|
this.fluidTicks = fluidTickScheduler;
|
|
// CraftBukkit start
|
|
this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
|
|
+ this.lightningTick = this.level.getThreadUnsafeRandom().nextInt(100000) << 1; // Pufferfish - initialize lightning tick
|
|
}
|
|
|
|
public org.bukkit.Chunk bukkitChunk;
|
|
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 b0c9fce9d4e06cac139e341d218d0b6aac1f1943..f25467ad1c5bac7eaef4b63b2845ad04d7c76e4e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
@@ -27,6 +27,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
|
|
|
|
public LevelChunkSection(int i, PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
|
|
@@ -198,6 +199,7 @@ public class LevelChunkSection {
|
|
|
|
if (!fluid.isEmpty()) {
|
|
--this.tickingFluidCount;
|
|
+ --this.fluidStateCount; // Pufferfish
|
|
}
|
|
|
|
if (!state.isAir()) {
|
|
@@ -212,6 +214,7 @@ public class LevelChunkSection {
|
|
|
|
if (!fluid1.isEmpty()) {
|
|
++this.tickingFluidCount;
|
|
+ ++this.fluidStateCount; // Pufferfish
|
|
}
|
|
|
|
this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper
|
|
@@ -260,6 +263,7 @@ public class LevelChunkSection {
|
|
if (fluid.isRandomlyTicking()) {
|
|
this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
|
|
}
|
|
+ this.fluidStateCount++; // Pufferfish
|
|
}
|
|
|
|
});
|
|
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 ff40fe323964f173561a6838fb443e79abf9df38..c2c3ed6ba79f9f41497e042571f699a0fc6e9335 100644
|
|
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
|
|
+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
|
|
@@ -43,6 +43,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) {}
|
|
@@ -51,6 +53,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() {}
|
|
@@ -239,6 +249,8 @@ public abstract class FlowingFluid extends Fluid {
|
|
}
|
|
|
|
private boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) {
|
|
+ // Pufferfish start - modify to use our cache
|
|
+ /*
|
|
Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap;
|
|
|
|
if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
|
|
@@ -246,9 +258,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);
|
|
@@ -259,11 +278,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();
|
|
@@ -271,6 +301,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/LootContext.java b/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java
|
|
index 35f9b11a3a61976c952a2c1c64bb2a932538f54f..9e9ac64764cf0a84e25e75d8d6f516cde6047284 100644
|
|
--- a/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java
|
|
+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootContext.java
|
|
@@ -41,8 +41,10 @@ public class LootContext {
|
|
this.level = world;
|
|
this.lootTables = tableGetter;
|
|
this.conditions = conditionGetter;
|
|
- this.params = ImmutableMap.copyOf(parameters);
|
|
- this.dynamicDrops = ImmutableMap.copyOf(drops);
|
|
+ // 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(drops);
|
|
+ // Pufferfish end
|
|
}
|
|
|
|
public boolean hasParam(LootContextParam<?> parameter) {
|
|
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 07eac5439164a7345476c55277538a152359630a..a4848a507f620cff686992c2e95039ca60af6d0e 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -248,7 +248,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");
|
|
@@ -1052,6 +1052,11 @@ public final class CraftServer implements Server {
|
|
plugin.getDescription().getName(),
|
|
"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 0b3b46348ac9195bff1492ffc11fcbff7d3f5c6f..4010052c53f3a2831b4d5aa1c041d85897856acb 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java
|
|
@@ -43,6 +43,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe
|
|
data.set(i, toNMS(ingred.get(i), true));
|
|
}
|
|
|
|
- MinecraftServer.getServer().getRecipeManager().addRecipe(new net.minecraft.world.item.crafting.ShapelessRecipe(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftItemStack.asNMSCopy(this.getResult()), data));
|
|
+ MinecraftServer.getServer().getRecipeManager().addRecipe(new net.minecraft.world.item.crafting.ShapelessRecipe(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftItemStack.asNMSCopy(this.getResult()), data, true));
|
|
}
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
index 1628913b1e9b91e68dcd942a38da4aed95b12d4a..05cc8f9cdcd7e920bf9503f68efb16cd74a359a2 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
@@ -430,7 +430,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
|
|
|
@Override
|
|
public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
|
|
- return new 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 1b42c98956342832c37f0aa266f85271daa4ba5b..b87756d9a7b04ea2613208984b2583eca3f32af6 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
|
|
{
|
|
@@ -216,6 +220,25 @@ public class ActivationRange
|
|
for (int i = 0; i < entities.size(); i++) {
|
|
Entity entity = entities.get(i);
|
|
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
|
|
}
|
|
@@ -232,12 +255,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;
|
|
}
|
|
}
|
|
@@ -291,7 +314,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
|
|
}
|